Opera Mini and JavaScript
Introduction
Opera Mini is one of the world's foremost mobile browsers, which runs on a range of devices and operating systems ranging from expensive smart phones and tablets to less expensive feature phones that are in common use across many parts of the world. In July 2012, there were 187 million Opera Mini users, an increase of 50% over July 2011. Opera Mini is growing strongly on smart phones; in the same period, the share of Opera Mini smartphone users in Asia Pacific countries increased from 9% to 32%; in Indonesia, the increase was more than 460%. (Our monthly State Of the Mobile Web reports give the latest statistics for different territories, lists of top ten handsets and websites.)
Much of the reason for Mini's popularity is that it is capable of rendering complex web pages, even on low-spec devices. It does this by using a proxy-based architecture (see Figure 1). Requests from the user's handset pass through the carrier's internet gateway on their way to Opera's transcoding servers. These servers then forward the request to the server.
The server sends the response back as normal — when this is received by the Opera transcoding servers, they parse the markup and styles, execute the JavaScript, and transcode the data into Opera Binary Markup Language (OBML). This OBML data is progressively loaded by Opera Mini on the user's device. Think of it as an interactive snapshot of a document's state, which is similar in form to a PDF.
Note: Opera Mini versions older than 5.0 do not support progressive loading of OBML content; the page loads will seem slower and more sluggish on older devices.
OBML is great for users and carriers; it sends less data — up to 90% less — over the network. But there is a downside for developers: JavaScript can behave in unexpected ways. In the course of this article we'll discuss what this means for your development, but first, let's look at how to create a test environment, and detect Opera Mini.
Note that in general I use ‘JavaScript’ interchangeably with ‘DOM scripting’ in this article. I've used more precise terms such as ‘DOM events’ where appropriate.
Setting up a testing environment Using MicroEmulator
Unlike Opera Mobile, Opera Mini does not have a standalone emulator, and it can be tedious to switch between a device and a computer during development. Fortunately, you can use MicroEmulator, an LGPL licensed J2ME environment to run your own instance of Opera Mini on your desktop machine. To use MicroEmulator, make sure you have Java installed first, and then:
- Download MicroEmulator and unzip it to a location of your choice.
- Download the Opera Mini package, and save it somewhere memorable. Go to the Opera for mobile devices download site and select the Download Opera Mini 7 link to give you a JAD installer file.
- Run microemulator.jar, then at the start-up screen, choose File → Open MIDlet File from the menu (see Figure 2) and select the Opera Mini JAD file.
- Click Start. Opera Mini will load as it would on a mobile device.
Note: If opening the JAD file in the MicroEmulator doesn't work, you can access the Opera Mini JAR file directly, as follows: open the JAD file with a text editor, find the MIDlet-Jar-URL: line, then copy the URL from that line and enter it into your browser to allow you to directly download the Opera Mini JAR file. Now try selecting this file using the MicroEmulator.
MicroEmulator offers two modes:
- Default device emulator, which mimics the behavior of a feature phone
- Resizable device (shown above), which does not provide dial pad emulation, but offers a larger screen.
You can select which mode you'd like to use under Options → Select device.
Detecting Opera Mini
99% of the time, you should use feature detection to determine whether a browser supports a particular feature or API. Yet sometimes browser sniffing is the right choice. There are two approaches you can take to detect Opera Mini. You can:
- Examine the user agent string
- Check for the presence of the
operamini
object
Using the user agent string
As with most browsers, Opera Mini identifies itself through the navigator
object, and its userAgent
property. Its user agent string adheres to the following pattern:
Opera/9.80 ($PLATFORM; Opera Mini/$CLIENT_VERSION/$SERVER_BUILD; U; $CLIENT_LANG) Presto/$PRESTO_VER Version/$OPERA_VER
To detect whether a particular request is (presumably) from Opera Mini, check for the presence of Opera Mini
in the user agent string. An example follows.
var isOperaMini = (navigator.userAgent.indexOf('Opera Mini') > -1);
Detecting Opera Mini using the window.operamini
property
Opera Mini also includes an operamini
object as a property of the window
object. To check for the presence of this object, use the following code.
var isOperaMini = Object.prototype.toString.call(window.operamini) === "[object OperaMini]"
Determining the corresponding desktop version
Unfortunately, navigator.appVersion
is not an accurate way to determine which version of Opera you're using (here's why). Instead, you'll need to parse the value of the navigator.userAgent
string. An example using regular expressions is shown below.
var operaVersion = navigator.userAgent.match(/Version\/([1-9]+\.[0-9]{2})/)[1];
Again, in the vast majority of cases, you should use feature detection rather than user agent sniffing.
What to know about JavaScript in Opera Mini
There are two guiding principles to keep in mind when developing JavaScript with Opera Mini in mind.
- Everything requires user interaction.
- Everything requires a server round-trip.
As mentioned above, an OBML document is more like an interactive image of a document at a particular state. During the transcoding process, JavaScript is executed by the server. If a script continues to run, it will be paused before being sent to the user's device. In other words, animations won't work well, if at all.
In addition, links, buttons and most elements with a mouse event listener are transformed into hotspots. When a user clicks or taps on such a hotspot, it triggers a server request. If the hotspot has an event listener attached, its handler will be called during that request, and executed by the server.
How does that work in practice? Let's explore click and form events.
Click events in Opera Mini
In desktop versions of Opera (and mobile versions with trackball type controls available), the mouseover
and mouseenter
events are fired after the user hovers over an object, but before the user clicks it. When the user presses the mouse button, a mousedown
event is fired, followed by a mouseup
event when the button is released and then a click
event. When the user moves the mouse away from the object, the browser will fire mouseout and mouseleave events.
In Opera Mini, the mouseover
, mousedown
, mouseup
, and click
events are all fired at once, after the user engages with a clickable hotspot (see Figure 3, and view my click events example). Note that the mouseenter
, mouseleave
and mouseout
events are never fired.
Form events in Opera Mini
In a similar fashion, form fields can receive focus
, click
, change
, and blur
events, in that order. However, these events will only be triggered after the user has changed the value of a field (see Figure 4, and view my form events example).
Unlike desktop browsers, focus
and blur
events only apply to form input elements in Opera Mini. The click
event is the only mouse event supported for input
elements.
As you may have noticed in the examples above, any time the state of the document changes — any time a repaint or reflow needs to occur — Opera Mini must make a request to the transcoding server.
JavaScript execution limits
Opera Mini servers usually execute JavaScript quickly. Scripts that consume too much memory, however, will be killed. As with any JavaScript development, your goal should be to construct scripts as efficiently as possible.
Some JavaScript and DOM operations have absolute timeouts. When reached, the script is paused by the server, as mentioned above.
setTimeout
and setInterval
setTimeout
and setInterval
are subject to the following timeout limits:
- Opera Mini 5.0+:≈5 seconds
- Opera Mini 4.x: ≈2.5 seconds
This means that if you use setInterval
to invoke a function, it will be called a maximum of n times where n is the quotient of the timeout divided by the interval. If the interval is 1000 milliseconds, the function will be executed a maximum of five times (in newer versions of Opera Mini) before the entire script is paused (5000 ÷ 1000 = 5). If the interval is 5000 milliseconds, it will be executed no more than once before pausing.
setTimeout
, obviously, works a bit differently. If the timeout value is greater than 5000 milliseconds — give or take a few milliseconds — the function will not be executed unless the user interacts with the document. Your best bet is to set the smallest timeout value necessary.
In both cases, the timeout limit begins when the page starts to load.
XMLHttpRequest
timeouts
XMLHttpRequest
is also supported by Opera Mini. Timeout limits are as follows:
- Opera Mini 5.0+: ≈5 seconds
- Opera Mini 4.x: ≈2.5 seconds
As with setInterval
, if an XHR request exceeds the timeout, the script will be paused as it's transcoded.
Restarting paused scripts
What does a paused script look like? Have a look at my video example of a paused script being restarted. Here we are using setInterval
to change the background color of an object every 50 milliseconds. When the timeout limit is reached, the color will stop changing as the state of the script pauses.
It's possible to un-pause a script, however, with the help of a user-initiated event. Let's examine the JavaScript code used to create the animation in the video.
var box = document.getElementById('box'),
update = document.getElementById('valofnewcol'),
start = 0;
changecolor = function(element, degreeshift, update){
var newcol = (start * degreeshift)/360 << 8,
col = 'hsl('+newcol+',100%,50%)',
coltxt = document.createTextNode(col),
anim,
restart;
element.style.background = col;
start++;
if(update.firstChild){
update.replaceChild( coltxt, update.firstChild );
} else {
update.appendChild( coltxt );
}
}
anim = setInterval(changecolor, 50, box, 45, update);
// Unpauses the interval.
restart = function(){
// This can be an empty function.
}
box.addEventListener('click', restart, false);
The last few lines of this script add a click
event listener to the box
object. When the box object receives a click
event, the interval will resume. (view my animation example running live.)
As mentioned before, Opera Mini versions 5.0 and upwards support progressive loading, meaning data is sent to the client in chunks as it is transcoded. This is not true of Opera Mini 4, which is why its timeouts are shorter.
Unsupported DOM events
Mini supports DOM events, but only a subset. Much of this is dictated by hardware; some phones lack keyboards, for example. Below is a table of events and event attributes supported in Opera desktop or Opera Mobile that Opera Mini does not support.
Event name | Event attribute |
---|---|
contextmenu | oncontextmenu |
dblclick | ondblclick |
error | onerror |
keydown | onkeydown |
keypress | onkeypress |
keyup | onkeyup |
mousemove | onmousemove |
mouseenter | onmouseenter |
mouseleave | onmouseleave |
mouseout | onmouseout |
mousewheel | onmousewheel |
resize | onresize |
scroll | onscroll |
touchcancel | ontouchcancel |
touchend | ontouchend |
touchmove | ontouchmove |
touchstart | ontouchstart |
As you can see, key events such as keypress
and keyup
are not supported. Neither are touch
and scroll
events.
At this time, the unload
event and the onunload
event attribute are poorly supported across Opera products. Though feature detection can be used to indicate support, the current implementation does not perform as it should.
Unsupported techniques and APIs
The DOM methods, techniques, and HTML5 APIs that in this section are not supported in Opera Mini as of version 7.0.
Opening and closing windows
Opera Mini also doesn't support using JavaScript to open new windows (such as window.open()
). If you use window.open()
to launch a new window, Opera Mini will instead load that page as a new 'screen.' It will not open a new window or tab. Similarly, because a new window was never opened, window.close()
will not work. (This is also true of the target
attribute of HTML.)
Offline storage APIs
Since Opera Mini's client-server architecture requires it to be online, it shouldn't come as a surprise that Opera Mini doesn't support offline storage APIs.
Supporting local databases presents performance and scalability challenges since data would have to be stored on the server rather than client-side. Writing or reading data would require server round trips. Applications that rely on localStorage
, sessionStorage
, Application Cache, or client-side databases will not work for Opera Mini. Use server-side storage instead.
Web Workers
For scalability and performance reasons, Web Workers have been disabled in recent versions of Opera Mini, despite support in the corresponding desktop versions of Opera.
Native client-side form validation
Current versions of Opera desktop and Opera Mobile support HTML5 form features, including validation. Opera Mini, at this time, does not support them. Developers must still use JavaScript for client-side validation, and newer type attribute values such as range
and email
default to the text
type in Opera Mini. However, you can still use selectors such as input[type=range]
in your CSS and with the Selectors API.
You may also set attributes such as min
, max
and pattern
, and use the getAttribute()
method to retrieve those values for your validation script. Another option is to just rely solely on server-side validation — something you probably have and should have in place regardless of client-side validation support.
Debugging JavaScript in Opera Mini
Opera Mini lacks the robust development and debugging tools of other Opera products such as Opera Mobile and its emulator and Dragonfly for Opera desktop. It does, however, offer two development tools:
server:source
, which dumps the generated source code for the current page state.server:console
, which collects JavaScript errors and output fromconsole.log()
.
Using server:source
Entering server:source
into the address bar after a page has been rendered will reveal the server-generated source code for the page (see Figure 5).
You can also export this source to a script on your server. Enter server:source?post=http://your.server.com/script
in the address bar. This will send three fields — url
, host
, and html
as a POST request. Your script can then write this code to a file or a database.
Using server:console
Opera Mini's JavaScript console supports a subset of the Console API. JavaScript errors will be logged to the console as shown in Figure 6. You can also write messages to the console using console.log()
.
You can also download and install the corresponding version of Opera desktop to take advantage of Dragonfly's debugging tools. But be aware that the sequence of events may be fired in a different order than you might expect.
This article is licensed under a Creative Commons Attribution 3.0 Unported license.
Comments
Mağruf Çolakoğlu
Wednesday, September 5, 2012
Charles McCathieNevile
Wednesday, September 5, 2012
Yeswap
Wednesday, September 5, 2012
Chris Mills
Thursday, September 6, 2012
@Yeswap - I think you have a point here. I have updated the sentence to read "...some phones lack keyboards, for example..."
Maxim
Friday, September 7, 2012
Jerzy Głowacki
Sunday, September 30, 2012
When will more HTML5 elements and longer timeouts (ca. 10 s) be supported in Opera Mini? Have a look at Puffin Browser, which also streams websites, but in much more responsive and interactive way.
And please get rid of those transcoding errors while loading pages using a lot of JS, like Disqus!