@miketaylr 'n stuff.

Subscribe to RSS feed

U.S. Citizenship and Immigration Services Change of Address, bless their hearts.

OTW-6273 is a bug that was opened in the first month I joined Opera (nearly 2 years ago). The title is "USCIS Online Change of Address doesn't work in Opera".

That title is perhaps misleading now. At the time, Opera was the only browser out there with decent HTML5 forms support. Since then, nearly all browsers have some level of support for the constraint validation APIs and new input types.

So here's the original report from the bug:


NOTE:
This is the US government site for online change of address that foreigners in the US must use.

Steps to reproduce:
1. Go to the URL https://egov.uscis.gov/crisgwi/go?action=coa
2. Click "Change your address online" at the bottom of the page
3. Click "I Accept these terms and conditions" at the bottom of the page
4. Click "No, this change of address is not for a US Citizen"
5. Fill out the fields with the asterisk symbol
6. Click "Signature" button to submit

Result:
In Opera, you can't submit the form unless all the non essential fields are filled out.

Expected:
You should be able to submit the address change form with other fields without the asterisk symbol being filled out. This works in Firefox. (Mike: well, it did 2 years ago anyways)



So why is this so broken in modern browsers?

They use a custom attribute (custom at the time, anyways) to denote a required input element. However, their "custom" attribute is actually just the required attribute set to required="yes" or required="no". If you look at the spec, you'll see that required is a boolean attribute. This means its mere presence sets the DOM property to "true", no matter the value.



You can add recent veresions of Firefox, Chrome and Safari to the list of browsers this site doens't work on. And IE10 in the near future.

In the two years that I've tried to contact them and advocate how to fix this site (data-required and a few simple corresponding changes), while they haven't yet fixed it, they've at least noticed the site is broken:

screenshot of site saying We cannot support Google Chrome for completing your Change of Address at this time. Change of Address is optimized to use Internet Explorer 6 and 7

"We cannot support Google Chrome for completing your Change of Address at this time. Change of Address is optimized to use Internet Explorer 6 and 7."

Bless their hearts.

Maerskfleet.com and "optimising" for browsers.

If you visit maerskfleet.com in Opera, you'll get a page that looks like this:



That's not quite right, what does it look like in Chrome?



Wow, quite the difference. Let's check out the error console.



The main culprit is this guy:

Uncaught exception: TypeError: 'this.browser[BrowserDetect.browser]' is not a function 


As it turns out, in http://www.maerskfleet.com/js/VideoTools.js, they're determining support for HTML5 video based on browser names and version numbers. Given that the HTML5 video API was designed in such a way that a browser will play any format it understands, and also has a fallback scenario (for older browsers), this is, well... weird.

 /*
     * @return {boolean} True or false, according to if the browser and version supports video 
     */
    var compatible = function () {
        this.browser = {
            'Firefox' : function (version) {        
                return version < 3 ? false : true;
            },
            'Chrome' : function (version) {
                return version < 4 ? false : true;
            },
            'Safari' : function (version) {
                return version < 3 ? false : true;
            },
            'Explorer' : function (version) {
                if (version<9) {
                    return false;      	
                } else {
                    return true;
                }
            }
        };
        
        return this.browser[BrowserDetect.browser](parseInt(BrowserDetect.version));
    }


They have some previous sniffing code (the BrowserDetect part) that correctly identifies Opera users. But since they don't include a function for it it in their this.browser object, we get the error.

If we were lucky enough to get through that without an error (which we weren't), the next bit of code is used to determine video source:

 /*
     * Lookup object to recieve correct video URLs for deferent browsers
     */
    var parseUrl = {  
        'Firefox' : function (url) {
            return {
                'url' : config.urls.video[config.urls.location] + 'light/' + url + '.theora.ogv',
                'type' : 'video/ogg; codecs="theora, vorbis"'
            }
        },
        'Safari' : function (url) {
            return {
                'url' : config.urls.video[config.urls.location] + url + '.mp4video.mp4',
                'type' :  'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'
            }
        },
        'Chrome' : function (url) {
            return {
                'url' : config.urls.video[config.urls.location] + url + '.theora.ogv',
                'type' : 'video/ogg; codecs="theora, vorbis"'
            }
        },
        /*'Chrome' : function (url) {
            return {
                'url' : url + '.webmvp8.webm',
                'type' : 'video/webm; codecs="vp8, vorbis"'
            }
        },
        */
       'Explorer' : function (url) {
           return {
                'url' : config.urls.video[config.urls.location] + url + '.mp4video.mp4',
                'type' :  'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'
            }
       },
        'iPad' : function (url) {
            return {
                'url' : config.urls.video[config.urls.location] + url + '.mp4video.mp4',
                'type' :  'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'
            }
        },
        'iPhone/iPod' : function (url) {
            return {
                'url' : config.urls.video[config.urls.location] + url + '.mp4video.mp4',
                'type' :  'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'
            }
        }
    }

That's a whole lot of logic when all they needed to do was the following:

<video>
  <source src="foo.mp4video.mp4" type="...">
  <source src="theora.ogv" type="...">
</code>

There's even a nice article on Wikipedia for the lazy.

I did finally get in touch with someone working on the site, but their final reply was:

Thanks for the heads up on the Opera compatibility for Maerskfleet. It is currently not a browser we are optimizing to.



It seems in this case, taking a browser-agnostic API and coding in stumbling blocks is considered optimization.

Problematic anfreak.com.br "problem detection"

OTW-8161 is a weird little bug that I've had on my plate for a while.

If you visit anfreak.com.br with Opera, you get redirected to http://anfreak.com.br/ie.html. Notice the end part, "ie.html". Curious.



If you study the image closely (and perhaps speaking Portuguese will help here) you'll notice that the page tells you to download and use a browser other than Internet Explorer. The weird part is the Opera logo with the words "PROBLEMA DETECTADO" on it.

It gets more interesting when you look at the sniff that lands you here. Here's the whole script, but the part that interests us is at the end:

if (BrowserDetect.browser == "Opera"){
location.href="http://anfreak.com.br/ie.html"

It turns out that IE users don't even get redirected, just those using Opera.

This isn't an overly popular site, so I'm not sure if we'll bother spoofing as Firefox for everyone. If anyone happens to know someone who runs this site, send 'em my way. I'd love to find out what problems were detected by this PNG image. :)

Creating my first Opera Speed Dial extension

, ,

Just wanted to document quickly what it took to create my first Speed Dial extension (don't forget about Daniel's excellent article as well). The extension is simple—it just shows a portion of the first cell of the most recent Dinosaur Comics. There's only two files in this extension: a manifest document and an HTML file that serves as the host document for the background process of the extension.

config.xml

<?xml version="1.0"?>
<widget xmlns="http://www.w3.org/ns/widgets" id="http://miketaylr.com/" defaultlocale="en" viewmodes="minimized">
  <name short="Dino Comics">Dino Comics Preview</name>
  <description>See the first panel of the most recent Dino Comics. Click through to read the rest!</description>
  <access origin="http://www.qwantz.com" subdomains="true" />
  <icon src="dino.png"/>
  <author>Mike Taylor</author>
  <feature name="opera:speeddial" required="false">
    <param name="url" value="http://www.qwantz.com/index.php"/>
  </feature>
</widget>
Not much to note if you've developed any widget that follows the W3C Packaging and Confuration spec. I'll just point out the viewmodes="minimized" on the widget root element as well as the opera:speeddial feature element. The url param, with its corresponding value determine where the actual Speed Dial cell navigates to when someone clicks on it. Also important is the access element where I'm telling the extension that it can fetch resources from qwantz.com.

The Code

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>Dino Comics</title>
<canvas width="244" height="243" id="c"></canvas>
<style>
  @media screen and (view-mode: minimized) {
    canvas {-o-transform:translate(-3px, -5px);margin:0 auto;display:block;}
  }
</style>

We first create a bucket of variables, instantiate stuff, store references, etc.:
var xhr = new XMLHttpRequest(),
    ctx = document.getElementById('c').getContext('2d'),
    url, dummy = document.createElement('div');
Then, we perform an XMLHttpRequest to the mobile version of qwantz.com (I decided to use the mobile page just because it's simpler and easier to scrape). If everything is peachy, we send the content of the page (which is returned in the reponseText property of the XHR object) to the findURL function:
function findURL(response){
  dummy.innerHTML = response;
  url = /http.+png/.exec(dummy.querySelector('img').style.backgroundImage);
  createImg(url);
}

We jam the responseText into an off-document <div> so the browser will parse the HTML and we can use DOM methods to poke at it (in this case querySelector). We then run a fairly lazy RegEx on the content of the first image's inline style text to grab a reference to the PNG image of the comic. That gets passed off to the createImg function:
function createImg(url){
  var img = new Image();
  img.onload = function(){
    ctx.drawImage(img,0,0);
  };
  img.src = url;
}

This function creates a new image from the URL we passed in, and once that has finished loading draws it onto the <canvas> of index.html. Et voilà:
That's it. Mind blowingly awesome, I know. A fun exercise to get familiar with the Speed Dial extension API that probably took two hours. OK I'm lying, it took like three because I wasn't sure what it was even going to do. Feel free to grab the code in this gist. Now let's see if it gets approved. ^_^

Injected scripts execution order in Opera 11 extensions

,

It turns out there's a very specific order as to when an extension's injected script is added and executed. As it stands, scripts within the /includes/ folder are executed in alphabetical order while extensions are executed in reverse alphabetical order.

So, for example, if you have the following two extensions A and B installed:

A.oex/
  config.xml
  index.html
  /includes/
    a-A.js
    b-B.js


B.oex/
  config.xml
  index.html
  /includes/
    a-B.js
    b-B.js


The execution order will be: a-B.js, b-B.js, a-A.js, b-B.js

Including .js files into Opera extensions

, ,

When working on my .js and .css highlighting extension, I wanted a way to move all of my JavaScript for my interactive options.html page into a file that lives in the extension package.

Until we get a fancy API method to do this for us (à la Chrome's getURL), you can just dynamically create a script element and append that to the document.

function addJS( name ) {
  var script = document.createElement('script'),
      root = window.location.protocol + "//" + window.location.host + "/",
      head = document.head;

  script.src = root + name;
  head.appendChild( script );
};

addJS( 'options.js' );

When called inline from options.html, this will inject a script element that looks something like:

<script src="widget://wuid-00000f00-0fff-0000-00ff-f0ff0fff0fff/options.js"></script>