Reading Multiline, Word, and HTML Data From the Clipboard

Basically, I just want something that is syntactically readable to me. (I’ve been doing Javascript in my spare time for 3 weeks).

I find the notation and docs completely incomprehensible, so (for example), “adding a callback with .then()”, is less comprehensible to me than “(⍴X)⍴(,X)[A[⍋(,⍉(⌽⍴X)⍴⍳1↑⍴X)[A←⍋,X]]]”, which I know sorts a matrix in ascending order.

The example given earlier (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) does not appear to me to show how to actually get a value from a returned promise, just the success or failure of a hypothetical function, which in my case, is as proverbially useful as nipples on a bull.

I do understand how this promises might make Javascript run more smoothly, but I really don’t care in my application, because the syntax has become even more obscure.

I just want to get the contents of the clipboard into a variable reliably with code that is as clear (to me) as possible.

I understand that this will necessarily mean that the code is slower and larger than it would be using “good” programming techniques, but this is some simple string handling, not the back end at healthcare.gov.

If you get an object back from a function, try viewing it as JSON. Not the easiest format, but many viewers can “pretty print” it. So in your example:

var moo = readFromClipboard('text/plain');
console.log(JSON.stringify(moo));

Once you know the structure, you can pull out the parts you want.

Actually, no, that would work if readFromClipboard() is not asynchronous. I guess you need something more like:

var moo = readFromClipboard('text/plain');
moo.then(moo => console.log(JSON.stringify(moo)));

I just want to get the contents of the clipboard into a variable reliably with code that is as clear (to me) as possible.

I understand that this will necessarily mean that the code is slower and larger

Ok, so maybe it helps if (for now) you forget how the promise API works. Simply see all functions that return promises as async functions.
Whenever you call an async function, put the await keyword in before the function invocation, and maybe, just to be sure about operator precedence, wrap the entire thing in parentheses.
And to be allowed to use await in a function, it needs to be declared as async, which in turn means it has to be invoked with await.

You may end up with way to many awaits, which can make the code slow, but the code will still be quite readable and short.

And for now, don’t ever use .then(). There is hardly ever a reason to do so. In my code (and I think I use promises as efficiently as possible) I use less than 10% of the promises explicitly.

So the shortest solution to the clipboard reading is:

async function someEventOrWhatever() {
    // ...
    const moo = (await readFromClipboard('text/plain'));
    console.log('`moo` is:', moo);
    // ...
}

Also, don’t call JSON.stringify on stuff you log. The console itself will offer the object for inspection much nicer (as long as you don’t make the mistake of concatenating it with the description string).

1 Like

OK, I’ll try it.

BTW, why are you assigning moo to a constant rather than a variable?

Because it’s not changed inside that snippet. const just means you can’t change it after setting it, other than that it’s exactly the same as let.

OK, I have a simplified extension, and I am checking it out on FF 56 as well as FF 63 Beta (In FF 56, I change dom.moduleScripts.enabled to true.), and I attempt to import the module:

import {Readfromclipboard} from './es6lib/dom.js';

I added dom.js to the manifest:

	"content_scripts": [
		{
		"matches": ["*://*/*"],
		"js": ["content_script.js", "./es6lib/dom.js"]
		}
	],

I get the following error:
SyntaxError: import declarations may only appear at top level of a module

Whether I attempt this in the background script or the content script.

Am I missing something?

I don’t think you can use the module syntax at all in content scripts, and that is where you need to use this function.

My script is a “normal” script that exposes its exports either to a AMD loader or as a global variable.
The scripts default path (when installed via npm i es6lib) is node_modules/es6lib/dom.js.
For you the easiest way (unless you are already using require.js or something like that) would be to load es6lib/dom.js first, then use it from the es6lib_dom global:

manifest.json:

	"content_scripts": [ {
		"matches": ["*://*/*"],
		"js": [ "node_modules/es6lib/dom.js", "content_script.js" ]
	} ],

content_script.js:

(function(global) {

const { readFromClipboard, } = global.es6lib_dom

// ... lots of other code, I presume

async function someEventOrWhatever() {
    // ...
    const moo = (await readFromClipboard('text/plain'));
    console.log('`moo` is:', moo);
    // ...
}

// ... more of your code

})();

That, and I find it to be a good practice to mark everything I don’t intend to change (which is the vast majority of variables) as const. Makes it easier to read, lint and maybe even optimize the code.
All other variables should be lets. Don’t use var. It is one of those “broken” ES5 (and below) things that have better alternatives in ES6 (and above).

1 Like

OK, I just tried that:

The relevent section of my manifest.json reads:

"content_scripts": [
	{
	"matches": ["*://*/*"],
	"js": ["node_modules/es6lib/dom.js", "content_script.js"]
	}
],

I did an NPM install on windows from the root of the addon as follows:

C:\Moo\phpBB\quick>npm install --prefix . ..\NiklasGollenstede-es6lib-v0.0.2-0-g
e0da2b6.tar.gz

The directory was created and the output was:

C:\Moo\phpBB\quick
└── es6lib@0.0.2

When I attempt to load the addon through the debugging panel, I get the following error in the browser console:
TypeError: global is undefined, can't access property "es6lib_dom" of it

The code for my content_script.js is:
(function(global) {

const { readFromClipboard, } = global.es6lib_dom

var clickedElement = null;

document.addEventListener("mousedown", function(event){
    //right click
    if(event.button == 2) { 
        clickedElement = event.target;
    }
}, true);
browser.runtime.onMessage.addListener(function(RunParse, CommandString, sendResponse) {
var CallFunction = RunParse.runwhat;
var ArgString = RunParse.ParseArg;
       CommandParse (ArgString);
        sendResponse({value: clickedElement.value});
});

async function CommandParse(ArgString) {
//       Get Info About Textbox
 var TextBoxName = clickedElement.getAttribute('name');
 var TextBoxID = clickedElement.getAttribute('id');
 var FocusInfo = document.activeElement;
var txtcont = document.getElementById(TextBoxID).value; //contents of edit box
var selstart = clickedElement.selectionStart; // index of selectin start
var selend = clickedElement.selectionEnd; //index of selection end
var selcont = txtcont.substring(selstart,selend); // selected text content
var firsttext = txtcont.substring(0,selstart); //stuff before the selection
var lasttext = txtcont.substring(selend); // stuff after the selection
//var clipcont = readClipboard (); //contents of clipboard
/*
console.log("moo:",moo);
    // ...
    const moop = await readFromClipboard('text/plain');
    console.log('`moop` is:', moop);
    // ...
*/

//simplified pasting

}

})();

Am I missing something?

You are not passing in anything to your iife as global param. You probably want to pass in window (or not use an iife to make window called global at all…)

I was literally copying and pasting what NilkasG suggested in order to run the he entered above.

I’ve been unable to get it to load into the content script or into the context script, and I can’t get it to work when I paste the code inline to either.

I’m just trying to find a syntactically simple way to test out how this works on reading the clipboard value, and I have zero concern about efficiency, and negative concern about the latest and greatest features of Javascript.

My mistake, the last line of the script should be })(this);.

or not use an iife to make window called global at all…

Yeah, but that is another of those weird JS things. It doesn’t really make sense for the global variable to be named window. Or for the Window instance to be the global variable.
With a function wrapper like this the code works in node.js as well.

I’m sure you know https://github.com/tc39/proposal-global

Thanks for the fix.

I am getting one of two errors now:

In FF 56:
Error: Timeout after 1000ms dom.js:209:10

In FF 63 Beta:
NoModificationAllowedError: Modifications are not allowed for this document dom.js:198
The context menu is firing off:

case "test_clipboard":
    browser.tabs.sendMessage(tab.id,{runwhat: "zzBBCode", ParseArg: "[b]{{selection}}[/b]"});
break;

And the variable is passing to the content script: (much simplified to test just access to the clipboard)

(function(global) {

const { readFromClipboard, } = global.es6lib_dom

var clickedElement = null;



document.addEventListener("mousedown", function(event){
    //right click
    if(event.button == 2) { 
        clickedElement = event.target;
    }
}, true);

browser.runtime.onMessage.addListener(function(RunParse, CommandString, sendResponse) {
var CallFunction = RunParse.runwhat;
var ArgString = RunParse.ParseArg;
       CommandParse (ArgString);
        sendResponse({value: clickedElement.value});
});



async function CommandParse(ArgString) {

console.log("Confirm passing Argstring: ",ArgString);
    // ...
    const moop = (await readFromClipboard('text/plain'));
    console.log('`moop` is:', moop);
    // ...


console.log("After Read from Clipboard:");

}

})(this);

On the web console, I get:

Confirm passing Argstring: [b]{{selection}}[/b]

And in the browser console, I error messages.

I’ve never heard about a NoModificationAllowedError, but the most likely cause for the timeout is that you are not allowed to read the clipboard (at that time).

Try to read the clipboard at the same tiem you grab the clicked element (in a mousedown/click/whatever user action DOM handler).

As for the other error, you could try to remove the transfer.clearData(); line. I really can’t remember what that one is supposed to do. If that fixes the problem, I’ll update the code.

Just to clarify, you are suggesting that I should invoke it on the background page (where the right click menu is called), and not the foreground page.

That could work as well (with the right permission), but I was suggesting you do it in the content scripts, just earlier, when the DOM mousedown event fires (which should work without any specific permissions).

Thank you.

You sir, are a king among men, may the Flying Spaghetti Monster bless you with his noodly appendage.

Commenting out transfer.clearData(); fixed the issue on FF 63 beta, and multiline, html, and word clipboard contents work great.

FF56 still does not work, but I figure that if someone wants to test it out who is still using that, they can use FF portable.

I am going to work my way back to see what is the earliest version where this works.

(on edit) It works on 57.0, but not on pre-Quantum FF.

Well, thank you too! I see you are a man of culture.

I’ve updated the published version of es6lib without the transfer.clearData().

Do you remember why you included the code so in the first place?

I am pretty sure I started with the reading function as a copy of the clipboard writing function. When writing, you get a mutable DataTransfer, which you need to clear first (in case the user had something selected).
Then writing the transfer seems to be immutable. I just forgot the .clearData() in the function. It seems that prevously it was a no-op (so I didn’t notice), but that changed and now it threw.