User JavaScript

for Opera 8

Intro

Opera 8 beta 3 introduced support for User JavaScript. Opera 8 Final introduces support for specifying multiple userscript files, and for specifying directories to scan for *.js files, and Greasemonkey compatability.

User JavaScript is a powerful tool to make websites behave like you want! A userscript is basically a script from a local file that gets executed before any other scripts (or event handlers) on the page, once in every document. With userscripts, you can change layout, elements, behavior, almost anything.

This might ring a bell with some cross-browser readers. Firefox users can install an extension, the recently developed Greasemonkey, which gives Firefox the capability to download userscripts from the web. It should be noted that Greasemonkey was not the inspiration for developing User JavaScript, as CNet.com seems to imply. This idea had come up between Jonny A. (Opera tech thinker) and Lars H., (our main JavaScript guy) in 2002 already, and the code was actually written last year.

Opera's User JavaScript implementation offers more functionality, using special purpose events for the window.opera object. Opera's implementation in Opera 8 still requires some manual work, as you'll have to save script files to a specific location. Which not only means the feature will be limited to power-users, it also means that you can only shoot yourself in the foot after carefully pointing your gun.

User JavaScript files and directories

In Opera 8 final, you define a User JavaScript path under "Preferences > Advanced > Content > JavaScript options". You can use the map picker, but also directly enter a path, for example like this:

C:\userjs\main.js,C:\userjs\opera\;opera,C:\userjs\greasemonkey\;greasemonkey

This will load C:\userjs\main.js and all *.js files from the directory C:\userjs\opera\ and use them as ordinary Opera User JavaScripts. With all the special purpose events for the window.opera object available. The order of execution is undefined, depends probably on the order in which the operating system decides to report file entries in the directories. No recursion will take place. Then, also load all *.js files from the directory C:\userjs\greasemonkey\ and use them in Greasemonkey compatibility mode. If a file or directory is not followed by a type parameter, files with a name ending with user.js will be handled in Greasemonkey compatability mode.

Greasemonkey compatability mode

In this mode User Javascripts are executed just before onload instead of ASAP, and with no special events available. Greasemonkey style meta-comments are supported, so one can use:

// ==UserScript==
// @include http://*.opera.com/*
// @exclude http://my.opera.com/*
// ==/UserScript==

... to limit a script to certain hosts. Note that Greasemonkey userscripts that utilize Xpath do not work in Opera 8. Also, there is no GUI for automatically installing scripts, you'll have to download them yourself, and store them in your chosen directory.

Example script

With the help of Opera fans who know more JavaScript than me, I've made a simple framework script. This framework can be extended with your personal collection of script functions. All this using the venerable copy and paste method. Writing good JavaScript requires some experience, but anyone who can handle Notepad will be capable of enabling and disabling specific functions they want to run on all or on specific websites. With the arrival of multiple file support in Opera 8 final, it might be easier to create seperate script files though.

Framework

Save the framework script on your machine, and make sure your Opera installation uses the correct directory to look for User JavaScripts. This is the fragment you will want to edit in your local copy of the file:


//
// Listing of functions to be called on all pages:
//

allHandler=[
    bodyIDforAll,
//  killBlank,
// Do not comment out the last line
    doNothing];

//
// Listing of functions to be called for handling of specific domains:
//
domainHandler={
    'opera.com'             :   backgroundToGreen, // Canary in a coal mine: if opera.com turns green, this user.js is working
//  'www.nytimes.com'           :   makeWider,
    'Do not comment this out'   :   doNothing};


//
// Listings of functions to be called for handling of specific full paths:
//
pathHandler={
    'www.trader.ca/search/default.asp'  :   fixBrowserDetectTraderCa,
    'Do not comment this out'           :   doNothing};

By adding // (two slashes) at the start of a line, you can temporarily remove a handler. This will prevent the execution of the associated script on loading a webpage. By removing the //, the handler gets active. You'll notice three collections of handlers:

Included functions

The following functions are included in the example script:


// 'backgroundToGreen' can be used to test if the user script is working.
function backgroundToGreen(){
  document.body.style.background = "green";
}

// 'makeWider' sets all table widths in the page to 100%.
function makeWider(){
    var
        aElms=document.getElementsByTagName('table'),
        i=0,
        elm;
    while(elm=aElms.item(i++))
        elm.setAttribute('width','100%');
}

// 'fixBrowserDetectTraderCa' set a vital variable, then runs
// a native function in the searchpage of trader.ca
function fixBrowserDetectTraderCa() {
  is_ie = true;
  displayAll();
}

// 'bodyIDforAll' adds an ID attribute to body elements
// ID value is build from the server name:
// - www is dropped
// - dots are replaced with dashes
// - numbers are prepended with an underscore
function bodyIDforAll() {
    if(document.body){
        var
            b=document.body;
        if(!b.hasAttribute('id'))
            b.setAttribute('id',location.host
                .replace(/^www\./,'')
                .replace(/^\d/,'_$&')
                .replace(/\./g,'-'));
    // debug: uncomment next line to see generated body-IDs popping up (this gets old really fast)
    //  alert(document.body.id);
    }
}

// 'killBlank' replaces target=_blank attributes with target=_self
// adapted from the 'Destroy Target' Greasemonkey script
// http://meddle.dzygn.com/eng/weblog/destroy.target/
function killBlank() {
    var
        elm,
        i=0,
        external=document.links;
    while(elm=external.item(i++))
        if(elm.href&&(/_blank|_userwww/.test(elm.target)))
            elm.target='_self';
}

More scripts

I've made a collection of more scripts to copy and paste into the framework. The collection also links to various standalone userscripts for Opera I've found.

Notes

Performance

The impact of your userscript on Opera's performance depends on the functionality you put in the script. Efficiently written scripts will not cause noticable delays, unless the script has a lot of work to do. In Opera 8 beta 3, a crasher has been found related to the use of userscript.

Security

Hallvord explained:

A user JavaScript file can in no way harm your computer or stored data but badly written files can slow down Opera and malicious files can spy on your browsing. Never install and use a script library from someone you don't know and trust - if in doubt post in the Opera forums, newsgroups or mailing lists and ask if the script you would like to use is well written and exploit-free.

The userscript has exactly the same restrictions as a script on the page, except that it can access some specific userscript functionality (we'll get back to you on that) and that it can read the text property on any script element. Normal scripts can't read the text property of external scripts loaded from other servers than the document.

The additional privileges are available when the userscript is initially executed, and when a userscript event is being handled. Global functions defined by the userscript but called from a script in the page have no special privileges.

Support

Please don't expect me to explain specific functions, because I'm not a proficient JavaScripter myself. Experiment, share, and file bug reports if necessary. If you have developed a nice function, I'll be happy to add it to my collection here or to link to it. If I find a suitable location to get User JavaScript support, I'll link to it as well.