UserJS.org

Running scripts as soon as possible

Written on 2005-11-12 21:22 by tarquin. Last modified 2005-11-12 21:59

With most User JavaScripts, it is not critical to run them early. If they patch a script, they can usually wait for that script to be encountered, and alter it as needed. If they add an enhancement to the page, they can normally wait for the load event to fire, and then run whatever code they need.

But occasionally, scripts need to run earlier. The load event only fires after the information for all images and embedded objects has been received. Depending on what your script does, it may need to run earlier than that. Opera does not have any event that fires as soon as the DOM is complete.

Assuming the file name ends with .js (not .user.js), Opera will run User JavaScripts before the first script on the page is processed. This is done to allow you to set any user JavaScript events that you want, before any scripts are encountered. If the page has no scripts of its own, then user scripts are run immediately before the load event is about to fire. Note: if the page has no scripts of its own, then your script will always run after the page has loaded - there is nothing you can do about that.

User JavaScripts are run before the DOM for the page has been built, so you cannot just start working with the page and expect it to be there, because it won't be. So you have two choices:

  1. You can listen for an event and run your script when that event fires.
  2. You can wait for an amount of time, then run your script after that.

Neither of these are particularly efficient, so if possible, use a normal load listener. Note also that being able to reference an element does not mean that the element's children have completed loading. For example, if you append something to the body before it has completed parsing all children of the body, you will get an unreliable and inconsistent response. You can check if the element's nextSibling also exists, but that will only work if it actually has a next sibling. This is not a problem with a normal load listener.

Using events

There are two User JavaScript event types that may be useful.

The first is the event that fires before a script is loaded. If you know that everything your script needs to access will be available before a certain script loads, you can set a BeforeScript (or maybe a BeforeExternalScript) event listener, and run your script when that event fires. To keep your script efficient, you can then remove the event listener.

opera.addEventListener('BeforeExternalScript',function (e) {
  if( e.element.getAttribute('src').match(/scripts/main.js$/) ) {
    opera.removeEventListener('BeforeExternalScript',arguments.callee,false);
    ... do whatever you needed to do ...
  }
},false);

In that example, I check the script src, but you could just as easily check if the required elements could be accessed and then run your script.

You could also use the same techniques with elements that fire load events. Images also have load events, and the BeforeEvent.load event will fire for all of them. This is only really useful if the images are likely to load much faster than the page. Remember that if that image fails for any reason, your script will not run at all.

If you use BeforeEvent.load, note that the if there are no images (or other elements that can fire a load event), it will fire only once, immediately before the load event would fire. In this case, BeforeEvent.load will give you no advantages over using a normal load event, except that you can guarantee that your script will run before any script that use the normal load event.

Running scripts after a delay

This is more like a hack than a proper technique, but it works. This has been used in scripting for many years (I have used this in public scripts since as far back as the start of 2001, without any problems).

The idea is to set an interval, that checks every 200ms or so, to see if the required elements are available. At the same time, set a load event listener. If the load event fires first, it should clear the interval, then run your script. If the interval references the required elements first, then it should remove the load event listener, clear the interval, and then run your script.

(function () {

function runmyscript() {
  if( document.getElementById('theThingIAmLookingFor') ) {
    clearInterval(checkForElement);
    document.removeEventListener('load',checkForElement,false);
    ... do whatever you needed to do ...
  }
}

var checkForElement = setInterval(checkForElement,200);
document.addEventListener('load',checkForElement,false);

})();