How to stop extensions/add ons like grammarly on contenteditable editors

So for Grammarly in particular, if you are patient/persistent you can submit a ticket and they will disable their plugin on your site. They won't fix the actual problem, they still inject almost 4MB of extra payload into your editor, I still see the problem when using their plugin on our Dev and QA sites.

I took the approach of Detecting / Alerting Users / Disabling the page. The message was slightly different for Firefox but to keep the sample below brief I stripped out the FF variables and the logging of the offending user.

/* vars intentionally over complicated to make detection more difficult */
//Chrome Message: gc;
var r = /\{0\}/g;
var gc = "PHN0eWxlPg0KCS5pY29uIHsNCgkJLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsNCgkJZGlzcGxheTogaW5saW5lLWJsb2NrOw0KCX0NCgkuaWNvbiB7DQoJCWJhY2tncm91bmQtcmVwZWF0OiBuby1yZXBlYXQ7DQoJCWJhY2tncm91bmQtc2l6ZTogMTAwJTsNCgkJaGVpZ2h0OiA3MnB4Ow0KCQltYXJnaW46IDAgMCA0MHB4Ow0KCQl3aWR0aDogNzJweDsNCgl9DQoJLmljb24tZ2VuZXJpYyB7DQoJCWJhY2tncm91bmQtaW1hZ2U6IHVybCgiZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFKQUFBQUNRQVFNQUFBRGRpSEQ3QUFBQUJsQk1WRVVBQUFCVFUxT29hU2YvQUFBQUFYUlNUbE1BUU9iWVpnQUFBRkpKUkVGVWVGN3QwY0VOZ0RBTVE5RndZZ3hHNldqcGFJekNDQXhReFZnZ0Z1RGlDdmxMT2VSZEhSOXl6am5jSFZvcTNucHUrd1FVclV1Skh5bFNUbUJhZXNwSnlKUW9PYlVleXhEUWIzYkVtNUF1ODFjMHBTQ0Q4SFlBQUFBQVNVVk9SSzVDWUlJPSIpOw0KCX0NCgkuaW50ZXJzdGl0aWFsLXdyYXBwZXIgew0KCQlib3gtc2l6aW5nOiBib3JkZXItYm94Ow0KCQlmb250LXNpemU6IDFlbTsNCgkJbGluZS1oZWlnaHQ6IDEuNmVtOw0KCQltYXJnaW46IDEwMHB4IGF1dG8gMDsNCgkJbWF4LXdpZHRoOiA2MDBweDsNCgkJd2lkdGg6IDEwMCU7DQoJfQ0KCS5ibHVlLWJ1dHRvbiB7DQoJCS13ZWJraXQtdXNlci1zZWxlY3Q6IG5vbmU7DQoJCWJhY2tncm91bmQ6IHJnYig2NiwgMTMzLCAyNDQpOw0KCQlib3JkZXI6IDA7DQoJCWJvcmRlci1yYWRpdXM6IDJweDsNCgkJYm94LXNpemluZzogYm9yZGVyLWJveDsNCgkJY29sb3I6ICNmZmY7DQoJCWN1cnNvcjogcG9pbnRlcjsNCgkJZmxvYXQ6IHJpZ2h0Ow0KCQlmb250LXNpemU6IC44NzVlbTsNCgkJbWFyZ2luOiAwOw0KCQlwYWRkaW5nOiAxMHB4IDI0cHg7DQoJCXRyYW5zaXRpb246IGJveC1zaGFkb3cgMjAwbXMgY3ViaWMtYmV6aWVyKDAuNCwgMCwgMC4yLCAxKTsNCgl9DQo8L3N0eWxlPg0KPGJvZHkgaWQ9InQiIGNsYXNzPSJuZXRlcnJvciIgc3R5bGU9ImZvbnQtZmFtaWx5OiAnU2Vnb2UgVUknLCBUYWhvbWEsIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZTogNzUlO2JhY2tncm91bmQtY29sb3I6ICNmN2Y3Zjc7IGNvbG9yOiAjNjQ2NDY0OyI+DQo8ZGl2IGlkPSJtYWluLWZyYW1lLWVycm9yIiBjbGFzcz0iaW50ZXJzdGl0aWFsLXdyYXBwZXIiIGpzdGNhY2hlPSIwIj4NCiAgICA8ZGl2IGlkPSJtYWluLWNvbnRlbnQiIGpzdGNhY2hlPSIwIj4NCiAgICAgIDxkaXYgY2xhc3M9Imljb24gaWNvbi1nZW5lcmljIj48L2Rpdj4NCiAgICAgIDxkaXYgaWQ9Im1haW4tbWVzc2FnZSIganN0Y2FjaGU9IjAiPg0KICAgICAgICA8aDEgY2xhc3M9ImhlYWRpbmciPlBvdGVudGlhbGx5IGRhbmdlcm91cyBwbHVnaW4gezB9IGRldGVjdGVkLjwvaDE+DQogICAgICAgIDxwPlBsZWFzZSBkaXNhYmxlIG9yIHJlbW92ZSB0aGUgezB9IHBsdWdpbiB0byBjb250aW51ZS48L3A+DQogICAgICAgIDxkaXYgaWQ9InN1Z2dlc3Rpb25zLWxpc3QiPg0KICAgICAgICAgIDxwPjwvcD4NCiAgICAgICAgICA8ZGl2IGNsYXNzPSJ6aXBweS1vdmVyZmxvdyI+PGRpdiBjbGFzcz0iemlwcHktY29udGVudCIgc3R5bGU9Im1hcmdpbi10b3A6IDBweDsgdHJhbnNpdGlvbjogbWFyZ2luLXRvcCAwLjIxOHMgZWFzZS1vdXQ7IG92ZXJmbG93OiBhdXRvOyI+DQoJCQkgIDxwPlRvIHJlbW92ZSBhbiBleHRlbnNpb24gZnJvbSBHb29nbGUgQ2hyb21lOjwvcD4NCgkJCTxvbD4NCgkJCQk8bGk+SWYgdGhlIGV4dGVuc2lvbiBoYXMgYW4gaWNvbiBpbiB5b3VyIENocm9tZSB0b29sYmFyLCB5b3UgY2FuIHJpZ2h0LWNsaWNrIG9uIHRoZSBpY29uLjwvbGk+DQoJCQkJPGxpPlNlbGVjdCA8c3Ryb25nPlJlbW92ZSBmcm9tIENocm9tZS48L3N0cm9uZz48L2xpPiAJCQkJDQoJCQkJPGxpPkEgbm90aWNlIHRvIHJlbW92ZSB0aGUgZXh0ZW5zaW9uIHdpbGwgYXBwZWFyLiBDbGljayZuYnNwOzxzdHJvbmc+UmVtb3ZlPC9zdHJvbmc+LjwvbGk+DQoJCQk8L29sPg0KCQkJICBvcjogPGJyIC8+DQoJCQkgIDxvbD4NCgkJCQk8bGk+T24geW91ciBicm93c2VyLCBjbGljayZuYnNwOzxzdHJvbmc+bWVudTwvc3Ryb25nPiZuYnNwOzxpbWcgc3JjPSJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vSE96dGhvamdJOFQyWUMxbi0xUmlxWnQ3eEQ0T2xyWGJhc0RzbXQ0RFJZR01zX3JoUXduRmhvQVdVc1Q0PXcxOCIgd2lkdGg9IjE4IiBoZWlnaHQ9IjE4IiBhbHQ9IiI+LjwvbGk+DQoJCQkJPGxpPlNlbGVjdCZuYnNwOzxzdHJvbmc+TW9yZSB0b29scyAmZ3Q7IEV4dGVuc2lvbnM8L3N0cm9uZz4uPC9saT4NCgkJCQk8bGk+T24gdGhlIGV4dGVuc2lvbiB5b3Ugd2FudCB0byByZW1vdmUsIGNsaWNrJm5ic3A7PHN0cm9uZz5SZW1vdmUgZnJvbSBDaHJvbWU8L3N0cm9uZz4mbmJzcDs8aW1nIHNyYz0iaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2I2ZkxLbGUyRUFjaEsycEJpOTBzTWlpYmJzaGU5TWdPQ2JmTVN2R21DUTh2UUs4Y1pRMW91RmpBMm9zTz13MTgiIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCIgYWx0PSIiPi48L2xpPg0KCQkJCTxsaT5BIG5vdGljZSB0byByZW1vdmUgdGhlIGV4dGVuc2lvbiB3aWxsIGFwcGVhci4gQ2xpY2smbmJzcDs8c3Ryb25nPlJlbW92ZTwvc3Ryb25nPi48L2xpPg0KCQkJICA8L29sPg0KCQkJPC9kaXY+PC9kaXY+DQoJCQk8YnV0dG9uIGlkPSJyZWxvYWQtYnV0dG9uIiBjbGFzcz0iYmx1ZS1idXR0b24iIG9uY2xpY2s9IndpbmRvdy5sb2NhdGlvbi5ocmVmPXdpbmRvdy5sb2NhdGlvbi5ocmVmOyI+UmVsb2FkPC9idXR0b24+ICAgICAgICANCiAgICAgICAgPC9kaXY+DQogICAgICA8L2Rpdj4NCiAgICA8L2Rpdj4NCiAgPC9kaXY+DQogIDwvYm9keT4=";
var s = "";

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

var observer = new MutationObserver(function (mutations, observer) {
    for (mutation in mutations) {
    /*console.log(mutations[mutation].target.outerHTML);*/
    var m = mutations[mutation].target.hasAttribute("data-gramm_id");
    if (m) {
        s = window.atob(gc).replace(r, "Grammarly");

        var newDoc = document.open("text/html", "replace");
        newDoc.write(s);
        newDoc.close();
    }
}
});
observer.observe(document, {
    subtree: true,
    attributes: true
});

In general you cannot fight extensions. They represent user intent which is prioritized by browser vendors over author intent. They also have more privileges than the website, e.g. they can bypass CSP.

It's not a fight you can win.

Options you have

  • try anyway. in that case you could inspect the addon source - since they're shipped in source form - and see if some particular sequence of events (loss of focus? disabling contenteditable before saving?) make them remove the injected markup
  • contact the addon authors and ask them to be less invasive
  • warn the user if you detect such behavior

Checked Medium editor

They don't use contenteditable.

https://medium.engineering/why-contenteditable-is-terrible-122d8a40e480


To disable Grammarly, you can add attributes on the <textarea> element. The attributes keep changing, but you can add all of them to be safe.

  • data-gramm="false"
  • data-gramm_editor="false"
  • data-enable-grammarly="false"

I did the following in CSS, which is working fine. Tested on Firefox:

[contenteditable] ~ grammarly-extension,
input ~ grammarly-extension,
textarea ~ grammarly-extension {
  display: none;
}

Should be far better than adding custom attributes which the Grammarly team changing frequently without any notice.