I recently released an update of the HTML5 / PhoneGap application I wrote a few months ago to the marketplace. This update fixed the location detection issue, where the marketplace certification process failed to detect that the application was using geolocation data via the browser (this issue has been fixed in PhoneGap 1.2), plus a few cosmetic improvements.

This application still exhibits a few UI quirks that make it fairly obvious that this is not a native application, the first is the gray box that appears whenever a link is clicked, the second is the overall responsiveness - there is a short delay between clicking on links / buttons and the application responding. It is this responsiveness that I would like to tackle in this blog post. This delay unfortunately gives the impression that your application is less responsive than it actually is!

So where does this delay come from?

Despite the rise in popularity of touch interfaces, much of the web is still designed for interaction with a mouse and keyboard, with the DOM exposing events for mouse-up, mouse-down and click user interactions. The Windows Phone 7 browser control captures touch interactions and translates these into the required mouse events, firing them on the DOM element being interacted with. However, the browser itself also needs to support gestures such as pan, pinch-zoom and double-tap zoom.

If we consider for example the pinch-zoom feature, where the user places two fingers on the screen and moves them apart in order to zoom in. It is highly unlikely they will place both fingers on the screen simultaneously. If, when the first finger was placed, a mouse-down event was fired on the relevant DOM element, there would be no way to 'cancel' this event when the user's second finger made contact with the screen shortly afterwards. For this reason the WP7 mobile browser does not properly support mouse-down / mouse-up events. You will find that mousedown, mouseup then click are always fired immediately in that order regardless of how long you hold your finger on the screen for. Clearly the need to add gesture support to the browser interferes with the way in which native touch events are translated into JavaScript mouse events.

Now consider the double-tap zoom feature, where the user taps twice in quick succession to zoom the web page by some fixed amount. If the DOM click event were fired on the first tap, this would cause a navigation to occur if an anchor element were tapped. This is certainly not the desired behaviour! Again, there is no way to 'cancel' an event once it is fired, so the only solution to this problem is, when the user first taps the screen, pause for a few hundred milliseconds to see if they tap a second time. Only after this pause is it safe to pass the interaction onto the DOM, firing a click event on the relevant DOM element.

Stock image courtesy of lovetheson

The need to handle gestures in the native layer causes numerous issues for the browser event handling. Thankfully these issues are relatively minor for most websites, but if your aim is to create a HTML-based application with a native look and feel, these issues become quite major.

Improving responsiveness

The problem I want to tackle in this blog post is that of the click DOM event firing delay to support the double-tap gesture. With HTML5-based applications you will typically want to disable pinch zoom, double-tap zoom and (optionally) scrolling as I described in a previous blog post. As a result your application has no need for the double-tap and pinch gestures and we are safe to short-circuit this behaviour, immediately raising DOM click events when the user taps the screen.

My solution follows a similar approach to the utility class I wrote for suppressing pan / zoom, where event handlers are added to the Border that sits within the WebBrowser control visual tree:

\-WebBrowser
  \-Border
    \-Border
      \-PanZoomContainer
        \-Grid
          \-Border (*)
            \-ContentPresenter
              \-TileHost

In order to forward Tap gestures to the browser, we can add event handlers to the Border element, then use InvokeScript to pass this onto the DOM rendered within the TileHost.

The JavaScript code to achieve this is very simple, the DOM exposes an elementFromPoint function which allows you to hit test the DOM. The IE9 DOM interface also exposes a non-standard click function which invokes any click event handlers, this will cause an anchor element to navigate. Putting it together, if you have an anchor element at (x=100, y=100), you can cause it to navigate using the following simple code:

document.elementFromPoint(100, 100).click()

Calling this JavaScript from our native C# code in response to Tap events is very easy. I have wrapped it up into a simple utility class:

/// <summary>
/// Captures mouse left button up events, triggering an immediate
/// click event on the clicked DOM element.
/// </summary>
public class BrowserFastClick
{
  private WebBrowser _browser;

  private Border _border;

  public BrowserFastClick(WebBrowser browser)
  {
    _browser = browser;
    browser.Loaded += new RoutedEventHandler(Browser_Loaded);
  }

  private void Browser_Loaded(object sender, RoutedEventArgs e)
  {
    // locate the element used to capture mouse events
    _border = _browser.Descendants<Border>().Last() as Border;
    _border.Tap += Border_Tap;
  }

  private void Border_Tap(object sender, GestureEventArgs e)
  {
    //determine the click location
    var pos = e.GetPosition(_border);

    // forward to the browser
    _browser.InvokeScript("eval",
      string.Format("document.elementFromPoint({0},{1}).click()", pos.X, pos.Y));
  }
}

To use it within a PhoneGap application, simply create an instance as follows:

new BrowserFastClick(PGView.Browser);

The net result is that your application becomes more responsive, with the browser immediately navigating links when they are first tapped.

I have updated the HTML5 SandwichFlow application I blogged about a few weeks ago to use this FastClick code. You can download the complete example here: HTML5SandwichFlow.zip

Regards, Colin E.