Skip navigation.

exploreopera

| Help

Sign up | Help

Posts tagged with "Firefox"

alignment of megaliths

, , , ...

The main point of the standards is of course to align the megaliths rendering and scripting engines of standards-compliant browsers. I've already discussed some of the problems arising when specs and implementations clash, and the dilemmas we face when we have the choice between being strictly standards-compliant and breaking some major website or breaking the spec, aligning with the other browsers, and making the site(s) work.

Never an easy decision to make!

I can now confirm a few points where Opera 9.5 is going to deviate somewhat from the specs or from our earlier implementations in order to be better aligned with other browsers. If you are a webmaster or JavaScript author please check that the changes will not cause problems for you!

We'll do the following:

  • Capturing event listeners will fire on target. This behaviour will be kept until Firefox fixes their bug. Opera's new implementation is not expected to cause problems since most sites using the addEventListener API will have been tested against Firefox. A possible exception is widget code which may expect Opera's old behaviour.
  • Implementation of capturing load event handlers changed: load events from within the document will now only be captured when the capturing event listener is attached to the document. In other words, earlier you could do
    window.addEventListener('load', func, true)
    and expect func to be called for all "load" events inside the document. From 9.5 you will have to do
    document.addEventListener('load', func, true)
    to get this capturing behaviour. (Please do not copy that code unless you know what the "true" part of it means!) This will probably become the official specification. Again, this will probably only break code specifically written for Opera such as widgets, and the change is known to fix large number of websites that use event capture by mistake.
  • We've changed the relationship between body.clientHeight and documentElement.clientHeight in standards mode. The value of body.clientHeight is now viewport height, documentElement.clientHeight is document height. This is AFAIK what Firefox does too. This change can break your website if you use browser detection before reading clientHeight from body or documentElement.


I believe Firefox will support adding capturing load events to document. There you go, alignment of megaliths!

(Like the post title? Found it on Wikipedia :smile:.)

cross-site scripting quirk in Firefox

Edit: the post title was meant to end with a smiley :smile: but the server turned it into HTML IMG markup.. Changing the title somewhat to make it sound less dramatic since I didn't get the smiley effect.

Quick quirk of the day: some methods on the window object can be called even from another domain. In Firefox these are window.close(), window.focus(), window.blur(), document.open() and (oddly enough) window.history.go().

It turns out that Firefox allows calling them even if they have been redefined in the page! So if you say

function close(){ alert('look, ma, x-domain!'); }


another site can call your window's close() method and your custom function will run. It can even return things the foreign script can use.

function close(){ return document.body.innerHTML; /* I'm so proud of my markup, I'll show anybody */ }


Quirky for sure. Insecure? Maybe it has some extremely limited XSS exploit potential. It would become an issue if a script author writes a function that returns something secret and names it close, blur or focus. But real-life severity depends on how likely that would be, and my guess is that even if scripters might use such function names they would be very unlikely to returning secrets from them. So IMO it's not a big issue. Mozilla hackers may want to fix it someday, meanwhile it might even be useful if you want to make your site slightly more accessible than current cross-domain limitations allow. p:

Firefox's pageshow and pagehide events not DOM2 events-compatible?

, , , ...

These events sound quite interesting and author friendly. But unless I'm missing something their current implementation in Firefox simply sucks!

I tried
document.addEventListener( 'pageShow', function(e){ log('pageshow fired'); }, false );
document.addEventListener( 'pageHide', function(e){ log('pagehide fired'); }, false );


No go. Then I tried window.addEventListener but that doesn't work either. Finally, I looked at the sample code and noticed it only shows DOM0-style event handlers -
<body onPageShow="..."
. :yuck: Really?? That makes the events pretty useless. I would like Opera to support similar events but Firefox's implementation looks too broken to copy. Tell me I'm missing something..

Firechicken

, , ,

Over at Mozilla.org they've been working on fixing their load events capture bug. That's great news, because Opera has had lots of trouble with sites that used event capture when they didn't intend to do so. We had the correct, spec-compliant implementation. However, web developers don't read specs. They test in browsers. We suffered the incompatibility problems because sites were written for Firefox's implementation. We've had ugly problems caused by this issue for years, on extremely important sites like AOL.com, and we've worked hard through Open The Web, information and even site patching.

Inevitably, when Firefox fixes the bug they run into those badly coded sites. They start seeing the problems we've had all along. So what do they do? They play chicken! They decide to tweak the spec to solve the problem their bugs created in the first place.

If they go for this solution, we have to follow them for compliance.

To be fair, I do think that aligning a spec to the web can be a good solution, as long as it ensures interoperability and covers the use cases. And the DOM2 Events spec itself is vague. I guess the problem is a spec that attempts to be so language-generic that it avoids referring to even the all-important JavaScript window object, it isn't clearly specified where the window object fits into the event capture/bubble model. They can and probably will argue that their implementation is DOM conformant (and make sure the HTML5/web apps spec lands on their side?).

I'm still disappointed.

Launching OddPatch 0.1 Beta

, , , ...


Launching in browser.js tomorrow or so: OddPatch beta, solving many of the compatibility issues with the service formerly known as OddPost, now Yahoo!Mail beta.

It's a complex patch sorting out a daunting number of issues on both sides. When things get this complex, it's rarely only "their fault" or "our fault" - the testing has uncovered several bugs in Opera and several mistakes in their JavaScript. Here's a walk-through of the entire patch:

// browser sniffing workaround - walking in through the back door
if( location.href.indexOf( '/dc/system_requirements?browser=blocked' ) >-1){
location.href='/dc/launch?sysreq=ignore';
}


Y!Mail has three modes for browsers: supported, possibly working, or blocked. Opera is blocked, but luckily there is a backdoor that will bypass the sniffing.

The irony here is that when they block us, they are making their work on Opera-compatibility much harder than necessary. If we get access, we'll do our best to make things work: test, find bugs, even decide to support things we haven't supported previously (we'll have selectSingleNode soon because Y!Mail uses it heavily, and their code was also a very important reason why DOM2 Style support was prioritised for Opera 9.0!). Blocking us makes it much harder for us to make their life simpler.

if( top.location.href.indexOf('/dc/launch')>-1 ){ 
// Gecko compatibility library uses defineGetter and defineSetter. We need to fake them.
//* Patch below is required but causes trouble.. 
Object.prototype.__defineGetter__=      function(){}
Object.prototype.__defineSetter__=      function(){}


This is known as "fake it until you make it". We won't have getters and setters anytime soon, but things will work anyway if we pretend we do.

// IEism called loadXML, basically a DOMParser / DOMLS equivalent
// must handle XML fragments without root element!
Element.prototype.loadXML=function(s){  
try{
var d=new DOMParser().parseFromString(s, 'text/xml'); 


This, I think, is a bit of the strange world IE lets you into if you put an XML tag in a page. That tag takes on a life of its own and starts behaving in some contexts like a document, it aquires several methods and properties - and though I at first thought I could simply fake it with a DOMParser I had to think again because...
}catch(e){ // DOMParser could not parse fragment, probably because of missing single root element. Workaround time..
var d=document.implementation.createDocument('', this.tagName, null), el=d.createElement('el');
//?? why did I use this.tagName there?
el.innerHTML=s;
for(var i=0 ; i<el.childNodes.length;i++){
d.appendChild(el.childNodes[i].cloneNode(true));
}
}


Yes, they are not always playing with well-formed XML fragments. Oh well, we'll pull out good-old-tagsoup-parsing .innerHTML and eat their strings anyway. Then we move on and fill in some other required bits and pieces of IE's XML DOM. Right now I'm not sure if all the stuff in this block is required, but there it is.. I'll be the first to admit that both code and comments are evidence of the somewhat chaotic process of late-night patching..

// faking IE-style XML element DOM - separate documents with documentElement within the main doc's DOM
this.documentElement=d.documentElement||d.firstChild; 
//?? firstChild is probably leftover from earlier versions using documentFragment?
this.XMLDocument=d;
// address book loading checks .parseError.errorCode
this.XMLDocument.parseError={ 'errorCode':0 };
return d;
}


And then is a peculiar mystery, who would ever need a function called isSameNode when == would presumably do the job?

// some method called isSameNode is called. Not sure where it comes from but simple enough to fake..
Element.prototype.isSameNode = function(n){
return n===this;
}


Here we go, more delicacies from IE's internals: the handy XPath method selectSingleNode. I have quite some reservations against loadXML and the other XML DOM stuff above, but selectSingleNode should be written into a standard as soon as possible because document.evaluate needs too many arguments for lazy JS coders and the returned object is too fiddly too.

// selectSingleNode support
var realSelectSingleNode=function( expr, resolver ){
var result=(this.ownerDocument?this.ownerDocument:this).evaluate( expr+'[1]', this, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null );
return ( result && result.snapshotLength ) ? result.snapshotItem(0) : null;
}
Node.prototype.selectSingleNode = function (expr, resolver)
{ 
  if (!resolver)
    if (this.nodeType == Node.DOCUMENT_NODE){
      resolver = document.createNSResolver (this.documentElement);
    }else if(this.nodeType == Node.ELEMENT_NODE && this.ownerDocument && this.ownerDocument.documentElement ){
      resolver = document.createNSResolver (this.ownerDocument.documentElement);
    }else{
resolver = document.createNSResolver (this);
    }
  return realSelectSingleNode.apply (this, [expr, resolver]);
}


Now, up till now the patching has been quite ordered. A patch is a bit of a kludge anyway, but so far it's been a nice kludge. Here come the problems that were too ugly for a nice kludge. Certain issues just required search and replace operations on the script source code, nothing else to do about them - and because Yahoo source is compressed and variable names random, search and replace must take that into account and go for seriously complicated and ugly regular expressions.

opera.addEventListener('BeforeScript', function(e){
// This is the riskiest patch
// Fixing typo: missing ' after attribute value
e.element.text=e.element.text.replace( /\):\(">"\)\),/, "):(\"'>\"))," );
e.element.text=e.element.text.replace( / id='_test_add_folder>/, " id='_test_add_folder'>" );


Yep, twice in their source code they say things like <tag class="foo> omitting the closing quote. That caused Opera to parse it as a text node instead of an element, meaning source code would appear here and there in the interface. Oops.

// WebForms2 problem: button attribute "action" is a URL in WF2
e.element.text=e.element.text.replace( /\.(action\b)/g, ".js$1" );


Specs and implementations collide again: if you set input.action to a value like 'markAsSpam' the WF2 spec means it will be resolved as a URL, so when the script reads it again it will see 'http://mail.yahoo.com/dc/markAsSpam' which is not at all what it expected.

// send button not working - attribute nodes must be in the document they will be used
e.element.text=e.element.text.replace( /(\w)\.setAttributeNode\((\w)\)/, "$1.setAttributeNode($1.ownerDocument.importNode($2, true))" );


Hm, is Firefox sloppy with exceptions on cross-document node usage again?

// workaround for getting the documentElement.xml  markup
e.element.text=e.element.text.replace( /(([\w\.]*)documentElement).xml/g, "(document.implementation.createLSSerializer()).writeToString($1)" );


IE's XML DOM rides again. Elements over there have an .xml property which is basically the equivalent of .innerHTML for an HTML element, showing the inner serialised markup.

I didn't take a long and hard look at how Y!Mail used it but some of that seemed very weird. I had the impression that they read .xml of the contacts list only to pass it around as a string and use loadXML later on. Why would they serialize markup just to parse it right into a DOM tree again? Oh well, there is probably some complex reason..

// for...in on objects run into our faked __defineGetter__ and __defineSetter__
// we try to add an exception to any for...in loops
e.element.text=e.element.text.replace( /(for\((var |)(\w*) in \w*\)\{)/g, "$1if($3.match(/^__define/))continue;" );


It turns out "fake it until you make it" wasn't such a good idea after all. The site called our bluff with code like
function foo(obj){
for( p in obj )return false; return true
}

var bar = {}; if(!foo(bar)) return;

and what exactly they meant by that I don't know either, except to check that a newly created object really REALLY had no properties. Huh?


// To: / CC: autocomplete fails
// we support IE's TEXTAREA.createTextRange but unfortunately not its boundingLeft property. Improving object detection..
if(e.element.src&&e.element.src.match(/ac\.js$/))e.element.text=e.element.text.replace( /if \( editCtrl\.createTextRange \)/, "if ( editCtrl.createTextRange  && editCtrl.createTextRange().boundingLeft )" );


This is a typical trap of piecemeal implementation of something: we support whatever of IE's stuff was deemed important to get some plaintext formatting JavaScript to work with 8.x. .boundingLeft wasn't on the list back then. Sorry. Look a bit harder when you look for something.

// Preferences not read correctly from XML attributes
// IE has an attribute node .text property. .nodeValue will work in Opera..
e.element.text=e.element.text.replace( /\.selectNodes\((\w*)\);\s*\}return\((\w*)\.length\)\?(\w*)\[0\]\.text:/g, ".selectNodes($1);}return($2.length)?$2[0].nodeValue:" );


IE again. text alias nodeValue, enough said.

// We throw an unwanted exception if both arguments to insertBefore are the same node
e.element.text=e.element.text.replace( /var (\w*)=(\w*)\?(\w*)\.nextSibling:(\w*)\.firstChild;\s*(\w*)\.insertBefore\((\w*),(\w*)\);/, "var $1=$2?$2.nextSibling:$4.firstChild;if($6!=$1) $4.insertBefore($6,$1);" );


Now this is plainly a bug. The DOM spec says we should throw an error if the node you insert is a parent of the reference child, but we also did so if the inserted node was the reference child itself.

The code doesn't make sense, mind you... Why do you want to replace an element with itself?

// Opera 9.00 and 9.01 has a bug that means createContextualFragment on table elements is unreliable
// easily the worst patch.. but then it works around a really tricky bug..
if( navigator.userAgent.indexOf('9.01')>-1 || navigator.userAgent.indexOf('9.00')>-1 ){ 
// UA detection to target specific bug in specific version is OK
e.element.text=e.element.text.replace( /(\b(\w*)\.selectNodeContents\((\w*)\);\s*var (\w*)=(\w*).createContextualFragment\((\w*)\))/, "if($3.tagName=='TBODY'||$3.tagName=='TR'){ $2.createContextualFragment=function(s){var n=s.match(/<(\\w*)/)[1]; var e=document.createElement('div');e.innerHTML='<table><tbody>'+s+'</tbody></table>';return e.getElementsByTagName(n)[0];  } }$1" ); 


Just as ugly as it looks, just an attempt to make the code work in 9.01.
Imagine if Yahoo!Mail blocked us until they one day decided that it was necessarily to start working on Opera compatibility? If we never had gotten to test their system, they would probably have to add such an ugly workaround to their application to get around this bug. Developers everywhere: please, don't sniff, just leave more of the burden of compatibility on the UA's table (and listen to feedback!).

Hey, we're done with the replacements! It wasn't pretty, and I look forward to deleting one by one while things are fixed on either side. That will also give us a nice performance lift. Their scripts are huge. At some point I did some profiling of the above replace calls and found that up to half of the time it took Opera to load Y!Mail was spent applying the above patches.

}
}, false)

// No scrollbars appear for message list..
// uses an "overflow" CSS property to control scrollbars. e.overflow="-moz-scrollbars-vertical", and some odd clipping as well..
document.addEventListener( 'load', function(){ setTimeout( function(){ var divs=document.getElementsByTagName('div');for(var i=0,div;div=divs[i];i++)if(div.className&&div.className.indexOf('fakeScrollBar')>-1){div.style.overflow='auto';div.style.clip='auto';}},500);}, false );


Yes. That problem. It uses some CSS I still haven't fully understood.. I think they wanted to show an element with a scroll bar but clip the whole element away so only the scroll bar would be visible.

// redraw problem hides To: field in compose screen
document.addEventListener( 'load', 
function(){ if(top.document.frames['newmessage']){
setTimeout( function(){try{top.document.frames['newmessage'].document.body.className+=' ';}catch(e){}},1000);
}}, true);


This is another tricky one, it has to do with timing and I still haven't quite captured the sequence of events. Basically the "To" and "Subject" fields in the compose screen disappear until you click "Show BCC".

// sluggish performance due to unintended event capture
(function(ael){ 
window.addEventListener = function(type, func, capture){ ael.call(window, type, func, false); }
})(window.addEventListener);


..and just to top if off, they had to capture events by mistake. Of course. That's from the curriculum of "How to code Opera-incompatible websites 101".

opera.postError( 'Yahoo mail patched' );
}
}


Yippee! We did it!

Now, this patch is (repeat after me) in beta! There are problems that I'm aware of but haven't fixed, and there are problems that I'm not aware of and haven't fixed. And while I was working on this stuff, code changes in Yahoo mail would break things again every few days (and even break differently on the U.S. and the U.K. sites!). So, hurry up and try it while you have a chance! I'll try to keep the patch maintained, and we sure hope that we'll get all the issues sorted out from either side for a fast, friendly, responsive experience - somewhere in the future..

eval ... everywhere?

, , , ...

Harvested from an Opera bug report, here is another interesting surprise from Gecko internals: Object.prototype.eval . It's like a normal eval() but in the scope of that object. In other words, doing

document.body.eval('tagName');


is practically the same as doing

with(document.body){eval('tagName');}


(Before you ask, the best practice would be to say either
document.body.tagName
or
document.body['tagName']
Using eval should be avoided and generally can be avoided without problems. So don't let me catch you using any of the two first snippets on a production site!)

So hey Gecko, where did this come from? Is it something we should copy and implement or something we should scorn and point fingers at?

praising Firefox :)

To keep up the appearance of being somewhat fair and balanced after my recent anti-Firefox rant, here's some praise for Opera's favourite competitor :smile:

One of the Firefox features I like most is a tiny touch that I'm pretty sure none of you ever noticed: the default alternative text for image inputs. This test case shows what I'm talking about. Opera's default alt text is same as for a usual image, just saying [image] in brackets. Firefox says "Submit query". Lovely and thoughtful attention to detail went into that decision!

Somewhere in Opera's BTS there is a bug report by me saying "copy that feature!". It might not make it into the big lists of "features that browser x copied/borrowed/stole from browser y which had it FIRST" but we'll see.. :wink:

When specs and implementations clash

, , , ...

One of the points of having open, independently developed standards from the W3C is of course to achieve compatibility.

However, standards are not always clear and consistent, and browsers do not always get things right.

When facing a choice between being compatible with other browsers and websites OR the standard, we may have a third way: fixing the standard to align it with the common implementations and the content. That's the only way one can have the cake and eat it, be standards-compatible and compatible with the Web as it is. Is it feasible? Or even a good idea? Standards are supposed to be set-in-stone after all, to keep implementors happy so that we don't spend hours and money doing it this way only to find a rewritten standard tells us to follow that way instead..

Three real-life examples of cases where we do break or are about to break the standard:

1) getAttribute on non-existing attributes
Standard mandated returning an empty string. Other browsers returned null. Sites running into problems due to Opera's correct standards support included Yahoo mail.

We reached a general consensus that returning null is a better idea, so we have changed Opera and asked the relevant W3C group to consider changing the text in an errata or the next version.

2) Throwing WRONG_DOCUMENT_ERR on cross-document node usage
If a node created in one document is appended to another document, the standard clearly states that a WRONG_DOCUMENT_ERR should be thrown. Firefox doesn't, and sites taking the Firefox behaviour for granted and breaking in Opera included Blogger.com's rich text editor.

Opera will start to call adoptNode implicitly. I'm not sure what way the standard will go - nobody seems to intend to push for a change here. According to their bug report, Mozilla intends to fix it (which is great!) and as soon as they do and websites fix their coding errors, we can revert to spec-compatible mode. Meanwhile, we are not going to live with sites being broken in Opera due to Firefox's bugs.

3) addEventListener and firing capturing events on target
Setting a capturing "click" event listener on, say, an IMG tag has no effect, because the standard clearly says that event listeners should not fire on target. Again, Firefox gets the implementation wrong and sites where this has caused trouble include live.com (according to the Safari team, I have not seen this myself but their analysis is probably right).

Sure, we should fix the standard here and go for it - I don't see any specific benefit to not firing the event listener though I'm all ears if anyone knows why the spec is written like that. We haven't changed this yet but I guess we will follow Safari.

The lesson? To standards bodies: please care about existing implementations, experience and content when developing the standards. Specs that are seriously incompatible with prior implementations or web content cause implementation headaches, incompatibilities and suffering users.