Archive for April, 2008

IE 6 bug causes jQuery globalEval error

UPDATE: this is fixed in jQuery 1.2.6 and later, see ticket #2709.

After upgrading our code base to use the latest jQuery 1.2.3 (previously we were using 1.2.1) our testers discovered a quite ridiculous bug in IE 6 that caused jQuery to fail (IE 7 is fine, which is why we didn’t experience it development). The issue manifested itself as the script error message “Problems with this web page might prevent it from being displayed properly…

Line: 24
Char: 76
Error: Invalid Argument
Code: 0
...

…and various document.onready handlers not running.

Of course line 24, char 26 doesn’t really help much because IE always seems to get this a few lines out of whack and with the minified version of jQuery it would be way out. So I replaced the minified version jQuery.js with the uncompressed version, cleared the browser cache and re-visited the web app in IE 6. This then gave line 659, char 4 as the offending location:

image

After whacking in a several alert()s in the lines in the vicinity of 659, it turns out the issue is with head.removeChild(script) in the globalEval function, 4 lines up. Obviously.

So, why was head.removeChild failing? I put the following debug code in:

alert(head.tagName) // displays "HEAD" as expected
alert(script.parentNode.tagName) // displays "BASE" !!!

So, it transpires that our pages having a <base> tag within the <head> contributes to the issue. IE 6 seems to get totally confused as to the structure of the document HEAD when there’s a self-closing or unclosed BASE tag. BASE tags I suppose are quite rare and this is probably why this issue doesn’t appear to be commonplace. (The BASE tag is in there for legacy reasons in our code, but they’re also quite useful when you save the source of HTML pages they still pick up images and script from the originating server which is handy for debugging automatically-generated HTML.)

So, before, the offending base tag looked something like this:

<base href='http://example.com/blah' />

After some experimentation it appears IE6 doesn’t exhibit its odd behaviour if you do this instead:

<base href='http://example.com/blah'></base>

Job done.

Update: Chris Venus comments about the reason for the weird behaviour in IE6 and earlier. Previously <base> was interpreted as a container, which could appear multiple times in a document: different sections within the page could have different bases, so would logically end up wrapped within each <base> (now there’s a feature everybody wanted, right?). Because of this, <head> got treated as a “section” of the document and elements added to it ended up as children of any <base> it contained rather than siblings. See IEBlog: All your <base> are belong to us.

Images Broken When Viewing Windows SharePoint Services RSS Feeds in Outlook

If using Windows Live Writer or Microsoft Word 2007 to create blog posts in Windows SharePoint Services (WSS) 3.0 it appears, at least on our installation of SharePoint, that when you embed new images in the post, the image tags are generated with relative links, e.g. <img src="/Lists/Posts/Attachments/51/image_thumb.png"> . This is fine if you’re viewing the feeds in a web browser as it can resolve the server name for the links from the feed address. But if you’re using Outlook 2007 to subscribe to the feeds, any images you embedded in the post using Live Writer or Word are generated as relative links, Outlook can’t/doesn’t resolve the site name hence the images are broken:

image

Whose fault this is (Outlook/SharePoint/Live Writer) I don’t know, but fixing it required a bit of coding hackery - I couldn’t find any option in the SharePoint configuration for controlling the generation of RSS.

WSS v3.0’s default RSS generator is a page called ListFeed.aspx that lives in Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\template\layouts. By default it looks like this:

<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages" %>
<%@ Page Language="C#" Inherits="Microsoft.SharePoint.ApplicationPages.ListFeed" %> 

Clearly all the logic is in Microsoft.SharePoint.ApplicationPages.dll. So to fix this, I backed up this file and added the following code to ListFeed.aspx which basically passes all of the feed content through a filter using HttpResponse.Filter which uses a regular expression to replaces any relative <img src="/blah“> tags with the absolute <img src="http://site/blah”>.

<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages" %>
<%@ Page Language="C#" Inherits="Microsoft.SharePoint.ApplicationPages.ListFeed" %>
<script language="C#" runat="server">
protected override void OnInit(EventArgs e)
{
  base.Response.Filter = new RelativePathFilter(base.Response.Filter, this.Request, this.Response);
  base.OnInit(e);
}

class RelativePathFilter : System.IO.Stream
{
  System.IO.Stream _innerStream;
  string _siteUrl;
  Regex _imagesRegex;
  HttpResponse _response;

  public RelativePathFilter(System.IO.Stream innerStream, HttpRequest request, HttpResponse response)
  {
    _innerStream = innerStream;
    _siteUrl = (new Uri(request.Url, HttpRuntime.AppDomainAppVirtualPath)).AbsoluteUri;
    _response = response;

    // Finds rooted images <img ... src="/ ...
    _imagesRegex = new Regex(@"(<img .*? \s src \s* = \s* [""']?)/", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
  }

  public override void Write(byte[] buffer, int offset, int count)
  {
    string content = _response.ContentEncoding.GetString(buffer, offset, count);

    // Ignore the fact that it's theoretically possible for the <img> tag to be split between 2 Write()s
    content = _imagesRegex.Replace(content, @"$1" + _siteUrl);

    byte[] newBuffer = _response.ContentEncoding.GetBytes(content);
    _innerStream.Write(newBuffer, 0, newBuffer.Length);
  }

  public override bool CanRead { get { return _innerStream.CanRead; } }
  public override bool CanSeek { get { return _innerStream.CanSeek; } }
  public override bool CanWrite { get { return _innerStream.CanWrite; } }
  public override void Flush() { _innerStream.Flush(); }
  public override long Length { get { return _innerStream.Length; } }
  public override long Position { get { return _innerStream.Position; } set { _innerStream.Position = value; } }
  public override int Read(byte[] buffer, int offset, int count) { return _innerStream.Read(buffer, offset, count); }
  public override long Seek(long offset, System.IO.SeekOrigin origin) { return _innerStream.Seek(offset, origin); }
  public override void SetLength(long value) { _innerStream.SetLength(value); }
}
</script>

I’m not entirely sure whether hacking around with these files is supported (although it did seem to survive the installation of WSS 3.0 SP1), or if there’s a better way of doing this, so use at your own risk.

Update 22 April 2008: Modified code so that it doesn’t assume the site is at the root of a virtual server.