Copy to cliboard

Hi! I’m wondering if anybody uses copy to clipboard functionality. Every time I call copyText I’m getting error Plugin Error: Plugin xd.plugin is not permitted to make changes from the background. Return a Promise to continue execution asynchronously.. The code I use is:

public async copyUrl(): Promise<void> {
  try {
    await clipboard.copyText(this.shareUrl);
    this.shareSucceed = true;
  } catch (ex) {
    this.shareError = ex;
  }
}

Any ideas what’s wrong?

Checked other topics it seems that background problem is pretty common for different not background operations: Using Jimp to write images and Changing `selection.items` without "not permitted to make changes from the background" error.

Promises have to be returned to continue execution asynchronously as the error message suggest.s However, in this case, copyText is a synchronous method. You don’t have to await or wrap it around with a promise block.

Really error doesn’t depend on if I await for result on not. If I just call copyText in a sync manner error is the same:

Plugin Error: Plugin io.sympli.xd.plugin is not permitted to make changes from the background. Return a Promise to continue execution asynchronously.
    at convertPluginErrorToString (plugins/PluginErrorUtil.js:1:198)
    at internalFormatPluginError (plugins/PluginErrorUtil.js:1:1073)
    at internalReportPluginError (plugins/PluginErrorUtil.js:1:1180)
    at Object.reportPluginError (plugins/PluginErrorUtil.js:1:1612)
    at Object.checkAllowedToEdit (plugins/ScenegraphGuard.js:1:1435)
    at Object.<anonymous> (plugins/ClipboardWrapper.js:1:194)
    at t.copyUrl (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\main.js:1:501323)

Where is copyUrl() being called from? I don’t see anything below it in your call stack. You have to be getting called from XD (in response to a user action) and return the Promise back to that caller. If you’re not acting in response to a user action, you won’t be able to modify the clipboard.

1 Like

copyUrl is a click handler so it’s called on button click. @stevekwak, said that copyText is synchronous method so I don’t need to return promise back. Is it correct?
Just curious why clipboard wrapper calls checkAllowedToEdit? I’m not editing anything, I’m just trying to copy some text.
Full stack is:

Plugin Error: Plugin xd.plugin is not permitted to make changes from the background. Return a Promise to continue execution asynchronously.
    at convertPluginErrorToString (plugins/PluginErrorUtil.js:1:198)
    at internalFormatPluginError (plugins/PluginErrorUtil.js:1:1073)
    at internalReportPluginError (plugins/PluginErrorUtil.js:1:1180)
    at Object.reportPluginError (plugins/PluginErrorUtil.js:1:1612)
    at Object.checkAllowedToEdit (plugins/ScenegraphGuard.js:1:1435)
    at Object.<anonymous> (plugins/ClipboardWrapper.js:1:194)
    at t.copyUrl (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\main.js:1:501319)
    at Object.handleEvent (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\main.js:1:505516)
    at Object.handleEvent (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\main.js:1:202654)
    at Object.handleEvent (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\main.js:1:224395)
    at So (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\main.js:1:173569)
    at C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\main.js:1:179549
    at b.<anonymous> (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\main.js:1:275734)
    at e.invokeTask (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\polyfills.js:1:7850)
    at Object.onInvokeTask (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\main.js:1:141548)
    at e.invokeTask (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\polyfills.js:1:7771)
    at t.runTask (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\polyfills.js:1:3032)
    at t.invokeTask [as invoke] (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\polyfills.js:1:8933)
    at m (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\polyfills.js:1:22379)
    at b.k (C:\Users\avgre\AppData\Local\Packages\Adobe.CC.XD.Prerelease_adky2gkssdxte\LocalState\develop\xd.plugin\src\polyfills.js:1:22609)
    at uxp://uxp-internal/home/ubuntu/jenkins/workspace/Torq/torq-native/release-3.1.0/build/modules_gen/domjs/src/js/domjs_scripts.js:322:893
    at j (uxp://uxp-internal/home/ubuntu/jenkins/workspace/Torq/torq-native/release-3.1.0/build/modules_gen/domjs/src/js/domjs_scripts.js:322:785)
    at g (uxp://uxp-internal/home/ubuntu/jenkins/workspace/Torq/torq-native/release-3.1.0/build/modules_gen/domjs/src/js/domjs_scripts.js:322:390)
    at f (uxp://uxp-internal/home/ubuntu/jenkins/workspace/Torq/torq-native/release-3.1.0/build/modules_gen/domjs/src/js/domjs_scripts.js:322:192)
    at k (uxp://uxp-internal/home/ubuntu/jenkins/workspace/Torq/torq-native/release-3.1.0/build/modules_gen/domjs/src/js/domjs_scripts.js:322:1097)
    at l (uxp://uxp-internal/home/ubuntu/jenkins/workspace/Torq/torq-native/release-3.1.0/build/modules_gen/domjs/src/js/domjs_scripts.js:322:1468)
    at b.value (uxp://uxp-internal/home/ubuntu/jenkins/workspace/Torq/torq-native/release-3.1.0/build/modules_gen/domjs/src/js/domjs_scripts.js:82:7361)

It’s hard to debug without looking at your code. Would you be able to share your plugin with us? Please feel free to send me the file at kwak@adobe.com

With everything simplified, this should work:

const clipboard = require("clipboard");

function test() {
    clipboard.copyText("hello world");
}

module.exports = {
    commands: {
        test
    }
};

When plugin is run, “hello world” text will be copied to the clipboard

1 Like

@stevekwak, could you please try this one:

function helloHandlerFunction(selection) {
    const clipboard = require("clipboard");
    
    if (dialog == null) {
        //  create the dialog
        dialog = document.createElement("dialog");

        //  create the form element
        //  the form element has default styling and spacing
        let form = document.createElement("form");
        dialog.appendChild(form);
        //  don't forget to set your desired width
        form.style.width = 200;

        //  create a footer to hold your form submit and cancel buttons
        let footer = document.createElement("footer");
        form.appendChild(footer);

        let copyButton = document.createElement("button");
        copyButton.uxpVariant = "cta";
        copyButton.textContent = "Copy";
        copyButton.onclick = (e) => { clipboard.copyText("hello world"); };
        footer.appendChild(copyButton);
    }
    
    document.body.appendChild(dialog).showModal();
}

You need to return that because .showModal() returns a promise:

return document.body.appendChild(dialog).showModal();
1 Like

Thanks, that works for dialogs. What should be returned in case of panels?

function showCopyPanel() {
    const clipboard = require("clipboard");

    panel = document.createElement("panel");

    let form = document.createElement("form");
    panel.appendChild(form);
    form.style.width = 200;
    let footer = document.createElement("footer");
    form.appendChild(footer);

    let copyButton = document.createElement("button");
    copyButton.uxpVariant = "cta";
    copyButton.textContent = "Copy";
    copyButton.onclick = (e) => { clipboard.copyText("hello world"); };
    footer.appendChild(copyButton);

    return document.body.appendChild(panel);
}

and show event just call it:

show(event) {
  return showCopyPanel();
}

You need to attach the created panel to the event.node object. Take a look at this example and let me know if you have any questions.

Did I get it right, you mean something like following:

function showCopyPanel(container) {
    const clipboard = require("clipboard");
    panel = document.createElement("panel");
    let form = document.createElement("form");
    panel.appendChild(form);
    form.style.width = 200;
    let footer = document.createElement("footer");
    form.appendChild(footer);

    let copyButton = document.createElement("button");
    copyButton.uxpVariant = "cta";
    copyButton.textContent = "Copy";
    copyButton.onclick = (e) => { clipboard.copyText("hello world"); };
    footer.appendChild(copyButton);

    return container.appendChild(panel);
}

and show event

show(event) {
  return showCopyPanel(event.node);
}

The result is exactly the same :frowning: If you don’t mind could you please share code snippet with tested working example. Maybe it’s platform issue? I’m testing it on windows 10.

Wrap the above like so:

copyButton.onclick = (e) => {
    require("application").editDocument( () => clipboard.copyText("hello world"); );
}

Not sure why copy text requires document editing but it works! Thank you very much!

I’m getting the same error attempting to copy from a dialog:

<dialog>
  <button onclick="require('clipboard').copyText('hello world')">copy</button>
</dialog>

Throws with the following message:

Plugin <id> is not permitted to make changes from the background. Return a Promise to continue execution asynchronously.

and

<dialog>
  <button onclick="require('application').editDocument(() => require('clipboard').copyText('hello world'))">copy</button>
</dialog>

Throws:

Panel plugin edits must be initiated from a supported UI event.

Just to clarify, are using the code exactly like you shared? I checked the code you shared using innerHTML and it doesn’t work, it throws Plugin Error: Refusing to load event handler tag as executable code. The node was injected using innerHTML.

I’m asking because edit operation is very sensitive, e.g. I can’t use it because it doesn’t work after await: What is Panel plugin edits must be initiated from a supported UI event

What happens when you add an id and an uxpEditLabel?

<dialog>
   <button id="myButton" uxpEditLabel="Copy Text" onclick="require('application').editDocument(() => require('clipboard').copyText('hello world'))">copy</button>
</dialog>

You also have a typo in your onclick event:

 onclick="require('application" <-- non matching double quotes

@gdreyv @Velara apologies for the lack of clarity and the typo in my message above. Below are two repros you can try in your plugin. @Velara adding uxpEditLabel has no effect on either.

document.body.innerHTML = '<div id="container"></div>';
document.getElementById('container').innerHTML = `
  <dialog id="dialog">
    <button id="button">copy</button>
  </dialog>
`;

document.getElementById('dialog').showModal();
document.getElementById('button').addEventListener('click', () => {
  require('clipboard').copyText('hello world');
});

Throws:

Plugin Error: Plugin <my_plugin_id> is not permitted to make changes from the background. Return a Promise to continue execution asynchronously.
    at convertPluginErrorToString (plugins/PluginErrorUtil.js:1:198)
    at internalFormatPluginError (plugins/PluginErrorUtil.js:1:1073)
    at internalReportPluginError (plugins/PluginErrorUtil.js:1:1180)
    at Object.reportPluginError (plugins/PluginErrorUtil.js:1:1612)
    at Object.checkAllowedToEdit (plugins/ScenegraphGuard.js:1:1572)
    at Object.<anonymous> (plugins/ClipboardWrapper.js:1:194)
    at b.document.getElementById.addEventListener
   ...

and

document.body.innerHTML = '<div id="container"></div>';
document.getElementById('container').innerHTML = `
  <dialog id="dialog">
    <button id="button">copy</button>
  </dialog>
`;

document.getElementById('dialog').showModal();
document.getElementById('button').addEventListener('click', () => {
  require('application').editDocument(() =>
    require('clipboard').copyText('hello world')
  );
});

Throws:

Plugin Error: Panel plugin edits must be initiated from a supported UI event
    at Object.checkAllowedPanelToEdit (plugins/ScenegraphGuard.js:1:2127)
    at Object.<anonymous> (plugins/ApplicationWrapper.js:1:4137)
    at b.document.getElementById.addEventListener
   ...

Sorry, I didn’t try to reproduce your error but check posts above Copy to cliboard - showModal really is not a void function. It returns Promise which you must return to the parent function in order to use something like editDocument. I guess it must be warned by the app but not it’s tricky. @stevekwak, even official examples have this problem: https://github.com/AdobeXD/plugin-samples/blob/master/ui-hello/main.js
For me it solved the error for dialog.