I've been working with Javascript recently but I'm not actually very proficient with it. I'm trying to track down what looks to be a memory leak, since my Memory Usage seems to steadily increase. One idea I have for what might be causing this is the use of inner functions. I use inner functions when dealing with XMLHTTPRequests (and other related objects) and these requests get created every few seconds. I feel like perhaps I'm not closing it properly or something. So here's some code for one of the inner functions, if someone could tell me if this is the problem and how to fix it that would be great. (This is in the context of IE, I do not know and cannot find out if it happens in other browsers).
me.request.open("GET", url, true);
me.request.onreadystatechange = onReadyStateChange;
me.request.send();
function onReadyStateChange() {
if (4 == me.request.readyState) {
if (me.request.status == 200) {
var results = me.request.responseText;
var resultsString = me.request.responseText.toString();
me.stringOperation(resultsString);
me.request.abort();
me.request = null;
} else {
me.request.abort();
me.request = null;
}
}
}
Add
function noop() {}
And then in onReadyStateChange, after me.request = null, me.request.onreadystatechange = noop.
This removes a cyclic reference between me, and onReadyStateChange that crosses the JS object / host object barrier.
Related
I have the following javascript function:
function actionFunction() {
let lookup_table = {
'imageurl1': "imageurl2",
};
for (let image of document.getElementsByTagName("img")) {
for (let query in lookup_table) {
if (image.src == query) {
image.src = lookup_table[query];
}
}
}
}
I want it to work even after a page is fully loaded (in other words, work with dynamically generated html elements that appeared post-load by the page's js).
It could either be by running the function every x seconds or when a certain element xpath is detected within the page, or every time a certain image url is loaded within the browser (which is my main goal here).
What can I do to achieve this using javascript + greasemonkey?
Thank you.
Have you tried running your code in the browser's terminal to see if it works without greasemonkey involved?
As to your question - you could either use setInterval to run given code every x amount of time or you could use the MutationObserver to monitor changes to the webpage's dom. In my opinion setInterval is good enough for the job, you can try learning how the MutationObserver works in the future.
So rewriting your code:
// arrow function - doesn't lose this value and execution context
// setInterval executes in a different context than the enclosing scope which makes functions lose this reference
// which results in being unable to access the document object
// and also window as they all descend from global scope which is lost
// you also wouldn't be able to use console object
// fortunately we have arrow functions
// because arrow functions establish this based on the scope the arrow function is defined within
// which in most cases is global scope
// so we have access to all objects and their methods
const doImageLookup = () => {
const lookup_table = {
'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png': 'https://www.google.com/logos/doodles/2022/gama-pehlwans-144th-birthday-6753651837109412-2x.png',
};
const imgElementsCollection = document.getElementsByTagName('img');
[...imgElementsCollection].forEach((imgElement) => {
Object.entries(lookup_table).forEach(([key, value]) => {
const query = key; // key and value describe object's properties
const replacement = value; // here object is used in an unusual way, i would advise to use array of {query, replacement} objects instead
if (imgElement.src === query) {
imgElement.src = replacement;
}
});
});
};
const FIVE_MINUTES = 300000; // ms
setInterval(doImageLookup, FIVE_MINUTES);
You could make more complex version by tracking the img count and only doing the imageLookop if their number increases. This would be a big optimization and would allow you to run the query more frequently (though 5 minutes is pretty long interval, adjust as required).
How can I find and stop all of setInterval (s) in Angular?
Some setInterval do not created by my program and run in another components or even a jQuery in my web application and I want to stop it!
CORS is not concerned.
If you absolutely need "a" solution, even a bad one, then you can overwrite the setInterval and setTimeout functions, and maintain your own list of assigned interval identifiers. However, a much better solution would be to write your code to deal with the fact that other parts of the libraries and frameworks you use can schedule timeouts and ntervals, instead of forcefully killing them with complete disregard of what was relying on them to run in the first place.
Indiscriminantly killing all timers will break things.
So, with that covered, the terrible solution would be code like this:
(function monitorIntervalsAndTimeouts() {
const timerList = [];
const oldSetInterval = globalThis.setInterval.bind(globalThis);
globalThis.setInterval = function(...args) {
const timerId = oldSetInterval(...args);
timerList.push(timerId);
return timerId;
};
const oldSetTimeout = globalThis.setTimeout.bind(globalThis);
globalThis.setTimeout = function(...args) {
const timerId = oldSetTimeout (...args);
timerList.push(timerId);
return timerId;
};
...
And of course, the corresponding clear functions:
...
const oldClearInterval = globalThis.clearInterval.bind(globalThis);
globalThis.clearInterval = function(timerId) {
const pos = timerList.indexOf(timerId);
if (pos > -1) timerList.splice(pos, 1);
return oldClearInterval(timerId);
};
const oldClearTimeout = globalThis.clearTimeout.bind(globalThis);
globalThis.clearTimeout = function(timerId) {
const pos = timerList.indexOf(timerId);
if (pos > -1) timerList.splice(pos, 1);
return oldClearTimeout(timerId);
};
...
and finally, a "clear all timers" function:
...
// Call this whatever you want.
globalThis.clearAllIntervalsAndTimeouts = function() {
while(timerList.length) {
// clearInterval and clearTimeout are, per the HTML standard,
// the exact same function, just with two names to keep code clean.
oldClearInterval(timerList.shift());
}
};
})();
You then make sure you load this using <script src="./monitor-intervals-and-timeouts.js"></script>, without the async and/or defer keyword, as very first script in your page's <head> element, so that it loads before anything else. And yes: that will block the page parsing while it does that. Or if you're using Node (which this will work fine for, too, because of the use of globalThis), you need to make sure it's the absolute first thing your main entry point imports/requires (depending on whether you're using ESM or CJS code).
Remember: this is the nuclear option. If you use this code, you're using it because you need to run something for local dev work that lets you debug your project, for finding out where timeouts/intervals are getting set and cleared. If you need it for that: go for it. But never, ever use this in production to solve a problem relating to timeouts/intervals used by third party/vendor code. Solve that the right way by actually solving whatever problem your own code has.
For several years I've used the waitForKeyElements() function to track changes in webpages from a userscript. However, sometimes I've found it doesn't trigger as expected and have worked around out. I've run into another example of this problem, and so am now trying to figure out what the problem is. The following is the barest example I can create.
Given a simple HTML page that looks like this:
<span class="e1">blah</span>
And some Javascript:
// function defined here https://gist.github.com/BrockA/2625891
waitForKeyElements('.e1', handle_e1, false);
function handle_e1(node) {
console.log(node.text());
alert(node.text());
}
setInterval(function() {
$('.e1').text("updated: "+Math.random());
}, 5000);
I would expect this code to trigger an alert() and a console.log() every 5 seconds. However, it only triggers once. Any ideas?
Here's a codepen that demonstrates this.
By design and default, waitForKeyElements processes a node just once. To tell it to keep checking, return true from the callback function.
You'll also want to compare the string (or whatever) to see if it has changed.
So, in this case, handle_e1() would be something like:
function handle_e1 (jNode) {
var newTxt = jNode.text ();
if (typeof this.lastTxt === "undefined" || this.lastTxt !== newTxt) {
console.log (newTxt);
this.lastTxt = newTxt;
}
return true; // Allow repeat firings for this node.
}
With the constant string comparisons though, performance might be an issue if you have a lot of this on one page. In that scenario, switching to a MutationObserver approach might be best.
I had a page which executes heavy javascript code after loading. To prevent the page from freezing upon loading, I spaced the execution into batches with some "no-execution" time in between (using Timeouts), and things worked well.
Lately, I've had to add additional heavy javascript code which can execute upon client actions, yet these actions can occur even before the original heavy script is done executing. This time, spacing the action won't help, since at the "downtime" of one script the other can run and vice versa, which will cause the browser to freeze.
The problem is actually more complicated as there are multiple such actions, each executing a different heavy script, and each script sort of has a different "priority" as to how fast i'd like it to finish, compared to the other ones.
My question is, what is the common practice in such situations? I tried thinking of a way to solve it, but all I could think of was quite a complex solution which would pretty much be like writing an operating system in javascript - i.e., writing a "manager" code which executes every X time (using an "interrupt"), and chooses which "context to switch to" ( = which job should run right now), etc...
This however sounds pretty complicated to me, and I was hoping there might be other solutions out there. My problem sounds like one which I'd assume many people have stumbled upon before, so even if the only solution is what I suggested, I'd assume someone already wrote it, or there is some library support for this.
Any help would be greatly appreciated. Thank you.
== EDIT ==
by "heavy code", I mean for example the DOM manipulation of a great number of elements.
You will need to think of defining your UI/Problem domain as a set of Asynchronous tasks. Here's some more insight http://alexmaccaw.com/posts/async_ui until I formulate a better answer for you.
If you don't want to block your script you can use web workers. See MDN: Using web workers for a good introduction. Note that web workers are still relative new and not supported by most browser.
However, if you want to support all browser and add some kind of priority for your "heavy scripts", you should define something yourself, e.g:
function WorkerQueue(this_argument){
this.queue = [];
this.this_argument = this_argument;
this.priority = 1;
}
WorkerQueue.prototype.enqueue = function(callback){
this.queue.push(callback);
}
WorkerQueue.prototype.dequeue = function(){
return this.queue.splice(0,1)[0];
}
function WorkerPool(){
this.pool = [];
this.status = "running";
this.timeout = null;
}
WorkerPool.prototype.addWorker = function(this_argument){
this.pool.push(new WorkerQueue(this_argument));
return this.pool[this.pool.length - 1];
}
WorkerPool.prototype.nextTask = function(){
var max_priority = 0;
var max_priority_task = this.pool.length;
for(var i = 0; i < this.pool.length; ++i){
if(this.pool[i].priority > max_priority && this.pool[i].queue.length !== 0){
max_priority = this.pool[i].priority;
max_priority_task = i;
}
}
// pool is empty or all tasks have an invalid priority
if(max_priority_task === this.pool.length)
return;
if(this.pool[max_priority_task].this_argument)
this.pool[max_priority_task].dequeue().apply(this.pool[max_priority_task].this_argument);
else
this.pool[max_priority_task].dequeue().apply();
if(this.status !== "running")
return;
this.timeout = setTimeout(function(t){return function(){t.nextTask();};}(this),1000);
}
var Workers = new WorkerPool();
var worker1 = Workers.addWorker();
worker1.enqueue(function(){
console.log("Hello");
});
worker1.enqueue(function(){
console.log("World");
});
var worker2 = Workers.addWorker();
worker2.priority = 2;
worker2.this_argument = worker2;
worker2.enqueue(function(){
console.log("Worker 2 - changing priority");
this.priority = .2;
});
worker2.enqueue(function(){
console.log("Worker 2 - after change");
});
Workers.nextTask();
Demo
In this case, every "heavy script" is a worker, which is basically a queue of tasks. You create a new worker in the pool by using addWorker and add tasks to the specific workers queue by using worker.enqueue(callback).
I was just wondering whether there are any way (libraries, frameworks, tutorials) to do javascript tracking with another script? Basically, i want to track as the user work with the site, which function gets executed with what parameters and so on, as detailed as possible.
thanks a lot!
The extent of detail you're expecting will be challenging for any solution to gather and report on without severely slowing down your scripts -- consider that, for every call, at least 1 other call would need to occur to gather this.
You'd be better to pick a few key events (mouse clicks, etc.) and track only a few details (such as time) for them. If you're using ajax, keep JavaScript and the browser oblivious and just track this on server-side.
There's a few options but I'm not sure if there are any "great" ones. I take it Firebug/IE Dev toolbar profiling won't work because you are trying to track remote user's actions.
So, one option (I'm not highly recommending for production purposes), will work in some but not all browsers.
Essentially you overwrite every function, with a wrapper that you then inject your logging.
(I haven't tested this, trying to recall it from memory... hopefully in "pseudo code" you get the idea...)
//e.g. get all functions defined on the global window object
function logAll(){
var funcs = [];
var oldFunc;
for(var i in window){
try {
if(typeof(window[i]) == 'function'){
if(i != 'logAll'){
funcs.push(i);
}
}
} catch(ex){
//handle as desired
}
}
var x;
for(var i in funcs){
x = '_' + new Date().getTime();
window[x] = window[i];//save the old function as new function
//redefine original
window[i] = function(){
//do your logging here...
//then call the real function (and pass all params along)
call(window[x]);
};
}
};