I’m writing a user-script that uses WebComponents, i.e. class TestElement extends HTMLElement
, this.attachShadow({ mode: "open" })
, customElements.define("test-element", TestElement)
, etc. But it seems, that due to the security system of GreaseMonkey, I can’t fully use WebComponents: instance methods, getters and setters aren’t applied to my <test-element>
elements. Yet, I have found a work-around: the only method actually running is the constructor
, within which I can define methods, getters and setters, which do work.
Here is my minimal example code, demonstrating the issue:
// ==UserScript==
// @name WebComponents test
// @include http://example.com/
// @run-at document-start
// ==/UserScript==
addEventListener("DOMContentLoaded", function(){ "use strict";
class TestElement extends HTMLElement{
constructor(){
super();
const shadow = this.attachShadow({
mode: "open"
});
this.content = shadow.appendChild(document.createElement("div"));
Object.defineProperty(this, "testGetter", {
get(){
return this.content.textContent || "something";
},
set(value){
this.content.textContent = value;
}
});
Object.defineProperty(this, "testMethod", {
value(){
console.log("Hello, World");
}
});
console.log("Constructor actually runs."); // It does.
}
get dataGetter(){
return this.content.textContent || "something";
}
set dataGetter(value){
this.content.textContent = value;
}
someMethod(){
console.log("Hello, World");
}
}
customElements.define("test-element", TestElement);
const testElement = document.createElement("test-element");
document.body.append(testElement);
console.log("instance", testElement instanceof TestElement); // false
console.log("dataGetter in", "dataGetter" in testElement); // false
console.log("dataGetter property", testElement.dataGetter); // undefined
testElement.dataGetter = "Test"; // Silently fails.
Object.assign(testElement, {
dataGetter: "Test"
}); // Silently fails.
console.log("dataGetter property", testElement.dataGetter); // "Test", but not from the Getter.
console.log("testGetter in", "testGetter" in testElement); // true
console.log("testGetter property", testElement.testGetter); // "something"
testElement.testGetter = "Test"; // Works.
Object.assign(testElement, {
testGetter: "Test"
}); // Works.
console.log("testGetter property", testElement.testGetter); // "Test"
testElement.testMethod(); // Logs "Hello, World"
testElement.someMethod(); // TypeError: testElement.someMethod is not a function
});
The comments explain what’s going on. As the code is running on the webpage
http://example.com/, the JavaScript context doesn’t seem to be allowed to access
TestElement
instance properties that were defined on the
class
itself. But since the
constructor
runs, I can define new instance properties, using
Object.defineProperty
. To be clear, all of this would work perfectly fine, if the script was a normal content script within a
<script>
, not using a user-script.
What I ended up using in my real script is this one-liner after super();
—it takes all instance properties of the class (which are accessible, if the class is accessed directly), and redefines every property on the current instance, except constructor
:
Object.defineProperties(this, Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(MyCustomElement
.prototype))
.filter(([key]) => key !== "constructor")));
This is the easiest work-around I came up with, but is it really not possible to use WebComponents
normally in GreaseMonkey user-scripts? The work-around still doesn’t account for
testElement instanceof TestElement
.