UserJS.org

Avoiding User JavaScript conflicts

Written on 2005-06-13 22:31 by tarquin. Last modified 2005-09-19 09:19

The most important part of avoiding conflicts is ensuring that your script only runs on the correct pages. See our article on avoiding page address detection mistakes. However, some scripts are designed to run on many different pages, and many are designed to run on all pages. There are billions of pages out there that the script may interract with, so it becomes very important to ensure that your scripts can run on them without conflicting with the existing scripts and code on the pages.

Remain anonymous

Wherever possible, use anonymous functions. These do not have a name, and will not be created as a global variable. Anonymous functions can either be executed immediately, or referenced for use later. You can usually wrap your entire script inside an anonymous function, although as long as you do not create any other global variables, this is not necessary:

(function () {
  ...code to execute...
})();

This is particularly useful when adding event handlers:

window.opera.addEventListener('BeforeScript',function (e) {
  ...code to execute...
},false);

If you need to use the same event handler function several times, and you do not want to repeat the same code, this is still possible:

(function () {
  var myHandler = function (e) {
    ...code to execute...
  };
  window.opera.addEventListener('BeforeEventListener.mousemove',myHandler,false);
  window.opera.addEventListener('BeforeEventListener.mouseover',myHandler,false);
})();

Local variables

Whenever you create or use a variable inside a function, make certain that you are using a local variable, not a global one. The keyword 'var' will create the local instance for you. Take the following example:

function myfunction() {
  tablerows = document.getElementById('datatab').getElementsByTagName('tr');
  for( x = 3; x < 10; x++ ) {
    tablerows[x].className = 'high';
  }
};

As well as the global function itself, this code contains two possible sources of conflict within the function code. Firstly, it uses a variable called 'tablerows'. The page may also have a global variable called 'tablerows'. If it does, this function will not create its own variable. It will overwrite the existing one, and cause problems for the page. Additionally, the page may have a global variable called 'x', and this function will also overwrite that. In both cases, the 'var' keyword should have been used to ensure that a local variable was created.

function myfunction() {
  var tablerows = document.getElementById('datatab').getElementsByTagName('tr');
  for( var x = 3; x < 10; x++ ) {
    tablerows[x].className = 'high';
  }
};

The same thing applies to cookies, since they share the same environment as the page's own cookies. Unfortunately, with cookies, there is no keyword to help you to avoid conflicts, so you just have to be careful with naming, and try to use a safe global variable name (see below).

Safe global variables

If you need to store a global variable, for example, so that an event handler can use it later, it is still possible. Firstly choose a name that is almost never going to be used by any pages (making sure it still has a descriptive name). Then add some characters that will never exist in normal variables, such as '$', '#', or '%'. Now instead of referencing it by name like a normal variable, reference it using array notation. You could even attach it to an object like 'document' instead of 'window'. Your variable would now stand as little chance as possible of conflicting with existing variables:

document['$%#bhgFixMenu'] = someValueToStore;

If you need to create lots of variables, you can store them as properties of your new safe variable:

<span class="js-comment">//make it an object so you can attach properties to it</span>
document['$%#bhgFixMenu'] = {};
...
document['$%#bhgFixMenu'].newColor = 'red';
document['$%#bhgFixMenu'].fullString = 'BHG menu fix';
document['$%#bhgFixMenu'].cleanUp = function () {
  ...
};

With cookies, this is not possible, but cookies allow you to use any name to store them with, and they are case sensitive, so you could use something like '$#%ujsImgSizer'. All you have to do is to make sure you use escape() when storing them, and unescape() when recovering them.

Not your prototypes

You should be very careful about adding extra properties to object prototypes. These are examples of the most problematic prototype modifications, which affect all variables, as they inherit from Object:

Object.prototype.newProperty = 'foo';
Object.prototype.newMethod = function () { ... };

Several scripts use the for( var x in y ) loop, and if you modify the prototypes like this, they will end up indexing properties that they did not expect, causing unexpected behaviour. With many menu scripts, this actually causes the entire scripts to fail. If you need to add to the prototype like this, add it just before you use it, do what you need, then delete it again so that it does not affect any more scripts:

Object.prototype.newProperty = 'foo';
Object.prototype.newMethod = function () { ... };
bar.newMethod();
delete Object.prototype.newProperty;
delete Object.prototype.newMethod;

Other User JavaScripts

Since User JavaScripts can be shared, and users can run any number of scripts, it is quite likely that your script may be being used on the same page as many other, even many hundred other User JavaScripts. Just because your script does not conflict with the scripts on the page, does not mean it will not conflict with other User JavaScripts. This makes it doubly important to follow the guidelines given in these articles.

Additionally, other User JavaScripts may be making use of objects that your script modifies. For example, if your script is attempting to prevent a browser sniffer from identifying Opera, you might want to delete the window.opera object. However, other User JavaScripts will almost certainly want to use the opera object, and will fail if you delete it. If possible, it is much better to modify the page's code to prevent it from detecting the window.opera object.

As another example of something that might cause problems, take the linkify text files and fix content type scripts. These both need to run on files that Opera is rendering as plain text. They then modify the files, adding or changing the structure. Once one of these has run, the other one will no longer be able to recognise the structure as being a plain text file, and will not run correctly. Conflicts like this are very difficult to avoid.

Invalid elements

JavaScript is not the only thing that can cause conflicts. CSS can also cause problems. If you add an extra element to the page (for example, to display a menu), it is possible that the page could already have some CSS that styles it in a way you did not expect. It might position it over other content, change its size, or make it invisible. The easiest way to avoid this is to add an invalid element that the page would not possibly use. Make up a new element type. Opera will treat unknown elements as a generic inline element (actually 'flow' level, so it can contain block elements, assuming its parent allows this) with almost no default styles. You can then apply any style you need:

var myElement = document.createElement('myblock');
myElement.setAttribute('style','position:absolute;top:0px;left;0px;');

As an additional bonus, this should prevent the page's scripts from accidentally picking up your element when they use getElementsByTagName. To make this even more reliable, wherever possible, append your new elements to an area that the page is unlikely to use. If you put them at the start of the page, then the DOM tree is modified in a way that shifts every element down the DOM tree by one sibling - this could easily cause scripts that use getElementsByTagName to make mistakes. If you append your elements to the end of the body, this does not have much effect on the other elements on the page, and is therefore preferred.