Activate your free membership today | Log-in

Wednesday, August 27th, 2008

Internet Explorer 8 Beta 2 and Web Standards

Category: Announcements, Browsers, IE

Internet Explorer 8 Beta 2 was released today. There are several cool UI enhancements that this beta brings to the table that I won't cover in this post, but you can learn more about them on the IEBlog. Instead, I want to talk about how beta 2 affects IE's relationship to web standards.

First, CSS Expressions are no longer supported in Standards Mode:

Also known as 'Dynamic Properties', CSS expressions are a proprietary extension to CSS with a high performance cost. As of Internet Explorer 8 Beta 2, CSS expressions are not supported in IE8 standards mode. They are still supported in IE7 Strict mode and Quirks mode for backward compatibility.

In case you don't know, CSS expressions were actual bits of JavaScript that you could run from CSS rules; this was commonly used to simulate the CSS max-width property for IE:

CSS:
  1.  
  2. div.someClass {
  3. /* Internet Explorer */
  4. width: expression(document.body.clientWidth> 600) ? "600px" : "auto";
  5. /* Standards-compliant browsers */
  6. max-width: 600px;
  7. }
  8.  

IE 8 beta 2 also now supports alternate style sheets:

Internet Explorer 8 now supports alternative style sheets as specified by HTML4 and CSS2.1. The alternative styles that are defined by the Web page author is available through the Style menu under the Page menu. The styles are also available through the Style menu under the View menu. The No Style option on either menu can be used to disable all authors styling.

In terms of the Known Issues with IE 8 Beta 2, the first is related to Ajax bookmarking and back button support and using window.location.hash to do cross-domain communication:

Internet Explorer 8 create entries in the travel log and back button for each instance of window.location.hash that is set. This is part of the behavior for Internet Explorer 8 AJAX Navigation. If you use this technique to communicate between documents, we recommend that you switch to the Internet Explorer 8 Cross Document Messaging feature that is based on Section 6.4 of the HTML 5.0 specification.

Finally, there are some issues with the onload event for IE's XDomainRequest object that helps with cross-domain communication:

The onload event may not fire reliably. We recommend you use the onprogress events which continues to fire as the data is received.

Unfortunately this is it for this release. You can see the full Beta 2 release notes, or download it yourself.

On a related note, IE 8 Beta 2 includes cross-site scripting attack (XSS) protection:

The XSS Filter operates as an IE8 component with visibility into all requests / responses flowing through the browser. When the filter discovers likely XSS in a cross-site request, it identifies and neuters the attack if it is replayed in the server’s response. Users are not presented with questions they are unable to answer – IE simply blocks the malicious script from executing.

Finally, PPK has also published a post on IE 8 Beta 2 and its changes.

Posted by Brad Neuberg at 5:59 pm
3 Comments

+++++
5 rating from 1 votes

YouTube Uploader now using Gears, and what people missed in Gears 0.4

Category: Gears

While we posted about the Gears 0.4 features a lot of the press only really talked about the Geolocation piece.

I think that is important, and posted on that too, but Brad's piece discussed the full gamut including the Blob API, resummable HTTP, and Desktop API improvements to allow controlled file system access. The example that Brad built was the upload movie tool which ties this all together.

Check out the source code and you will see the parts and pieces in action:

Geolocation

JAVASCRIPT:
  1.  
  2. MovieTool.prototype.getLocation = function(callback) {
  3.   var geolocation = google.gears.factory.create('beta.geolocation');
  4.   if (!geolocation.getPermission('Upload Movie Tool', '',
  5.                                   'This sample can use your '
  6.                                 + 'geographic coordinates in order to tag '
  7.                                 + 'uploaded videos with your location')) {
  8.     document.getElementById('location').innerHTML = 'Permission denied';
  9.     callback();
  10.     return;
  11.   }
  12.  
  13.   var self = this;
  14.   geolocation.getCurrentPosition(
  15.     function(p) {
  16.       var addr = p.gearsAddress;
  17.       var address = addr.city + ', ' + addr.region + ', ' + addr.country;
  18.       var latitude = p.latitude;
  19.       var longitude = p.longitude;
  20.       self.geoAddress_ = address + ' (' + latitude + ', ' + longitude + ')';
  21.       document.getElementById('location').innerHTML = self.geoAddress_;
  22.       callback();
  23.     },
  24.  
  25.     function(err) {
  26.       var msg = 'Error retrieving your location: ' + err.message;
  27.       document.getElementById('location').innerHTML = msg;
  28.       callback();
  29.     },
  30.    
  31.     {
  32.       enableHighAccuracy: true,
  33.       gearsRequestAddress: true,
  34.       gearsLocationProviderUrls: ['http://www.google.com/loc/json']
  35.     }
  36.   );
  37. }
  38.  

HTTPRequest

JAVASCRIPT:
  1.  
  2.   var req = google.gears.factory.create('beta.httprequest');
  3.   req.open('GET', '/list');
  4.   var self = this;
  5.   req.onreadystatechange = function() {
  6.     if (req.readyState == 4) {
  7.       if (req.status == 200) {
  8.         var loadingMsg = document.getElementById('loadingFilesMsg');
  9.         loadingMsg.parentNode.removeChild(loadingMsg);
  10.         self.movieList_ = eval(req.responseText);
  11.         for (var i = 0; i <self.movieList_.length; i++) {
  12.           var entry = self.movieList_[i];
  13.           // associative entry for fast lookup based on filename
  14.           self.movieList_['_' + entry.filename] = entry;
  15.           var status = 'Uploaded'
  16.           var percent = '100%';
  17.          
  18.           // was this movie partially uploaded during an earlier browser
  19.           // session?
  20.           if (entry.uploaded == false && entry.blob == null) {
  21.             status = 'Partially uploaded; re-select file to continue uploading';
  22.             percent = Math.round((entry.bytesUploaded / entry.length) * 100) + '%';
  23.           }
  24.          
  25.           // is this movie too large?
  26.           if (entry.uploaded == false && entry.length> self.MAX_FILE_SIZE) {
  27.             status = 'File too large';
  28.             percent = 'N/A';
  29.           }
  30.          
  31.           self.drawRow(entry.filename, entry.geoAddress, status, percent);
  32.         }
  33.       }
  34.      
  35.       callback();
  36.     }
  37.   }
  38.  
  39.   req.send();
  40. }
  41.  
  42. ....
  43.  
  44. // sending a piece
  45.   req.open('POST', uploadURL);
  46.  
  47.   req.setRequestHeader('Content-Disposition',
  48.                         'attachment; filename="' + fileName + '"');
  49.   req.setRequestHeader('Content-Type', 'application/octet-stream');
  50.   req.setRequestHeader('Content-Range', 'bytes ' + byteRange);
  51.  
  52.  

Desktop API

JAVASCRIPT:
  1.  
  2. MovieTool.prototype.selectFiles = function() {
  3.   var desktop = google.gears.factory.create('beta.desktop');
  4.   var self = this;
  5.   desktop.openFiles(
  6.     function(files) {
  7.       for (var i = 0; i <files.length; i++) {
  8.         var entry = {filename: files[i].name, uploaded: false,
  9.                             length: files[i].blob.length,
  10.                             blob: files[i].blob, bytesUploaded: 0,
  11.                             geoAddress: self.geoAddress_, uploadRetries: 0};
  12.         if (self.movieList_['_' + entry.filename]) {
  13.           // was previously uploaded at an earlier browser session
  14.           var oldEntry = self.movieList_['_' + entry.filename];
  15.           if (!oldEntry.uploaded) { // partial upload
  16.             oldEntry.length = entry.length;
  17.             oldEntry.blob = entry.blob;
  18.             var percent = Math.round((oldEntry.bytesUploaded / oldEntry.length) * 100);
  19.             self.updateStatus(oldEntry.filename, 'Not uploaded', percent + '%');
  20.           } else {
  21.             self.updateStatus(oldEntry.filename, 'Uploaded', '100%');
  22.           }
  23.         } else { // new file
  24.           // associative entry for fast lookup based on filename
  25.           self.movieList_['_' + entry.filename] = entry;                   
  26.           self.movieList_.push(entry);
  27.          
  28.           var status = 'Not uploaded';
  29.           var percent = '0%';
  30.           // is this movie too large?
  31.           if (entry.length> self.MAX_FILE_SIZE) {
  32.             status = 'File too large';
  33.             percent = 'N/A';
  34.           }
  35.          
  36.           self.drawRow(files[i].name, self.geoAddress_, status, percent);
  37.         }
  38.       }
  39.      
  40.       document.getElementById('movieUpload').disabled = false;
  41.       document.getElementById('clearMovies').disabled = false;
  42.     },
  43.    
  44.     { filter: ['video/quicktime', '.wmv', 'video/avi'] }
  45.   );
  46. }
  47.  

It turns out that YouTube has implemented a multifile upload using Gears which puts this into practice. As someone who uses YouTube a lot to upload files this is very welcome indeed, and I can't wait to see more sites use it now the building blocks are there in a different way (can always use Flash / other controls in the past).

Posted by Dion Almaer at 9:20 am
2 Comments

+++--
3.8 rating from 6 votes

Proxy issues with querystrings in path names

Category: Performance

You have seen this before: /path/to/something.js?v=2, or maybe it used a date or a version control id or some such. The notion of putting the version into the URL so you can aggressively cache and yet quickly push new versions.

There has long been issues with using the querystring as the version. At some point I seem to remember Safari not going a good job caching that scenario and thinking that it was different.

Steve "Neo" Souders has posted about this issue especially as it relates to proxy servers and default configurations:

There’s a section in my book called Revving Filenames. It contains an example of adding a version number to the filename. That’s prompted several emails where people have asked me about tradeoffs around using a querystring versus embedding something in the filename. I wasn’t aware of any performance difference, but in a meeting this week a co-worker, Jacob Hoffman-Andrews, mentioned that Squid, a popular proxy, doesn’t cache resources with a querystring. This hurts performance when multiple users behind a proxy cache request the same file - rather than using the cached version everybody would have to send a request to the origin server.

I tested this by creating two resources, mylogo.1.2.gif and mylogo.gif?v=1.2. Both have a far future Expires date. I configured my browser to go through a Squid proxy. I made one request to mylogo.1.2.gif, cleared my cache (to simulate another user making the request), and fetched mylogo.1.2.gif again. This produces the following HTTP headers:

>> GET http://stevesouders.com/mylogo.1.2.gif HTTP/1.1
<< HTTP/1.0 200 OK

<< Date: Sat, 23 Aug 2008 00:17:22 GMT
<< Expires: Tue, 21 Aug 2018 00:17:22 GMT
<< X-Cache: MISS from someserver.com
<< X-Cache-Lookup: MISS from someserver.com

>> GET http://stevesouders.com/mylogo.1.2.gif HTTP/1.1
<< HTTP/1.0 200 OK
<< Date: Sat, 23 Aug 2008 00:17:22 GMT
<< Expires: Tue, 21 Aug 2018 00:17:22 GMT
<< X-Cache: HIT from someserver.com

<< X-Cache-Lookup: HIT from someserver.com

Notice that the second response shows a HIT in the X-Cache and X-Cache-Lookup headers. This shows it was served by the Squid proxy. More evidence of this is the fact that the Date and Expires response headers have the same values, even though I made these requests 10 seconds apart. For conclusive evidence, only one hit shows up in the stevesouders.com access log.

Loading mylogo.gif?v=1.2 twice (clearing the cache in between) results in these headers:

>> GET http://stevesouders.com/mylogo.gif?v=1.2 HTTP/1.1
<< HTTP/1.0 200 OK
<< Date: Sat, 23 Aug 2008 00:19:34 GMT
<< Expires: Tue, 21 Aug 2018 00:19:34 GMT

<< X-Cache: MISS from someserver.com
<< X-Cache-Lookup: MISS from someserver.com

>> GET http://stevesouders.com/mylogo.gif?v=1.2 HTTP/1.1
<< HTTP/1.0 200 OK
<< Date: Sat, 23 Aug 2008 00:19:47 GMT
<< Expires: Tue, 21 Aug 2018 00:19:47 GMT
<< X-Cache: MISS from someserver.com
<< X-Cache-Lookup: MISS from someserver.com

Here it’s clear the second response was not served by the proxy: the caching response headers say MISS, the Date and Expires values change, and tailing the stevesouders.com access log shows two hits.

Proxy administrators can change the configuration to support caching resources with a querystring, when the caching headers indicate that is appropriate. But the default configuration is what web developers should expect to encounter most frequently. Another interesting note about these tests: notice how the proxy downgrades the responses to HTTP/1.0. This is going to alter browser behavior in terms of the number of connections that are opened. When I’m doing performance analysis I make sure to avoid being connected through a proxy.

Posted by Dion Almaer at 6:06 am
2 Comments

++---
2.5 rating from 4 votes

Towards Using Custom Fonts

Category: CSS, Design

A little while ago, we talked about the two competing custom font technologies for the Web: linking and "embedding" (aka EOT). With Firefox about to implement support for linking à la Safari, John Allsopp has a summary of the state of font technologies and an illustration of just how easy it is to use these in stylesheets.

Once you define a font-face using both linking and EOT thusly:

CSS:
  1. @font-face {
  2.   font-family:"Fenwick";
  3.   src: url(fenwick.eot);
  4. }
CSS:
  1. @font-face {
  2.   font-family: "Matrix";
  3.   src: url(http://www.westciv.com/CSS3Tests/matrix.ttf) ;
  4. }

You can use a single CSS attribute to reference whichever is supported on the currently browser and gracefully degrade on browsers that don't support either technology:

CSS:
  1. p {
  2.   font-family: Fenwick, Matrix, san-serif;
  3. }

Soon-to-be ubiquitous <canvas> (somehow, we'll get there), faster JavaScript, and custom fonts. Man, this is exciting...

Posted by Ben Galbraith at 6:00 am
3 Comments

++++-
4.8 rating from 9 votes

Ubiquity: Quicksilver of the Firefox browser

Category: Firefox, Utility

Aza Raskin and the Mozilla Labs team have launched Ubiquity the command line tool that they have been talking about for awhile.

Ubiquity is "experiment into connecting the Web with language in an attempt to find new user interfaces that could make it possible for everyone to do common Web tasks more quickly and easily."

The overall goals of Ubiquity are to explore how best to:

  • Empower users to control the web browser with language-based instructions. (With search, users type what they want to find. With Ubiquity, they type what they want to do.)
  • Enable on-demand, user-generated mashups with existing open Web APIs. (In other words, allowing everyone–not just Web developers–to remix the Web so it fits their needs, no matter what page they are on, or what they are doing.)
  • Use Trust networks and social constructs to balance security with ease of extensibility.
  • Extend the browser functionality easily.

The screencast explains more:

What is cool about the system, is that it is a platform. If you fancy adding a new command, it is as easy as the following 'date' command:

JAVASCRIPT:
  1.  
  2. CmdUtils.CreateCommand({
  3.   name: "date",
  4.  
  5.   _date: function(){
  6.     var date = new Date();
  7.     return date.toLocaleDateString();
  8.   },
  9.  
  10.   preview: function( pblock ) {
  11.     var msg = 'Inserts todays date: "<i>${date}</i>"';
  12.     pblock.innerHTML = CmdUtils.renderTemplate( msg, {date: this._date()} );
  13.   },
  14.  
  15.   execute: function() {
  16.     CmdUtils.setSelection( this._date() );
  17.   }
  18. })
  19.  

Fancy a go? Download Ubiquity 0.1.

Posted by Dion Almaer at 3:00 am
6 Comments

++++-
4.8 rating from 13 votes

Using CSS to do the print watermark technique

Category: CSS, Tip

Andy Pemberton has put together a simple solution to get the watermark technique to work nicely with print CSS.

Check out the sample and pull up a print preview. He details the good, bad, and ugly:

The Good

The first step to getting a printable watermark is to use an inline tag, rather than background-images. In most browsers, background-images won’t be printed unless the user selects an additional browser option to enable it.

From here, we need to place the watermark image so that it is repeated on each page. It looks like the W3C thought of this ahead of time in the positioning module. The position property’s fixed value should ensure that a watermark is printed on each page. Some browsers, like FireFox and Internet Explorer 7, apply this rule correctly.

The Bad

Not all browsers play nicely with position:fixed and we haven’t yet considered using z-indexing to ensure the watermark displays behind all the regular page content. For IE and FireFox 3+, simply applying z-index:-1 to the watermark will do the trick.

Though, it turns out that earlier versions of FireFox (and other Gecko-based browsers) don’t play nicely with negative z-indexing. So, my current approach uses a few Gecko-specific hacks to degrade the watermark approach for FireFox 2. For FireFox 2, the watermark will display over the content, but still display on every page. Unfortunately, this means the CSS for my approach doesn’t validate.

The Ugly

But of course, position:fixed isn’t supported at all in IE6; it appears to ignore the rule altogether and display the image inline. So, I use conditional comments to filter just a *few* IE6-specific hacks:

Absolute positioning and an additional 100% height on the watermark and printable content are the keys to getting this working for IE6:

div.watermark{
position:absolute;
overflow:hidden;
}
div.content{
height:100%;

}