User:Catrope/Bizarre browser bugs

Opera
Opera does a couple of annoying brain-damaged things that makes web developers go crazy. If someone ever starts serial-killing Opera developers, here's why:

Newline handling
The representation of newlines varies over browsers. For instance, Firefox represents newlines as  (even on Windows), while IE represents them as. Both browsers seem to be tolerant when trying to insert text in the 'wrong' newline format, and automatically convert it. This means you can code JS quite nicely without having to worry about one browser saying a newline is one character while the other says it's two.

Since Opera is this schizophrenic browser torn between wanting to be a good browser and wanting to be like IE, it came up with the following compromise: when you grab the contents of a textarea using  or  , newlines are represented as   , i.e. as one character. However, when manipulating selections using  or   (Opera, in its schizophrenia, supports both the Gecko way and the IE way), newlines are treated as two characters (presumably  ). This means that the following snippet doesn't do what you expect in Opera: In Firefox, this would search for the string "foo" in the textarea and select it if found. It'll also work in IE if you replace the  assignment with something using a TextRange object. However, it won't work in Opera, because of the different newline representations: the selection will be off to the left by the number of newlines preceding the found occurrence of "foo".

To hack around this, you'd have to do  and then call. Of course, you only want to do this when you're sure you're on a broken version of Opera (to my knowledge, all current versions are broken, but they might fix it some day), so you need a function that tests for this brokenness, then conditionally replaces  with.

The code for this function can be found in this file in a function called.

Moving selections
Some older versions of Opera (I only tested this on 9.0, not sure about 9.4 or 10) are very picky about how selections are moved. A typical code snipper to select the 4th through the 8th character would be: This looks very intuitive and works fine... initially. However, changing the selection to  afterwards using the same template doesn't do what you want: you end up selecting.

The reason for this is that in between the  and the   assignments, the selection would be  ; that is,   would be moved past. All sane browsers that use  allow this and treat negative-length selections as selections of length zero; that is, if   the cursor is placed at   and nothing is selected. However, Opera is not flexible like that: it stubbornly refuses to set  past , and simply ignores the assignment. To work around that, you have to set  first so   can safely be moved. However, assuming that this bug is symmetrical, moving  first is actually right when moving the selection back, so as to prevent moving   back past. This results in the following code:

However, even this doesn't fully address Opera 9.0's bugginess. In addition to refusing to move selections 'the wrong way around', it also refuses to create a zero-length selection before the current cursor position, that is, move the cursor back without selecting anything. In detail, it refuses to change  to be equal to. The reverse seems to work, but that's only an option when moving forward.

Internet Explorer
Some of the bugs and workarounds in this section were found by or in cooperation with Trevor Parscal.

Content-editable iframe manipulation
IE has some weird bugs related to content-editable iframes. They're detailed below in increasing order of weirdness.

A simple attempt at getting a content-editable iframe to work:

This works nicely in Firefox, but in IE the document.open fails, because the iframe hasn't been properly initialized yet. It's been added to the DOM, but it hasn't recevied its own DOM yet. This seems to happen semi-asynchronously: either the iframe is being constructed in parallel, or IE merely schedules the iframe to be constructed after the current event has been processed (I think the latter is more likely).

To work around this, we put the rest of the code in a setTimeout. Timeouts also fire after the current event has been processed completely, and in the case of IE it also seems to fire after the async construction of the iframe. This even happens for timeout intervals of 0 ms. Of course all of this is balancing on implementation details only barely coming together, but it works:

With this workaround, the document.* function calls don't fail any more. Instead, the iframe shows up for a short while, and then the entire page becomes blank. What's happening here is the most bizarre, confusing and scary phenomenon I've ever encountered. For some reason, IE doesn't like the fact that we're messing with the body element inside the iframe (the bug doesn't appear when messing with a pre inside the body, and also appears when using .text instead of .html), and just totally freaks out. It runs through the room in circles, screaming, with its hands against its head. And when it's calmed down, it finds out that it's decapitated the DOM.

That's right, the DOM has become headless. For some reason,  and   (point to the html and body elements of the document respectively) have both become null. The DOM elements themselves can find see each other with .parentNode and jQuery's .parent and what not, and you can still access DOM elements if you already had references to them, but traversing the DOM tree from the root down is no longer possible. This means that IE is unable to render anything (that's why the screen goes blank), and that every reference to a property of  or   results in a JS error. Even simple jQuery methods (at least all selector-based ones, it seems) use these, as well as jQuery's event handling system. In short: you thought you were having some iframe fun, and all of a sudden you get a mysterious blank page and even more mysterious JS errors all over the place in jQuery's gore internals, all just because you removed a pre tag.

One workaround for this is to avoid manipulating the directly:

Another one is to load the iframe contents from a .html file and use the load event on the iframe. However, once  is set, IE reloads the iframe DOM. This causes a number of issues: The workaround involves short-circuiting the first load event in IE and waiting for the second one before doing any manipulation other than enabling designMode. The event binding issue can be worked around by binding events from the load handler (on the second call of course).
 * Pretty much any action in the rest of the load handler will fail
 * The load event will fire a second time when IE is done reloading the DOM
 * Any events bound from inline s in the iframe will not fire because the elements they've been bound to have been destroyed

Loss of selection in content-editable iframe
When a content-editable iframe loses focus to an element in the main document, IE forgets the selection and cursor position (which is really a selection of length 0) in the iframe. When focus is restored to the iframe, the cursor will be at the beginning of the iframe. This makes implementing a toolbar that inserts text into the iframe challenging: if you're not careful, clicking a toolbar button will cause said button to gain focus, causing IE to forget where the cursor was and to insert text at the beginning of the iframe contents. To make matters fun, certain natural choices for toolbar buttons cause the aforementioned breakage on IE, while others don't.

The most natural choices for toolbar buttons are: Each of these would have an onclick handler that manipulates the selected text in the iframe. Now let's see which of these leave the iframe selection alone: If you find yourself designing a toolbar button/widget that needs more flexibility than a plain  or   can provide, you can still use a   as long as you take care to put the actual clickable part in an   or.
 * an  if your button is an image
 * an  if it's text
 * a  if you need more flexibility
 * an  works just fine
 * an  works too (remember to return false from the onclick handler)
 * an  without an href attribute does not work
 * a  also doesn't work

Whitespace collapsing
Whitespace collapsing is very serious business in IE. Both IE7 and IE8 are very aggressive about collapsing sequences of whitespace characters into single spaces. This whitespace collapsing will never remove HTML elements like  or entities like , but it will collapse other kinds of whitespace into them in certain cases.

Whitespace handling inside dynamically generated tags
When inserting arbitrary HTML into whitespace-sensitive tags (most notably ), you have to be very careful: IE doesn't always realize the tag you're inserting into is whitespace-sensitive, in which case it'll wrongly collapse all sequences of whitespace characters into single spaces. Interestingly, there seems to be a difference between injecting HTML with  and injecting it into the string fed to the jQuery constructor; I guess the latter makes the browser parse the injected HTML 'in context', realize it's inside a   and handle whitespace accordingly.

Leading whitespace collapsing in HTML
Having collapsed sequences of whitespace characters into single spaces, IE turns to s followed by spaces. It's not possible to collapse such a sequence into a single space without removing the  element, so IE will happily collapse it into the   instead, removing the spaces. This means that leading spaces on all lines but the first are removed.

To work around this, we need to replace leading spaces with. Fortunately, IE8 is happy when we replace the first leading space with ; this produces sequences like , which will be left alone (in our circumstances that is; it seems inserting into a content-editable element triggers special behavior, in more standard circumstances IE8 doesn't seem to be leaving these spaces alone). However, IE7 is more zealous and will still collapse the spaces following the  into a single space no matter what; in fact, it'll collapse all whitespace everywhere, so in IE7 we need to replace all spaces with  :

Tab collapsing in HTML
IE really doesn't like tab characters. In addition to the aforementioned whitespace collapsing affecting tabs, IE will convert any tab character in your HTML to a space; the exceptions IE8 seems to make for content-editable elements don't apply here. The only workaround we found to be able to display tab characters and retrieve them from the DOM is by replacing them with s that are then styled to look like tabs and converted back to tab characters when translating the DOM content back to text.

Newline insertion in normalized HTML in IE 7
After inserting stuff into a content-editable iframe and looking at the resulting HTML, different browsers produce different results: The difference between Firefox and IE 8 is that IE 8 capitalizes tag names (fair enough), but IE 7 additionally inserts newlines here and there. Closer investigation seems to indicate this happens before the start of each block-level element.

Of course each of these three normalized formats is equally valid, but most code developed on Firefox or IE 8 will be confused by IE 7 making up newlines out of nowhere, especially if said code is something like.

Multiline regexes are broken: dot matches
correctly returns null in both IE and Firefox: the dot doesn't match  because of the multiline modifier. However  returns null on Firefox (correct) but   in IE: apparently the dot doesn't match   but does match   in IE, even in multiline mode. Not only is this wrong ( is a line break character, so it shouldn't be matched by the dot in multiline mode), it's also particularly annoying because   returns   in IE.

TextRange property strips newlines in IE8
Suppose the DOM contains  or   and the user selects Bar and Baz (note that there's a line boundary between them). Getting the selection text in Firefox with  returns   just fine. In IE, the way to go is. This returns  in IE7, but   in IE8; the newlines get stripped. The only way to reliably determine the selected text including newlines in IE8 is by using  (which is accurate in both IE7 and IE8) and processing that to replace   and   by newlines and unescape entities.