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.
--
You received this message because you are subscribed to the Google Groups
"greasemonkey-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/greasemonkey-users/d4d410d4-49ba-463f-b5e2-9e1d994b6800%40googlegroups.com.