Browsers Blog Archive

Ghost Table Cells in IE9

What's this about ghosts?

I recently came across an arcane layout issue in my work on the new RJ Matthews site. The problem was specific to Internet Explorer 9 (IE9). The related CSS styles had been well tested and rendered consistently across a variety of browsers including IE7 and 8. Everything was fine and dandy until some new content was introduced into the page for a "Quickview" feature. While all of the other browsers continued to behave and render the page correctly, the layout would break in random and confusing ways in IE9. The following screenshots compare the correct layout with an example of the broken layout in IE9.

Correct grid layout:

Correct grid

Broken layout in IE9:

IE9 ghost cells

The Stage

The following is a list of the factors at work on the page in question:

  • Internet Explorer 9
  • Browser mode: IE9, Document mode: IE9 standards
  • Some content manipulation performed via JavaScript (and jQuery in this case)
  • Lots of table cells

Debugging

The page included a list of products. The first "page" of twelve results was shown initially while JavaScript split the rest of the list into several additional pages. Once this JavaScript pagination function was complete, users could cycle through products in bite-sized pieces.

My first thought was that the issue may be related to CSS or Javascript. I tested and debugged the styles thoroughly, tweaked styles and edited the underlying HTML structure to see if that might resolve the problem. I also tested the JavaScript and compared the original HTML with the parts which had been paginated via JavaScript. No dice.

When changes improved the paginated HTML, the bug appeared in the initial HTML. Other changes resolved the issue in the original HTML but it appeared in the paginated HTML.

I inspected the table with the Chrome Developer Tools console and also with the Developer Tools in IE9. There did not appear to be any differences between the rows which rendered properly and those which were skewed.

Bugging Out

At this point I began to research the issue and discovered it was a bug in the IE9 browser. This Microsoft forum post describes the issue and includes responses from Microsoft stating that it will not be fixed. It also includes a sample application which demonstrates the issue. I tested and verified that the problem has been addressed and fixed in Internet Explorer 10 thankfully.

This explained the many, seemingly random ways I had seen the grid break. At times the cells were squished and pushed to the left. This was because the ghost cell had been added at the end of a row. Other times the cells were shifted to the right (as seen in the screenshot above). In this case, the ghost cell had been added to the middle of a row.

The Fix

Further digging revealed the the issue was related to whitespace between table cells. The solution was fairly simple: use a regular expression to remove all whitespace between the table elements:

$('#problem-table').html(function(i, el) {
  return el.replace(/>\s*</g, '><');
});

With all of the whitespace removed from the affected <table>, IE9 rendered the page correctly.

Test Web Sites with Internet Explorer for Free

Browser Testing

While many Web Developers prefer to build sites and web applications with browsers like Chrome or Firefox it's important for us to keep an eye on the browser market share for all web users. Internet Explorer (IE) still owns a large piece of this pie and because of this, it is important to test sites and applications to ensure they work properly when viewed in IE. This poses a potential problem for developers who do not use Windows.

Although I use OS X on my desktop, I have Windows virtual machines with IE 6,7,8,9 and 10 installed. I also have a Linux virtual machine running Ubuntu so I can check out Chrome/Chromium and Firefox on that platform. In the past I had tried solutions like MultipleIEs but wasn't satisfied with them. In my experience I've found that the best way to see what users are seeing is to have a virtual machine running the same software they are.

I did some IE8 testing for a colleague a short time ago and suggested she should give VirtualBox a shot. Her response was "You should write a blog post about that!". So here we are.

Free Tools

VirtualBox is a free virtualization application similar to Parallels or VMWare. These applications aim to solve this problem by allowing us to run different "guest" operating systems on the "host" operating system of our choice. For example, I run several versions of IE and Windows on my computer which is running OS X.

Microsoft have begun in recent years to release Virtual PC (VPC) images for free to enable Web Developers to test their sites and applications with various versions of Internet Explorer. The most recent images were released on July 30, 2012 and you can check them out here. Although these images are created to run on Microsoft's Virtual PC application, with a little bit of effort we can also use them on Linux, OS X or Windows via VirtualBox.

For our purposes, we will be building a Windows 7 virtual machine running Internet Explorer 8. However, there are several images available with various Windows OS and Internet Explorer version combinations:

  • Windows 7 / IE8
  • Windows 7 / IE9
  • Windows Vista / IE7
  • Windows XP / IE6
There will likely be some differences creating virtual machines for the various images but the process will be similar to what we'll document here for Windows 7 / IE.

Linux and OS X Users

In researching this article I came across a very helpful project on GitHub. The ievms project by xdissent is a bash script that automates this process. I was able to run the script and run my newly created Windows 7 / IE virtual machine shortly afterward.

Requirements

To start we'll need to download and install VirtualBox. Be sure you have the most recent version available as it gets updated quite often. We'll also need to install the VirtualBox Extension Pack. Both are available on the VirtualBox Downloads page.

Because the VPC images are quite large (~2.6GB in our case) they have been split up into several files. For the Windows 7 / IE8 image we'll download the following files:

Once the files have been extracted we have created our virtual machine the disk will take up close to 10 GB. Make sure you have enough disk space available. I run my virtual machines on an external USB hard drive which works well if your local hard drive is starved for space.

Extract the VPC Files

Once you have all of the the VPC image files downloaded you'll need to extract them:
Windows users: double-click the Windows_7_IE8.part01.exe file
Linux / OS X users: You will need to install unrar. I did so on OS X with Homebrew and there are packages available for Ubuntu as well.

Once you have unrar installed, issue the following command in the directory containing all of the VPC image files:

`unrar e -y Windows_7_IE8.part01.exe`

This will extract the files and combine them into a single .vhd file. The -y flag tells unrar to say "yes" to the EULA from Microsoft so be aware of that.

Create a new Machine

Once the .vhd file is ready we can create a new machine in VirtualBox. Start up VirtualBox and click the "New" button:
Vbox new

Click "Continue" to proceed.

VM Name and OS Type

We'll enter a name (IE8 / Windows 7) and select "Microsoft" and "Windows 7" in the Operating System and Version drop-downs:
Vbox name and type
Click "Continue" to proceed.

Memory

Specify how much memory (RAM) you'd like to allocate to the virtual machine. This really depends on how much you have available but it is best to go with the minimum at the very least: Vbox mem config
Click "Continue" to proceed.

Disk

Select the "Do not add a virtual hard drive" option.: Vbox do not add hdd 1
The reason we skip this for now is because we want to add the hard disk as an IDE drive. For some reason VirtualBox adds the disk as a SATA drive which causes the blue screen of death (BSOD) to appear when the virtual machine is booted up.
Click "Continue" to proceed and click "Continue" again when presented with the warning about not attaching a hard disk.

Review Settings and Create the VM

At this point you can review the settings you've made and click "Create" to complete the process:
Vbox review and create

Configure Storage Settings

Highlight your newly created virtual machine and click the "Settings" button. From there, click the "Storage" tab and add a hard disk to the IDE controller: Vbox storage add hd

Absolute Pointing Device

In the Settings, click on the "System" tab and uncheck "Enable absolute pointing device". I had to do this in order to to get the mouse working properly.

Start it Up

We're almost there: next we need to start up the VM by clicking the "Start" button. You'll see Administrator and IEUser accounts and the password for both is "Password1". Log in as Administrator and check it out! It's important to note when the virtual machine has the keyboard and mouse focus. When you click inside the virtual machine window, it will capture the input from your mouse and keyboard. To return focus back to your computer you will need to press the "host key" sequence on your computer:
Vbox host key
The "host-key" sequence can be configured in the VirtualBox preferences. On my computer, the host-key sequence is the left CTRL key (displayed as Left ^). When the arrow icon is illuminated, the guest computer – Windows 7 in this case, has the focus. When I press the left CTRL key on my computer the arrow icon goes tray to indicate focus has been returned to my computer.

Activation

As soon as you log in you will be presented with the Windows Activation wizard. Follow the steps to activate this copy of Windows. Once that is complete you will be prompted to restart your computer. Go ahead and do that.
Vbox activate now
The screen resolution in the virtual machine will be quite low at first — I believe 800x600 is the default. Don't worry about this as it will be addressed in the next step.

Guest Additions

Log in as Administrator (Password1) and Install the VirtualBox Guest Additions. To do this, navigate to Devices in the VirtualBox menu on your computer (the "host" machine"). Choose the menu option to "Install Guest Additions". Each time you are prompted with the "Would you like to install..." dialog, choose "Install":
Vbox install guest additions

This will install some drivers optimized by VirtualBox which allow you to change the screen resolution, improve the interaction between your host computer and the Windows guest operating system. Once the install process is complete you will be prompted to restart your virtual machine once more.

Finished Product

With Windows activated and the Guest Additions installed you should be able to log in to your very own Windows 7 / IE8 testing machine!
Vbox ie8 mr

This free copy of Windows will operate for 30 days and the trial period can be extended twice by running the following at the command prompt:

slmgr –rearm

Check out the documentation at Microsoft for more details.

Code School: Journey into Mobile Review

Yesterday, I took the Journey into Mobile course over at Code School, an online training program with a handful of web technology courses. I just started a large mobile project for Paper Source, so this was good timing. Because a paid membership to Code School is required to participating in the training, I won't share too many of the course details here. But I'll share a few tidbits that will hopefully interest you in taking the course or learning more about mobile development.

The course was divided into 5 high level lessons (or levels):

  • Relative Font Size
  • Fluid Layouts
  • Adaptive Design
  • Responsiveness Adventures
  • Responsive Media

The first two topics covered working with proportional font sizes and content regions, and how to convert your existing layout to proportions (percentage or ems) to create a fluid layout which included proportional font sizes. In the Adaptive Design lesson, the CSS3 supported @media query was introduced. I've used the media query on the responsive design for The Best Game Apps and will be using it for Paper Source. Some examples of @media queries include:

@media (min-width: 655px) and (max-width: 1006px) {
  # styles specific to browser width 655-1006 pixels
}
@media only screen and (device-width: 768px) {
  # styles specific to browser width 768 pixels
}
@media (min-device-width: 320px) and (max-device-width: 655px) {
  # styles specific to browser width 320-655 pixels
}
@media (min-device-width: 450px) and (orientation:landscape) {
  # styles specific to browser width 450 pixels and landscape orientation
}

For each of the above @media queries, specific "break points" are determined to adjust styles as certain elements break as the browser width changes. For example, if elements begin to overlap as the screen narrows, the browser width at which this begins to happen is one break point, and new styles are defined for that width.

The last two levels of the training course covered Responsiveness Adventures and Responsiveness Media. Responsive design also leverages the @media query to design responsively for changing browser widths. One interesting topic covered in the Responsive Media lesson was how Retina Images are addressed on devices where the pixel density is 1.5-2 times regular pixel density. This was a topic I hadn't come across in mobile development. The lesson presented a couple of options for dealing with Retina images, including use of the @media query and picture HTML tag.

Overall, it was a decent course with a good overview. I would recommend it to anyone planning to get involved in mobile development.

UTOSC 2012 talks of interest

It's been two weeks now since the Utah Open Source Conference for 2012. My fellow End Pointers wrote previously about it: Josh Ausborne about the mini Liquid Galaxy we set up there for everyone to play with, and Josh Tolley with a write-up of his talks on database constraints and KML for geographic mapping markup.

There were a lot of interesting talks planned, and I could only attend some of them. I really enjoyed these:

  • Rob Taylor on AngularJS
  • Brandon Johnson on Red Hat's virtualization with oVirt, Spacewalk, Katello, and Aeolus
  • Clint Savage about RPM packaging with Mock & Koji
  • Daniel Evans on testing web applications with Capybara, embedded WebKit, and Selenium (which End Pointer Mike Farmer wrote about here back in December)
  • Aaron Toponce on breaking full-disk encryption (I missed this talk, but learned about it from Aaron in the hallway track and his slides afterwards)
  • Matt Harrison's tutorial Hands-on intermediate Python, covering doctest, function parameters and introspection, closures, function and class decorators, and more.

I gave a talk on GNU Screen vs. tmux, which was fun (and ends with a live demo that predictably fell apart, and audience questions that you can't hear on the recording). Here's the video:

Follow this Direct YouTube link in case the embedded version doesn't work for you. And here are the presentation slides.

I have a bit more to cover from the conference later!

Three Things: Rails, JOIN tip, and Responsiveness

Here's another entry in my Three Things series, where I share a few small tips I've picked up lately.

1. Rails and Dramas

Sometimes I think that since Rails allows you write code efficiently, [a few] members of the Rails community have time to overdramatize incidents that otherwise would go relatively unnoticed :) Someone with a good sense of humor created this website to track these dramas. While it's probably a waste of time to get caught up on the personal aspects of the drama, some of the dramas have interesting technical aspects which are fiercely defended.

2. JOIN with concat

Recently I needed to perform a JOIN on a partial string match in MySQL. After some investigation, I found that I had use the CONCAT method in a conditional (in an implicit inner JOIN), which looked like this:

SELECT * FROM products p, related_items ri WHERE concat(p.sku, '%') = ri.id

In modern MVC frameworks with ORMs, databases are typically not designed to include data associations in this manner. However, in this situation, data returned from a third party service in a non-MVC, ORM-less application was only a substring of the original data. There may be alternative ways to perform this type of JOIN, and perhaps my fellow database experts will comment on the post with additional techniques ;)

3. Responsiveness

Responsive web design is all the rage lately from the increase in mobile web browsing and tablets. Here is a fun tool that that the ecommerce director at Paper Source pointed out to me recently. The website allows you to render a single URL at various widths for a quick review of the UI.

Using nginx to transparently modify/debug third-party content

In tracking down a recent front-end bug for one of our client sites, I found myself needing to use the browser's JavaScript debugger for stepping through some JavaScript code that lived in a mix of domains; this included a third-party framework as well as locally-hosted code which interfaced with -- and potentially interfered with -- said third-party code. (We'll call said code foo.min.js for the purposes of this article.) The third-party code was a feature that was integrated into the client site using a custom domain name and was hosted and controlled by the third-party service with no ability for us to change directly. The custom domain name was part of a chain of CNAMEs which eventually pointed to the underlying *actual* IP of the third-party service, so their infrastructure obviously relied on getting the Host header correctly in the request to select which among many clients was being served.

It appeared as if there was a conflict between code on our site and that imported by the third party service. As part of the debugging process, I was stepping through the JavaScript in order to determine what if any conflicts there were, as well as their nature (e.g., conflicting library definitions, etc.). Stepping through our code was fine, however the third-party's JS code was (a) unfamiliar, and (b) minified, so this had the effect of putting all of the JavaScript code more-or-less on one line, which made tracing through the code in the debugger much less useful than I had hoped.

My first instinct was to use a JavaScript beautifier to reverse the minification process, but since I had no control over the code being included from the third-party service, this did not seem to be directly feasible. The third-party code was deployed only on our production site and relied on hard-coded domains which would make integrating it into one of our development instances challenging since we had no control over the contents of the returned resources. Since the relevant feature (and subsequent bugs) was only on the production site, making extensive modifications to how things were done and potentially breaking that or other features for users while I was debugging was obviously out as an option.

Enter nginx. I've been doing a lot with nginx lately as far as using it as a reverse proxy cache, so it's been on my mind lately. So I came up with this technique:

  1. Look up the IP address for the third-party's domain name (used for later purposes).
  2. Install nginx on localhost, listening to port 80.
  3. Modify /etc/hosts to point the third-party's domain name to the nginx server's IP (also localhost in this case).
  4. Configure a new virtual host with the following logical constraints:
    • We want to serve specific files (the beautified JavaScript) from our local server.
    • We want any other request going through that domain to be passed-through transparently, so neither the browser nor the third-party server treat it differently.

Given these constraints, this is the minimal configuration that I came up with (the interesting parts are located in the server block):

/etc/hosts:

example.domain.com 127.0.0.1

nginx.conf:

worker_processes 1;

events {
    worker_connections 10;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    
    server {
        server_name example.domain.com;
        root /path/to/local_root;

        try_files $uri @proxied;

        location @proxied {
            proxy_set_header Host $http_host;
            proxy_pass http://1.2.3.4;
        }
    }
}

Once I had the above configured/setup, I downloaded/saved the foo.min.js file from the third-party service, ran it through a JS beautifier, and saved it in the local nginx's cache root so it would be served up instead of the actual file from the third-party service. Any other requests for static resources (images, other scripts, etc) would pass-through to the third-party server, so I had my nicely-formatted JavaScript code to step through, the production site worked as normal for anyone else despite potential local changes to the file on my end (i.e., adding JavaScript alert() calls to the file, and no one was the wiser.

A few notes

The try_files directive instructs nginx to first look for a file named after the current URI (foo.min.js in our example) in our local cache, and if this is not found, then fallback to the proxied location block; i.e., relay the request to the original upstream server. We explicitly set the Host header on the proxy request because we want the request to behave normally with respect to name-based hosting, and provide the saved IP address to contact the server in question.

We only needed to preserve/lookup the upstream server's IP address because we're running the nginx server on localhost, so if we used a domain name the lookup would return the same IP defined in /etc/hosts; if the nginx server was running on a different machine, you would be able to just use the domain name as both the server_name and the proxy_pass parameters and set the entry for the host in your local /etc/hosts file to the IP of the nginx server.

A possible extension would be to detect when an upstream request matched a minified URL (via a location ~ \.min\..*\.js$ block) and automatically beautify/cache the content in our local cache. This could be accomplished via the use of an external FastCGI script to retrieve, post-process, and cache the content.

This technique can also be used when dealing with testing changes to a production site on which you are unable or unwilling to make potentially disruptive changes for the purposes of testing static resources. (JavaScript seems the most obvious application here, but this could apply to serving up images or other static content which would be resolvable by the local cache.)

I always need to remind myself to undo changes to /etc/hosts as soon as I'm done testing when using tricks like these. Particularly in something like this which is more-or-less transparent, the behavior would be functionaly identical as long as code/scripts on the third-party site stayed the same, but could easily introduce subtle bugs if the third-party services made changes to their codebase. Since our local copies would mask any remote changes for those non-proxied resources, this could be very confusing if you forget that things are set up this way.

Browser popularity

It's no secret that Internet Explorer has been steadily losing market share, while Chrome and Safari have been gaining.

But in the last couple of years I've been surprised to see how strong IE has remained among visitors to our website -- it's usually been #2 after Firefox.

Recently this has changed and IE has dropped to 4th place among our visitors, and Chrome now has more than double the users that Safari does, as reported by Google Analytics:

1. Firefox 43.61%
2. Chrome 30.64%
3. Safari 11.49%
4. Internet Explorer 11.02%
5. Opera 2.00%

That's heartening. :)

Cross Browser Development: A Few CSS and JS Issues

Coding cross browser friendly JavaScript and CSS got you down? In a recent project, Ron, David, and I worked through some painful cross browser issues. Ron noted that he even banged his head against the wall over a couple of them :) Three of these issues come up frequently in my other projects full of CSS and JS development, so I wanted to share.

Variable Declaration in JS

In several cases, I noticed that excluding variable declaration ("var") resulted in broken JavaScript-based functionality in IE only. I typically include variable declaration when I'm writing JavaScript. In our project, we were working with legacy code and conflicting variable names may have be introduced, resulting in broken functionality. Examples of before and after:

Bad Better
var display_cart_popup = function() {
    popup_id = '#addNewCartbox';
    left = (parseInt($(window).width()) - 772) / 2;
    ...
};
var display_cart_popup = function() {
    var popup_id = '#addNewCartbox';
    var left = (parseInt($(window).width()) - 772) / 2;
    ...
};
...
address_display = '';

country = $(type+'_country').value;
address = $(type+'_address').value;
address2 = $(type+'_address2').value;
city = $(type+'_city').value;
state = $(type+'_state').value;
zip = $(type+'_zip').value;
...
...
var address_display = '';

var country = $(type+'_country').value;
var address = $(type+'_address').value;
var address2 = $(type+'_address2').value;
var city = $(type+'_city').value;
var state = $(type+'_state').value;
var zip = $(type+'_zip').value;
...

I researched this to gain more insight, but I didn't find much except a reiteration that when you create variables without the "var" declaration, they become global variables which may have resulted in conflicts. However, all the "learning JavaScript" documentation I browsed through includes variable declaration and there's no reason to leave it out for these lexically scoped variables.

Trailing Commas in JSON objects

According to JSON specifications, trailing commas are not permitted (e.g obj = { "1" : 2, }). From my experience, JSON objects with trailing commas might work in Firefox and WebKit browsers, but it dies silently in IE. Some recent examples:

Bad Better

//JSON response from an ajax call
// if $add_taxes is not true, the carttotal element will be the last element of the list and it will end with a comma

{
  "response_message"    : '<?= $response_message ?>',
  "subtotal"            : <?= $subtotal ?>, 
  "shipping_cost"       : <?= $shipping ?>, 
  "carttotal"           : <?= $carttotal ?>, 
<?php if($add_taxes) { ?>
  "taxes"               : <?= $taxes ?>
<?php } ?>
}

//JSON response from an ajax call
//No matter the value of $add_taxes, the carttotal element is the last element and it does not end in a comma

{
  "response_message"    : '<?= $response_message ?>',
  "subtotal"            : <?= $subtotal ?>, 
  "shipping_cost"       : <?= $shipping ?>,  
<?php if($add_taxes) { ?>
  "taxes"               : <?= $taxes ?>,
<?php } ?>
  "carttotal"           : <?= $carttotal ?>
}

//Page load JSON object defined
//Last element in array will end in a comma

var fonts = {
[loop list=`$Scratch->{fonts}`]
    '[loop-param name]' : {
      'bold' : "[loop-param bold]",
      'italic' : "[loop-param italic]"
    },[/loop]
};

//Page load JSON object defined
//A dummy object is appended to the fonts JSON object
//Additional logic is added elsewhere to determine if the object is a "dummy" or not

var fonts = {
[loop list=`$Scratch->{fonts}`]
    '[loop-param name]' : {
      'bold' : "[loop-param bold]",
      'italic' : "[loop-param italic]"
     },[/loop]
    'dummy' : {}
};

Additional solutions to avoid the trailing comma include using join (Perl, Ruby) or implode (PHP), conditionally excluding the comma on the last element of the array, or using library methods to serialize data to JSON.

Floating Elements in IE

Often times, you'll get a design like the one shown below. There will be a static width and repeating components to span the entire width. You may programmatically determine how many repeating elements will be displayed, but using CSS floating elements yields the cleanest code.


Example of a given design with repeating elements to span a static width.

You start working in Chrome or Firefox and apply the following CSS rules:


CSS rules for repeating floating elements.

When you think you're finished, you load the page in IE and see the following. Bummer!


Floating elements wrap incorrectly in IE.

This is a pretty common scenario. In IE, if the combined widths of consecutive floating elements is greater than or equal to 100% of the available width, the latter floating element will jump down based on the IE float model. Instead of using floating elements, you might consider using tables or CSS position rules, but my preference is to use tables only for elements that need vertical align settings and to stay away from absolute positioning completely. And I try to stay away from absolute positioning in general.

The simplest and minimalist change I've found to work can be described in a few steps. Let's say your floating elements are <div>'s inside a <div> with an id of "products":

<div id="products">
  <div class="product">product 1</div>
  <div class="product">product 2</div>
  <div class="product" class="last">product 3</div>
  <div class="product">product 4</div>
  <div class="product">product 5</div>
  <div class="product" class="last">product 6</div>
</div>

And let's assume we have the following CSS:

<style>
div#products { width: 960px; }
div.product { float: left; width: 310px; margin-right: 15px; height: 100px; }
div.last { margin-right: 0px; }
</style>

Complete these steps:

  • First, add another div to wrap around the #products div, with an id of "outer_products"
  • Next, update the 'div#products' width to be greater than 960 pixels by several pixels.
  • Next, add a style rule for 'div#outer_products' to have a width of "960px" and overflow equal to "hidden".

Yielding:

<div id="outer_products">
  <div id="products">
    <div class="product">product 1</div>
    <div class="product">product 2</div>
    <div class="product" class="last">product 3</div>
    <div class="product">product 4</div>
    <div class="product">product 5</div>
    <div class="product" class="last">product 6</div>
  </div>
</div>

And:

<style>
div#outer_products { width: 960px; overflow: hidden; }
div#products { width: 980px; }
div.product { float: left; width: 310px; margin-right: 15px; height: 100px; }
div.last { margin-right: 0px; }
</style>

The solution is essentially creating a "display window" (outer_products), where overflow is hidden, but the contents are allowed to span a greater width in the inside <div> (products).


The white border outlines the outer_products "display window".

Some other issues that I see less frequently include the double-margin IE6 bug, chaining CSS in IE, and using '#' vs. 'javascript:void(0);'.