Interval set through content script being cleared by webpage - javascript

In my chrome extension, I have a setInterval in the content script which checks for changes in the webpage after every 3 seconds.
setInterval(detectChange, 3000)
function detectChange(){
...
}
This works perfectly well for all websites except one (www.rdio.com). The webpage scripts somehow clears the interval set through the content script.
I thought of putting the setInterval in background script and sending a message to the content script at each interval. But that would require me to track all the tabs in which the content script is running, which does not seem like a good idea.
Please let me know if there is a way around.

Cancelable task schedulers (setTimeout, setInterval, requestAnimationFrame, etc.) are apparently tied to a document. Although the script execution context of a content script is isolated from the page, the document is not.
It seems rather weird that a site clears timers that are not created by the site itself. You could try to debug the issue, and check why the site is clearing the timer at all by overriding the clearTimeout / clearInterval methods.
Here is an example to catch code that clears timers that are not installed by the script itself:
// Run this content script at document_start
var s = document.createElement('script');
s.textContent = '(' + function() {
var clearTimeout = window.clearTimeout;
var setTimeout = window.setTimeout;
var setInterval = window.setInterval;
// NOTE: This list of handles is NEVER cleared, because it is the
// only way to keep track of the complete history of timers.
var handles = [];
window.setTimeout = function() {
var handle = setTimeout.apply(this, arguments);
if (handle) handles.push(handle);
return handle;
};
window.setInterval = function() {
var handle = setInterval.apply(this, arguments);
if (handle) handles.push(handle);
return handle;
};
window.clearTimeout = window.clearInterval = function(handle) {
clearTimeout(handle);
if (handle && handles.indexOf(handle) === -1) {
// Print a stack trace for debugging
console.trace('Cleared non-owned timer!');
// Or trigger a breakpoint so you can follow the call
// stack to identify which caller is responsible for
// clearing unknown timers.
debugger;
}
};
} + ')();';
(document.head || document.documentElement).appendChild(s);
s.remove();
If this shows that the site is buggy, and (for example) clears every even-numbered timer, then you simply call setTimeout twice to resolve the problem.
For example:
Promise.race([
new Promise(function(resolve) {
setTimeout(resolve, 3000);
}),
new Promise(function(resolve) {
setTimeout(resolve, 3000);
});
}).then(function() {
// Any of the timers have fired
});
If all else fails...
If it turns out that the site clears the timers in an unpredictable way, you could try to use other asynchronous methods or events to schedule tasks, and measure the time between invocations. When a certain time has elapsed, simply trigger your callback. For example, using requestAnimationFrame (which is usually called several times per second):
function scheduleTask(callback, timeout) {
timeout = +timeout || 0;
var start = performance.now();
function onDone(timestamp) {
if (timestamp - start >= timeout) callback();
else requestAnimationFrame(onDone);
}
requestAnimationFrame(onDone);
}
// Usage example:
console.time('testScheduler');
scheduleTask(function() {
console.timeEnd('testScheduler');
}, 1000);
Or insert an <iframe> and create timers in the context of the frame.

Related

How to change instanly a DOM element?

I know that this might be a stupid question but it drives me crazy. I'm trying to change the innerHTML of a DOM element but it doesn't change until the end of the function's execution. For example:
function test(){
let testEl = document.getElementById('testEl')
for (let i = 0; i < 5; i++)
{
testEl.innerHTML = 'Count: ' + i;
alert(i);
}
}
Even if I have put an alert in the loop, the text of the element will not change until the end of the function's execution. How can the change be applied instantly (for example I mean during the loop)?
You can update the number every period of time using setInterval:
function test(){
let testEl = document.getElementById('testEl');
let i = 0;
const interval = setInterval(function(){
testEl.innerHTML = `Count: ${i++}`;
if(i === 5)
clearInterval(interval);
}, 1000);
}
test();
<p id="testEl"></p>
JavaScript runs in a single-threaded environment. This means that only one execution context can ever be running at any single point in time. Asynchronous code executes outside of the JavaScript runtime environment (in this case by the browser's native processing) and only when the JavaScript thread is idle can the results of an asynchronous request be executed (i.e. callbacks).
Below is an example that updates a DOM element approximately every second, creating a clock. However, if you click the button, it will ask the browser to render an alert, which is handled outside of the JavaScript runtime and is a blocking UI element, so the clock will stop. Once you clear the alert, you will see the time jump to be roughly current.
As you'll see, the asynchronous API call to window.setInterval() allows for the function to run repeatedly, every so often, and therefore not continuously. This replaces the need for a loop that runs in its entirety every time its accessed. Because of this, you can see updates to the webpage instead of the last value of your loop.
See the comments for more details:
const clock = document.querySelector("span");
// setInterval is not JavaScript. It's a call to a browser
// API asking the JS runtime to run the supplied function every
// 900 milliseconds, but that's just a request. After 900
// milliseconds, the browser will place the function on the
// JavaScript event queue and only when the JavaScript thread
// is idle will anything on the queue be executed. This is why
// the 900 milliseconds is not a guarantee - - it's just the
// minimum amount of time you'll have to wait for the function
// to run, but it could be longer if what's already running
// on the JavaScript thread takes longer than 900 milliseconds
// to complete.
window.setInterval(function(){
// Update the DOM
clock.textContent = new Date().toLocaleTimeString();
}, 900);
document.querySelector("button").addEventListener("click", function(){
// An alert is also not JavaScript, but another browser API that is executed
// by the browser, not JavaScript. However, it is a blocking (modal) UI element.
// The rest of the browser interface (including the web page) cannot update
// while the alert is present. As soon as the alert is cleared, the UI will update.
window.alert("I'm a UI blocking construct rendered by the browser, not JavaScript");
});
<div>Current time is: <span></span></div>
<button>Click for alert</button>
Another way to achieve it is by using async - Promise like this
async function test() {
let testEl = document.getElementById('testEl')
for (let i = 0; i < 5; ++i) {
testEl.innerHTML = 'Count: ' + i;
await new Promise((resolve, _) => {
setTimeout(function() {
resolve("");
}, 1000 /* your preferred delay here */);
});
}
}

Does Javascript have anything similar to VBA's DoEvents?

I have a long running for-loop in my code and I'd like to delay to loop to handle other tasks in the event queue (like a button press). Does javascript or JQuery have anything that could help me? Basically I'm trying to do something similar to delaying loops like here (https://support.microsoft.com/en-us/kb/118468).
If your application really requires long-running JavaScript code, one of the best ways to deal with it is by using JavaScript web workers. JavaScript code normally runs on the foreground thread, but by creating a web worker you can effectively keep a long-running process on a background thread, and your UI thread will be free to respond to user input.
As an example, you create a new worker like this:
var myWorker = new Worker("worker.js");
You can then post messages to it from the js in the main page like this:
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
And respond to the messages in worker.js like this:
onmessage = function(e) {
console.log('Message received from main script');
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
console.log('Posting message back to main script');
postMessage(workerResult);
}
With the introduction of generators in ES6, you can write a helper method that uses yield to emulate DoEvents without much syntactic overhead:
doEventsOnYield(function*() {
... synchronous stuff ...
yield; // Pump the event loop. DoEvents() equivalent.
... synchronous stuff ...
});
Here's the helper method, which also exposes the completion/failure of the function as a Promise:
function doEventsOnYield(generator) {
return new Promise((resolve, reject) => {
let g = generator();
let advance = () => {
try {
let r = g.next();
if (r.done) resolve();
} catch (ex) {
reject(ex);
}
setTimeout(advance, 0);
};
advance();
});
}
Note that, at this time, you probably need to run this through an ES6-to-ES5 transpiler for it to run on common browsers.
You can use the setTimeout:
setTimeout(function() { }, 3600);
3600 it's the time in milliseconds:
http://www.w3schools.com/jsref/met_win_settimeout.asp
There is no exact equivalent to DoEvents. Something close is using setTimeout for each iteration:
(function next(i) {
// exit condition
if (i >= 10) {
return;
}
// body of the for loop goes here
// queue up the next iteration
setTimeout(function () {
// increment
next(i + 1);
}, 0);
})(0); // initial value of i
However, that’s rarely a good solution, and is almost never necessary in web applications. There might be an event you could use that you’re missing. What’s your real problem?
Here's a tested example of how to use Yield as a direct replacement for DoEvents.
(I've used Web Worker and it's great, but it's far removed from DoEvents and near-impossible to access global variables). This has been formatted for ease of understanding and attempts to show how the extras required (to make the function handle yield) could be treated as an insertion within the original function. "yield" has all sorts of other features, but used thus, it is a near direct replacement for DoEvents.
//'Replace DoEvents with Yield ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield )
var misc = 0; //'sample external var
function myfunction() { //'This is the beginning of your original function which is effectively replaced by a handler inserted as follows..
//'-----------------------------------Insert Handler..
var obj = myfuncGen.next(); //'start it
if (obj.done == false) {
setTimeout(myfunction, 150); //'adjust for the amount of time you wish to yield (depends how much screen drawing is required or etc)
}
}
var myfuncGen = myfuncSurrogate(); //'creates a "Generator" out of next.
function* myfuncSurrogate() { //'This the original function repackaged! Note asterisk.
//'-------------------------------------End Insert
var ms; //...your original function continues here....
for (var i = 1; i <= 9; i++) { //'sample 9x loop
ms = new Date().getTime();
while (new Date().getTime() < ms + 500); //'PAUSE (get time & wait 500ms) as an example of being busy
misc++; //'example manipulating an external var
outputdiv.innerHTML = "Output Div<br>demonstrating progress.. " + misc;
yield; //'replacement for your doevents, all internal stack state and variables effectively hibernate.
}
console.log("done");
}
myfunction(); //'and start by calling here. Note that you can use "return" to return a value except by call backs.
<div id='outputdiv' align='center'></div>
..If you are new to all this, be aware that without the insertion and the yield keyword, you would simply wait 5 seconds while nothing happened and then the progress {div} would read "9" (because all the other changes to {div} were invisible).

Prevent and queue action (but only once globally) if previously called within X seconds

I always run into this problem and seem to implement a nasty looking solution.
It seems like a common design pattern to fire an action immediately, but not let that action queue up if clicked rapidly / delay firing if previously called within a timeframe. In my real world example, I have an AJAX call being made, so if I don't prevent repetitive actions the browser queues requests.
How would you implement this differently? What other options are there?
function myFunction() {
console.log("fired");
}
var timeout = null;
$("#foo").click(function() {
// if not previously clicked within 1 second, fire immediately
if (!timeout) {
myFunction();
timeout = setTimeout(function() {
timeout = null;
}, 1000);
} else {
// clicked again within 1s
clearTimeout(timeout); // clear it - we can't have multiple timeouts
timeout = setTimeout(function() {
myFunction();
timeout = null;
}, 1000);
};
});
With your current code, if you repeatedly click "#foo" at an interval slightly less than one second, say every 800ms, on first click it will fire the function immediately (obviously), but then it will fire the function exactly once more one second after the last click. That is, if you click ten times at 800ms intervals the function will fire once immediately and a second time approximately 8 seconds (800ms * 9 + 1000ms) after the first click.
I think you're better off removing the else case altogether, so that on click it will fire the function if it has not been called within the last second, otherwise it will do nothing with no attempt to queue another call up for later. Not only does that seem to me like a more logical way to operate, it halves the size of your function...
On the other hand, since you mentioned Ajax, rather than disabling the function based on a timer you may like to disable the function until the last Ajax request returns, i.e., use a flag similar to your timerid and reset it within an Ajax complete callback (noting that Ajax complete callbacks get called after success or failure of the request).
In the case of an auto-complete or auto-search function, where you want to send an Ajax request as the user types, you might want to remove the if case from your existing code and keep the else case, because for auto-complete you likely want to wait until after the user stops typing before sending the request - for that purpose I'd probably go with a shorter delay though, say 400 or 500ms.
Regarding general structure of the code, if I wanted a function to be fired a maximum of once per second I'd likely put that control into the function itself rather than in a click handler:
var myFunction = function() {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid=null; }, 1000);
// actual work of the function to be done here
console.log("myFunction fired");
};
}();
$("#foo").click(function() {
myFunction();
});
The immediately invoked anonymous function that I've added makes it uglier, but it keeps the timerid variable out of the global scope. If you don't like that obviously you could simply declare timerid in the same scope as myFunction() as you currently do.
This answer is getting kind of long, but if you have a lot of different functions that all need some kind of repeat control in them you could implement a single function to handle that part of it:
function limitRepeats(fn, delay) {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid = null; }, delay);
fn();
};
}
// myFunction1 can only be called once every 1000ms
var myFunction1 = limitRepeats(function() {
console.log("fired myFunction1()");
}, 1000);
// myFunction2 can only be called once every 3000ms
var myFunction2 = limitRepeats(function() {
console.log("fired myFunction2()");
}, 3000);
$("#foo").click(function() {
myFunction1();
myFunction2();
});

Is there a definitive JavaScript (not jQuery) method for checking whether or not a web page has loaded completely?

Is there a definitive JavaScript method for checking whether or not a web page has loaded completely? Completely, meaning 100% complete. HTML, scripts, CSS, images, plugins, AJAX, everything!
As user interaction can effect AJAX, let's assume there is no further user interaction with the page, apart from the initial page request.
What you're asking for is pretty much impossible. There is no way to determine whether everything has loaded completely. Here's why:
On a lot of webpages, AJAX only starts once the onload (or DOMReady) event fires, making the method of using the onload event to see if the page has loaded impossible.
You could theoretically tell if the webpage was performing an AJAX request by overriding window.XMLHttpRequest, but you still couldn't tell if plugins like Flash were still loading or not.
On some sites, like Twitter.com, the page only loads once and simply makes AJAX requests to the server every time the user clicks a link. How do you tell if the page has finished loading on a page like that?
In fact, the browser itself can't be completely certain whether the page has completely finished loading, or whether it's about to make more AJAX requests.
The only way to know for sure that everything loaded is to have every single piece of code on the page that loads something tell your code that it has finished once it loads.
A hacky, incomplete solution: You could try overriding XMLHttpRequest with a function that wraps the existing XMLHttpRequest and returns it. That would allow you to tell if a AJAX event is currently taking place. However, that solution wouldn't work for seeing if plugins are loaded, and you wouldn't be able to tell the difference between AJAX events that are triggered at page load and AJAX requests that happen periodically, like the ones on Stack Overflow that change the Stack Exchange icon on the top-left if you have new notifications.
Try something like this:
(function(oldHttpRequest){
// This isn't cross-browser, just a demonstration
// of replacing XMLHttpRequest
// Keep track of requests
var requests_running = 0;
// Override XMLHttpRequest's constructor
window.XMLHttpRequest = function() {
// Create an XMLHttpRequest
var request = new oldHttpRequest();
// Override the send method
var old_send = request.send;
request.send = function () {
requests_running += 1;
old_send.apply(request, arguments);
};
// Wait for it to load
req.addEventListener("load", function() {
requests_running -= 1;
}, false);
// Return our modified XMLHttpRequest
return request;
};
window.addEventListener("load", function() {
// Check every 50 ms to see if no requests are running
setTimeout(function checkLoad() {
if(requests_running === 0)
{
// Load is probably complete
}
else
setTimeout(checkLoad, 50);
}, 50);
}, false);
})(window.XMLHttpRequest)
The:
window.onload
event will fire at this point.
window.onLoad = function(){
//Stuff to do when page has loaded.
}
or
<body onLoad="functionCall()">
Basically ADW and Keith answer the question, but I would suggest not to use window.onload but:
if (window.addEventListener) {
window.addEventListener("load", myfunction, false);
} else {
window.attachEvent("onload", myfunction);
}
function myfunction() {
...
}
Using a combination of window.onload, document.readyState, and callbacks for AJAX requests, you should be able to do what you want. Simply make sure the window has loaded, the DOM is ready for manipulation, and keep track of AJAX requests.
For AJAX in particular, depending on how many requests you make: Increment a variable each time you make a request, and when the variable === the total amount of requests, fire a function. If you don't happen to know the amount of AJAX requests, but know which one would be last, simply have a callback function fire when it finishes.
When all is set and true, fire a final function to do what you want, knowing everything should be loaded.
In regards to Flash and Silverlight applications (not sure if window.onload or document.ready keeps track of those), you could also record the amount of data loaded withing the application, and when the loaded data === the total data, have the application fire a function or increment a variable to the page.
window.onload = function() {
var time = window.setInterval(function() {
if(document.readyState == "interactive") {
increment();
window.clearInterval(time);
}
}, 250);
}
var total = 10, current = 0;
var increment = function() {
current += 1;
if(current === total) { weAreDone(); }
}
function weAreDone() {
// Everything should be done!
}
Here is the non intrusive js function I scripted, using events on load. In this case, I fire events on js script load as this is my js autoloader function, but you can just add event on other items using the same principle. Provided this script looks after js scripts loaded in a dedicated div tag.
function scriptLoaded(e) {
var oLoadedScript = e.target || e.srcElement;
alert ('loaded : ' + oLoadedScript.src);
return false;
}
/**
* Import js lib and fire function ControlData on events
* #param js_librairies
* #returns {Boolean}
*/
function init(){
// lib import
// Locate js in the div
var myscript_location = document.getElementById('js_script_goes_here');
// DEBUG
if (undefined == myscript_location)
alert('div not found');
else
alert('found div : ' + myscript_location);
// to prevent js script from catching in dev mode
var force_js_reload = "?version=1" ;
for (var i=0; i < js_librairies.length ; ++i) {
var my_script = document.createElement('script');
my_script.defer = false;
my_script.src = relative_path + js_librairies[i] + force_js_reload ;
my_script.type = 'text/javascript';
// DEBUG
my_script.onload = scriptLoaded;
myscript_location.appendChild(my_script);
}
return false;
}
/**
* Start non intrusive js
* #param func
*/
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
};
}
}
//ONLOAD
addLoadEvent(init);
function r(f){/in/(document.readyState)?setTimeout(r,9,f):f()}
Courtesy: Smallest DOMReady code, ever - Dustin Diaz
Update: And for IE
function r(f){/in/.test(document.readyState)?setTimeout('r('+f+')',9):f()}
P.S: window.onload is a very different thing

setTimeout not working in Greasemonkey user script when JS disabled in browser

I'm working on a project that requires my user script be run on pages as they are rendered without executing any of the page's JavaScript. That is to say, we need to browse with JavaScript disabled.
I've encountered a problem though when I try to delay execution of a function within my script. Whenever I make a call to window.setTimeout, the function I pass in never gets executed.
I think maybe this function is actually getting called on unsafeWindow instead of window. Is there any workaround for this?
I should mention that calls to setTimeout work fine when JavaScript is enabled and everything else in my script is working fine without enabling JavaScript.
Thanks for your help!
Even though Greasemonkey JavaScript runs with elevated privileges, as Pointy said, setTimeout functions are appended to the page's JavaScript space -- wrapped in a closure as needed. (In normal operation, the Greasemonkey instance is often gone by the time any timers, it has set, fire.)
So, if the page's main JavaScript is disabled, the timer will never run.
Possible workarounds:
Use GM_xmlhttpRequest as a crude delay. You can setup a page that deliberately draws out its response. So code like:
GM_xmlhttpRequest
(
{
method: "GET",
url: "http://YourTestServer.com/DelayService.php?Seconds=2",
onload: function (response) {YourDelayedFunctionHere (); }
}
);
Would call a utility page that you set up to do the delay for you.
Use NoScript to disable all of the page's JavaScript except for the main page. For example, for page, YourSite.com/testpage.htm, which includes scripts from, say, *SpamGenerator.net... Allow scripts from YourSite.com but block them from SpamGenerator.net.
The window reference is still the page's window, just wrapped in the sandbox wrapper thing. When you call setTimeout on it you're still setting up something to be run by the page. I suppose that it must be the case that the browser won't fire those timeout events at all (or will just ignore the events) when Javascript is disabled.
this can be patched like this:
You can say NO to NoScript + setTimeout = failed
In greasemonkey.js: find [ injectScripts ]: function..... add our GM-api.....
Add this code:
sandbox.setTimeOut = function (callback, timeout, p1,p2,p3/*....*/){
var args = Array.prototype.slice.call(arguments,2);
return sandbox.window.setTimeout(function(){
return callback.apply(sandbox, args);
} ,timeout);
}
or
sandbox.setInterval = function (callback, timeout, p1,p2,p3/*....*/){
var args = Array.prototype.slice.call(arguments,2);
return sandbox.window.setInterval(function(){
return callback.apply(sandbox, args);
} ,timeout);
}
This code is working fine, I have used it since May 2010.
In user.js you can test it like this:
setTimeout(alert,1000, 'i am happy');
var loopid = setInterval(alert, 1000, 'I am happy again');
setTimeout(clearInterval, 5000, loopid);
var j=300;
for(;~j;j--){ //running perfectly!
setTimeout(alert, 1000+20*j, 'I am happy' )
}
Solution 2
sandbox.kk_setTimeout = function (func, timeout, repeat_type, p1,p2,p3/*....*/){
var callback = { k100: sandbox };
var args = Array.slice.call(arguments,3);
// repeat_type: 0=once 1=repeatng, after fired stopped 2=always repeat
if(repeat_type!=2){
callback.notify = function (timer){ func.apply(this.k100,args); }
var timerCC = Components.Constructor("#mozilla.org/timer;1", "nsITimer", 'initWithCallback');
var R = repeat_type?1:0;
} else {
callback.observe = function (subject, topic, data) { func.call(this.k100); };
var timerCC = Components.Constructor("#mozilla.org/timer;1", "nsITimer", 'init');
var R = 2;
}
return new timerCC(callback, timeout, R);
}
// now have to test it:
var test100 = kk_setTimeout(alert, 1000, 0, 'i am timer'); //running = setTimeout
var test100 = kk_setTimeout(alert, 1000, 2, 'i am timer'); //running = setInterval
test100.cancal() ; //clear it by cancel() method
kk_setTimeout(alert, 1000+20*j, 2, 'i am happy' );
var j=300;
for(;~j;j--){
kk_setTimeout(alert, 1000+20*j, 0, 'i am happy 2' );
}
//bug:
//this solution 2 running after about 3-8 times differently stop, why bug ? i don't know.
// you will fail to use many times(over 3-8 time) kk_timeout(); or using repeat_type = 2 after fired 3-8 times timeout
//or running total time max about 20-30 seconds stop
//--- this maybe stop by option in about:config -- about [max javascript run time]
china-kkmove patched
edit to add…
Sorry everyone,
There are still a few patches to the code that I forgot to write:
sandbox.window = sandbox._proto_; // add this line also to the solution 1#
This error just came to my mind this morning.

Categories