Goal: I'm trying to add an input element on Frontend Mentor to sort/search solutions.
Problem: The input element appears on the page but disappears after a second.
Code:
// ==UserScript==
// #name FEM
// #namespace http://tampermonkey.net/
// #version 0.1
// #description try to take over the world!
// #author bunee
// #match https://www.frontendmentor.io/solutions
// #icon https://www.google.com/s2/favicons?domain=frontendmentor.io
// #grant none
// ==/UserScript==
(function() {
'use strict';
window.addEventListener("load", () => {
const inputElement = document.createElement("INPUT");
inputElement.setAttribute("type", "text");
const navBar = document.querySelector(".NavigationSecondary__Wrapper-sc-13y3yjk-0>.wide-container");
navBar.append(inputElement);
console.log(inputElement);
}, false);
})();
Here's where I'm trying to add it.
If you can, Please run this script and let me know how I'm wrong.
Seems like some dynamic shenanigangs on the website delete your addition when it happens too early.
Have you tried just delaying it with a timeout?
Something like
// ==UserScript==
// #name FEM
// #namespace http://tampermonkey.net/
// #version 0.1
// #description try to take over the world!
// #author bunee
// #match https://www.frontendmentor.io/solutions
// #icon https://www.google.com/s2/favicons?domain=frontendmentor.io
// #grant none
// ==/UserScript==
setTimeout(function(){
const inputElement = document.createElement("INPUT");
inputElement.setAttribute("type", "text");
const navBar = document.querySelector(".NavigationSecondary__Wrapper-sc-13y3yjk-0>.wide-container");
//navBar.append(inputElement);
navBar.insertBefore(inputElement, navBar.lastChild);
console.log(inputElement);
}, 2000);
just adapt it as needed.
P.S.
navBar.append(inputElement) adds your new element to the end while
navBar.insertBefore(inputElement, navBar.lastChild); adds it in the middle as per your question.
Original question: Multiple arguments in Gio.Subprocess
So currently I'm trying to execute multiple asynchronous commands in my gnome-shell-extension via Gio.Subprocess. This works fine, if I put all commands as only one chained command with && in the command vector of the Subprocess. The drawback of this solution is, that the output of the different chained commands is only updated once and the execution time may be long.
What I'm now trying to do, is to execute every command on its own at the same time. Now the output can be updated if one command only has a small interval while another one needs more time.
Let's say these are my commands, in this case I would like to execute each command every second:
let commands = {"commands":[{"command":"ls","interval":1},
let commands = {"commands":[{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1}]}
Then I'm calling my refresh function for each command.
commands.commands.forEach(command => {
this.refresh(command);
})
What is happening now, is that the gnome UI is freezing almost every second, not much, but I can see my mouse cursor or scrolling stop for a very short amount of time, even though I use asynchronous communication.
What I have found out from debugging is that it seems to be the initialization of the Subprocess which causes the small freeze, maybe because all the commands are using it nearly at the same time?
proc.init(cancellable);
I think the documentation says that the init method is synchronous (https://developer.gnome.org/gio//2.56/GInitable.html#g-initable-init) and that there also seems to be an async version (https://developer.gnome.org/gio//2.56/GAsyncInitable.html#g-async-initable-init-async), but the Gio.Subprocess does only implement the synchronous one (https://developer.gnome.org/gio//2.56/GSubprocess.html)
So the final question is, what would be the correct way to avoid the freezing? I tried to move the init part to asynchronous function and continue with the command execution via callbacks after it is done, but with no luck. Maybe this is even the completely wrong approach though.
Whole extension.js (final updating of the output is not part of this version, just for simplicity):
const Main = imports.ui.main;
const GLib = imports.gi.GLib;
const Mainloop = imports.mainloop;
const Gio = imports.gi.Gio;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
let output, box, gschema, stopped;
var settings;
let commands = {"commands":[{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1}]}
function init() {
//nothing todo here
}
function enable() {
stopped = false;
gschema = Gio.SettingsSchemaSource.new_from_directory(
Me.dir.get_child('schemas').get_path(),
Gio.SettingsSchemaSource.get_default(),
false
);
settings = new Gio.Settings({
settings_schema: gschema.lookup('org.gnome.shell.extensions.executor', true)
});
box = new St.BoxLayout({ style_class: 'panel-button' });
output = new St.Label();
box.add(output, {y_fill: false, y_align: St.Align.MIDDLE});
Main.panel._rightBox.insert_child_at_index(box, 0);
commands.commands.forEach(command => {
this.refresh(command);
})
}
function disable() {
stopped = true;
log("Executor stopped");
Main.panel._rightBox.remove_child(box);
}
async function refresh(command) {
await this.updateGui(command);
Mainloop.timeout_add_seconds(command.interval, () => {
if (!stopped) {
this.refresh(command);
}
});
}
async function updateGui(command) {
await execCommand(['/bin/sh', '-c', command.command]).then(stdout => {
if (stdout) {
let entries = [];
stdout.split('\n').map(line => entries.push(line));
let outputAsOneLine = '';
entries.forEach(output => {
outputAsOneLine = outputAsOneLine + output + ' ';
});
if (!stopped) {
log(outputAsOneLine);
//output.set_text(outputAsOneLine);
}
}
});
}
async function execCommand(argv, input = null, cancellable = null) {
try {
let flags = Gio.SubprocessFlags.STDOUT_PIPE;
if (input !== null)
flags |= Gio.SubprocessFlags.STDIN_PIPE;
let proc = new Gio.Subprocess({
argv: argv,
flags: flags
});
proc.init(cancellable);
let stdout = await new Promise((resolve, reject) => {
proc.communicate_utf8_async(input, cancellable, (proc, res) => {
try {
let [ok, stdout, stderr] = proc.communicate_utf8_finish(res);
resolve(stdout);
} catch (e) {
reject(e);
}
});
});
return stdout;
} catch (e) {
logError(e);
}
}```
It's doubtful that Gio.Initable.init() is what's causing the freeze. First some comments on the usage of GSubprocess here.
function execCommand(argv, input = null, cancellable = null) {
try {
/* If you expect to get output from stderr, you need to open
* that pipe as well, otherwise you will just get `null`. */
let flags = (Gio.SubprocessFlags.STDOUT_PIPE |
Gio.SubprocessFlags.STDERR_PIPE);
if (input !== null)
flags |= Gio.SubprocessFlags.STDIN_PIPE;
/* Using `new` with an initable class like this is only really
* necessary if it's possible you might pass a pre-triggered
* cancellable, so you can call `init()` manually.
*
* Otherwise you can just use `Gio.Subprocess.new()` which will
* do exactly the same thing for you, just in a single call
* without a cancellable argument. */
let proc = new Gio.Subprocess({
argv: argv,
flags: flags
});
proc.init(cancellable);
/* If you want to actually quit the process when the cancellable
* is triggered, you need to connect to the `cancel` signal */
if (cancellable instanceof Gio.Cancellable)
cancellable.connect(() => proc.force_exit());
/* Remember the process start running as soon as we called
* `init()`, so this is just the threaded call to read the
* processes's output.
*/
return new Promise((resolve, reject) => {
proc.communicate_utf8_async(input, cancellable, (proc, res) => {
try {
let [, stdout, stderr] = proc.communicate_utf8_finish(res);
/* If you do opt for stderr output, you might as
* well use it for more informative errors */
if (!proc.get_successful()) {
let status = proc.get_exit_status();
throw new Gio.IOErrorEnum({
code: Gio.io_error_from_errno(status),
message: stderr ? stderr.trim() : GLib.strerror(status)
});
}
resolve(stdout);
} catch (e) {
reject(e);
}
});
});
/* This should only happen if you passed a pre-triggered cancellable
* or the process legitimately failed to start (eg. commmand not found) */
} catch (e) {
return Promise.reject(e);
}
}
And notes on Promise/async usage:
/* Don't do this. You're effectively mixing two usage patterns
* of Promises, and still not catching errors. Expect this to
* blow up in your face long after you expect it to. */
async function foo() {
await execCommand(['ls']).then(stdout => log(stdout));
}
/* If you're using `await` in an `async` function that is
* intended to run by itself, you need to catch errors like
* regular synchronous code */
async function bar() {
try {
// The function will "await" the first Promise to
// resolve successfully before executing the second
await execCommand(['ls']);
await execCommand(['ls']);
} catch (e) {
logError(e);
}
}
/* If you're using Promises in the traditional manner, you
* must catch them that way as well */
function baz() {
// The function will NOT wait for the first to complete
// before starting the second. Since these are (basically)
// running in threads, they are truly running in parallel.
execCommand(['ls']).then(stdout => {
log(stdout);
}).catch(error => {
logError(error);
});
execCommand(['ls']).then(stdout => {
log(stdout);
}).catch(error => {
logError(error);
});
}
Now for the implementation:
const Main = imports.ui.main;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
let cancellable = null;
let panelBox = null;
let commands = {
"commands":[
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1},
{"command":"ls","interval":1}
]
};
enable() {
if (cancellable === null)
cancellable = new Gio.Cancellable();
panelBox = new St.BoxLayout({
style_class: 'panel-button'
});
// Avoid deprecated methods like `add()`, and try not
// to use global variable when possible
let outputLabel = new St.Label({
y_align: St.Align.MIDDLE,
y_fill: false
});
panelBox.add_child(outputLabel);
Main.panel._rightBox.insert_child_at_index(panelBox, 0);
commands.commands.forEach(command => {
this.refresh(command);
});
}
disable() {
if (cancellable !== null) {
cancellable.cancel();
cancellable = null;
}
log("Executor stopped");
if (panelBox !== null) {
Main.panel._rightBox.remove_child(panelBox);
panelBox = null;
}
}
async function refresh(command) {
try {
await this.updateGui(command);
// Don't use MainLoop anymore, just use GLib directly
GLib.timeout_add_seconds(0, command.interval, () => {
if (cancellable && !cancellable.is_cancelled())
this.refresh(command);
// Always explicitly return false (or this constant)
// unless you're storing the returned ID to remove the
// source later.
//
// Returning true (GLib.SOURCE_CONTINUE) or a value that
// evaluates to true will cause the source to loop. You
// could refactor your code to take advantage of that
// instead of constantly creating new timeouts each
// second.
return GLib.SOURCE_REMOVE;
});
} catch (e) {
// We can skip logging cancelled errors, since we probably
// did that on purpose if it happens
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)
logError(e, 'Failed to refresh');
}
}
// `updateGui()` is wrapped in a try...catch above so it's safe to
// skip that here.
async function updateGui(command) {
let stdout = await execCommand(['/bin/sh', '-c', command.command]);
// This will probably always be true if the above doesn't throw,
// but you can check if you want to.
if (stdout) {
let outputAsOneLine = stdout.replace('\n', '');
// No need to check the cancellable here, if it's
// triggered the command will fail and throw an error
log(outputAsOneLine);
// let outputLabel = panelBox.get_first_child();
// outputLabel.set_text(outputAsOneLine);
}
}
It's hard to say what is causing the freeze you are experiencing, but I would first cleanup your Promise usage and be more explicit about how you use timeout sources, as these may be stacking every second.
If possible, you might want to group your subprocesses into a single timeout source, possible using Promise.all() to await them all at once. Overloading the event loop with pending sources and Promises could also be the cause of the freeze.
This script works just fine in Tampermonkey on both Chrome and Firefox, but doesn't work in Greasemonkey on Firefox. When I say it doesn't work I mean the button does not show up on the page. I'm using Chrome 61.0.3163.79 and Firefox 52.3.0.
The firefox console shows this warning but I'm not sure if it's relevant:
unreachable code after return statement[Learn More] jquery-1.3.2.min.js
Here's the code. Using greasemonkey on Firefox, I see "create button" in the console, but not "button has been created", which makes me think something is going wrong with the button creation. Any help greatly appreciated!
// ==UserScript==
// #name MyScript
// #namespace MyNS
// #description Adds a button to insert text into 2 text boxes
// #version 0.1
// #include *Removed for confidentiality*
// #compatible Greasemonkey
// ==/UserScript==
var descriptionText = "myDescription";
var testingText = "myTesting";
// Check if jQuery's loaded
function GM_wait() {
if (typeof unsafeWindow.jQuery == 'undefined') { window.setTimeout(GM_wait,100); }
else { $ = unsafeWindow.jQuery; init(); }
}
// All your GM code must be inside this function
function init() {
var description = $('#description');
var testingDone = $('#testing_done');
function insertText(template, editor) {
if (template) {
if (!editor._editing) {
editor.startEdit();
}
editor._field.queue(function() {
var oldVal = editor._field.val();
editor._field.val(template + (oldVal ? "\n" : "") + oldVal);
editor._field.keyup();
editor._field.dequeue();
});
}
}
function createButton(editor) {
console.log("create button:");
editor._insertTextButton = $('<input/>')
.attr('type', 'button')
.attr('value', 'Insert CR Template')
.css('margin-left', '1em')
.click(function() {
insertText(testingText, testingDoneEditor);
insertText(descriptionText, descriptionEditor);
});
console.log("button has been created");
editor._insertTextButton.insertAfter(editor._editIcon);
}
descriptionEditor = description.data('inlineEditor');
testingDoneEditor = testingDone.data('inlineEditor');
createButton(descriptionEditor);
}
GM_wait();
This question already has answers here:
Changing Javascript on an HTML page out of my control [duplicate]
(2 answers)
Closed 4 years ago.
There's a website I use written in some mighty fine javascript. Hardly any globals, closures everywhere and it uses strict mode. This is making it really hard to inject my own functionality into the website.
The website client objects are initialised in a jQuery.ready() call:
$(window).ready(function () {
var a, b, c, d;
// Setup global data [...]
// Setup configuration [...]
a = GlobalFoo.ConstructorA();
b = GlobalFoo.ConstructorB(a);
// Really wish I could put stuff here
c = GlobalFoo.ConstructorC(a, b);
d = GlobalFoo.ConstructorD(b, c);
// etc.
});
How can I, for example, replace b.someMethod() with my own code before the other constructors are called?
Can I stop the ready event from happening or replace it with my own code? Since it's quite small I can just duplicate a modified version in my code.
After a bit more searching I found this wonderful page by dindog. There is also this page on the GreaseSpot wiki describing #run-at.
#run-at allows your userscript to run before all other code. The beforescriptexecute event allows you to check each script before it executes. You can then skip or modify it.
My final solution is:
// ==UserScript==
// #name ...
// #description ...
// #namespace ...
// #include ...
// #version 1
// #run-at document-start
// #grant none
// ==/UserScript==
(function (w) {
// Remove the current jQuery ready code
function pre_script_execute_handler (e) {
var target = e.target;
if (target.innerHTML.indexOf("$(window).ready(") != -1) {
console.log('Removed ready');
e.preventDefault();
e.stopPropagation();
addReady();
}
}
w.addEventListener('beforescriptexecute', pre_script_execute_handler);
// Add new jQuery ready code
function addReady () {
console.log('Adding new ready');
w.$(window).ready(function () {
console.log('Our ready called');
});
}
}) (unsafeWindow);
I'm trying to modify a page that uses MooTools to add event-listeners to some fields, like so:
$('inputPassword').addEvents({
keypress: function(){
new WorkspaceModalbox({'href':'someurl.phtml', 'width':500, 'height':140, 'title':'foo'});
}
});
I need to remove this behavior using Greasemonkey/Tampermonkey. I tried:
// ==UserScript==
// #require http://userscripts.org/scripts/source/44063.user.js
// ==/UserScript==
window.addEventListener("load", function(e) {
$('inputPassword').removeEvents('keypress');
}, false);
where removeEvents is a function from MooTools, the opposite one to addEvents.
But the script doesn't work. (Editor's note: There are no reported errors)
Why?
Is it because my code is executed before the code from the real page?
The event was installed in page scope but the script is running in script scope. Also, depending on the browser and on the #grant settings, there may be a sandbox involved.
So, to remove that event, you must use script injection (Moo tools doesn't seem to play nice with unsafeWindow.)
This script works on both Greasemonkey and Tampermonkey:
// ==UserScript==
// #name _Remove a Moo event listener
// #include http://YOUR_SERVER.COM/YOUR_PATH/*
// #grant GM_addStyle
// ==/UserScript==
/*- The #grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
window.addEventListener ("load", function (e) {
addJS_Node ("$('inputPassword').removeEvents('keypress');");
}, false);
//-- addJS_Node is a standard(ish) function
function addJS_Node (text, s_URL, funcToRun, runOnLoad) {
var D = document;
var scriptNode = D.createElement ('script');
if (runOnLoad) {
scriptNode.addEventListener ("load", runOnLoad, false);
}
scriptNode.type = "text/javascript";
if (text) scriptNode.textContent = text;
if (s_URL) scriptNode.src = s_URL;
if (funcToRun) scriptNode.textContent = '(' + funcToRun.toString() + ')()';
var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
targ.appendChild (scriptNode);
}
Note that there is no need to #require-in Moo tool just for this, since the page's instance of Moo tools must be the one to remove the event(s).