Reading the clipboard


(Rappazf) #1

I try to get a readClipboard function on the same model … thanks for the correction

/**

  • Attempts to read data from the users clipboard.
  • @param {natural} time Maximum runtime of this asynchronous operation after which it will be canceled and rejected.
  • @return {Promise} Promise that rejects if the timeout or an error occurred. If it resolves the operation should have succeeded.
    */
    function readFromClipboard(time) { return new Promise(function(resolve, reject) {
    let done = false;
    function onPaste(event) { try {
    if (done) { return; } done = true;
    document.removeEventListener(‘paste’, onPaste);
    const transfer = event.clipboardData;
    //transfer.clearData();
    var data = transfer.getData(‘text/plain’);
    console.log("found ", data);
    if (typeof data === ‘string’) {
    resolve(data);
    } else {
    //Object.keys(data).forEach(mimeType => transfer.setData(mimeType, data[mimeType]));
    reject(new Error(“Can’t read the Clipboard”));
    }
    event.preventDefault();
    //resolve();
    } catch (error) { reject(error); } }
    setTimeout(() => {
    if (done) { return; } done = true;
    document.removeEventListener(‘paste’, onPaste);
    reject(new Error('Timeout after '+ (time || 1000) +‘ms’));
    }, time || 1000);
    document.addEventListener(‘paste’, onPaste);
    document.execCommand(‘paste’, false, null);
    }); }

    And to call this, I tried

    var p = readFromClipboard();
    p.then(function(data){console.log("fetch clipboard ", data);}, function(err){console.log("Error ", err);}
    );

    without success…
    F.

(Rappazf) #2

I would appreciate a comment on this … Is my code wrong or is it a firefox issue ?

Thanks

François


(Niklas Gollenstede) #3

That is the function I suggested. Why did you remove the data parameter from the function signature?

And if it fails, what is the console output? (By the way, you should use console.error for errors)
Where are you calling it from?


(Rappazf) #4

You suggestion was for writing to the clipboard,
now I want to read it … So I don’t need to pass it a data, I need to return the data …
The console says <unavailable>
F


(Niklas Gollenstede) #5

now I want to read it

Oh. I missed that.
Now, I have never done that in JavaScript (*). But it continued the changes you started and (to my surprise) it actually works – in Chrome on a focused extension page if the "clipboardRead" permission is set:

But AFAIK, Firefox doesn’t support that permission and content scripts probably won’t work at all.
So if it works at all, this function will. But it probably won’t due to permission problem:

/**
 * Attempts read data from the users clipboard.
 * @param  {string|[string]|null}   types  An optional (Array of) mime types to read.
 * @param  {natural}                time   Optional. Maximum runtime of this asynchronous operation after which it will be canceled and rejected.
 * @return {Promise<string|object>}        Promise to the current clipboard data as string. If types was an array, it returns an object of { [mimeType]: data, } pairs.
 */
function readFromClipboard(types, time) { return new Promise(function(resolve, reject) {
	let done = false;
	function onPaste(event) { try {
		if (done) { return; } done = true;
		document.removeEventListener('paste', onPaste);
		const transfer = event.clipboardData;
		transfer.clearData();
		if (typeof types === 'string' || !types) {
			resolve(transfer.getData(types || 'text/plain'));
		} else {
			resolve(types.reduce((data, mimeType) => ((data[mimeType] = transfer.getData(mimeType)), data), { }));
		}
		event.preventDefault();
	} catch (error) { reject(error); } }
	setTimeout(() => {
		if (done) { return; } done = true;
		document.removeEventListener('paste', onPaste);
		reject(new Error('Timeout after '+ (time || 1000) +'ms'));
	}, time || 1000);
	document.addEventListener('paste', onPaste);
	document.execCommand('paste', false, null);
}); }

*) I think ireading the clipboard can be quite intrusive. A prompt() has the same effect and clearly shows what’s happening. But if reading/writing the clipboard is the primary functionality of your extension, then it can be acceptable.


(Rappazf) #6

Thanks for the function.

My addon should modify the clipboard content before pasting it.
I have been told that clipboardRead is supported since Firefox 54.
I tried this: in manifest

"permissions": [ “storage”, “contextMenus”, “http:///”, “https:///”, “activeTab”, “tabs”, “clipboardRead”, “clipboardWrite”],

I navigate to a page, select text and copy it (ctrl + c) to the clipboard. I click on the addon button. This send a message from background.js to content script.
in content.js

chrome.runtime.onMessage.addListener(
function (request, sender){
console.log("Received form background in CS: ", request.data);
var p = readFromClipboard();
p.then(function(data){console.log("Clpbrd: " + data)}, function(err){console.error("error: " + err)});

}

);

I receive the error error: Error: Timeout after 1000ms.

What am I missing ?


(Niklas Gollenstede) #7

In many (maybe all?) regards, content scripts are executed with the permission of the page and not the add-on’s permissions. Seeing the “clipboardRead” permission probably had no effect there, which means it may be impossible to read from the clipboard that way.


(Rappazf) #8

Is there another way ?

Thanks, using this with nigthly I see that the paste command is not implemented.
can I conclude that there is no way to port my addon to webextension ?
Will that be possible in a near futur ?

Thanks for helping anyway


(Niklas Gollenstede) #9

Mozilla at least considered to implement some toolbar stuff. I don’t know the status of that.

You could experiment with sidebars too.


(Rappazf) #10

With the help of https://bugzilla.mozilla.org/show_bug.cgi?id=1364708 I get this that works:

function readClipboard () {
field = document.activeElement;
let area = document.createElement(“textarea”);
//document.body.appendChild(area);
field.parentNode.append(area);
area.contentEditable = true;
area.textContent = ‘’;
area.select();
console.log('Pre-paste: ’ + area.value);
console.log(document.execCommand(“paste”));
console.log('Post-paste: ’ + area.value);
area.setAttribute(“style”, “visibility:hidden;”);
return area.value;
}

I have to remember the field in the form where the cursor is, so field is defined in the script scope. It is used to insert the text fetch from the clipboard and modify by the extension.

Inserting the text area near the field prevent the display to jump down when the context menu is clicked. Depending on how the html page is made, there still is a modification when the context menu is used, which is not good…
So the function using the promise would be nicer if it worked