February 2014

Volume 29 Number 2

WinJS on Windows 8.1 : Build More Efficient Windows Store Apps Using JavaScript: Performance

Eric Schmidt

In exploring howto build more efficient Windows Store apps, I first looked at error handling. In this second article, I’ll examine several techniques for improving a Windows Store app’s performance, focusing on memory usage and HTML UI responsiveness. I’ll intro­duce the new predictable object lifecycle model in the Windows Library for JavaScript on Windows 8.1 (WinJS 2.0). Then, I’ll examine Web Workers and the new Scheduler API in WinJS 2.0, both of which complete background tasks without locking the UI. As in the previous article, I’ll present both diagnostic tools for finding the problems and solutions for addressing the issues uncovered.

I’ll assume you’re fairly familiar with building Windows Store apps using JavaScript. If you’re relatively new to the platform, I suggest beginning with the basic “Hello World” example (bit.ly/vVbVHC) or, for more of a challenge, the “Hilo” sample for JavaScript (bit.ly/SgI0AA). If you haven’t read the previous article, you can find it at msdn.microsoft.com/magazine/dn519922.

Setting up the Example

Throughout this article, I draw on specific examples you can test in your own code. You can follow along or download the complete code to peruse at your leisure.

I’m using different test cases than in the previous article, so you’ll want to add some new buttons to the global NavBar if you’re following along. (You can just start a brand-new Navigation app project if you prefer—that works, too.) The new NavBarCommands are shown in Figure 1.

Figure 1 Additional NavBarCommands in Default.html

<div data-win-control="WinJS.UI.NavBar">
  <div data-win-control="WinJS.UI.NavBarContainer">
    <!-- Other NavBarCommand elements. -->
    <div id="dispose"
      data-win-control="WinJS.UI.NavBarCommand"
      data-win-options="{
        location: '/pages/dispose/dispose.html',
        icon: 'delete',
        label: 'Dispose pattern in JS'
    }">
    </div>
    <div id="scheduler"
      data-win-control="WinJS.UI.NavBarCommand"
      data-win-options="{
        location: '/pages/scheduler/scheduler.html',
        icon: 'clock',
        label: 'Scheduler'
    }">
    </div>
    <div id="worker"
      data-win-control="WinJS.UI.NavBarCommand"
      data-win-options="{
        location: '/pages/worker/worker.html',
        icon: 'repair',
        label: 'Web worker'
    }">
    </div>
  </div>
</div>

For these test cases, I use the more realistic scenario of an app that surfaces content from the Web. This app fetches data from the United States Library of Congress Print & Photographs Online Catalog Web service (1.usa.gov/1d8nEio). I’ve written a module that wraps calls to the Web service in promise objects and defines classes for storing the received data. Figure 2shows the module, in a file titled searchLOC.js (/js/searchLOC.js).

Figure 2 Accessing the Print & Photographs Online Catalog Web Service

(function () {
  "use strict";
  var baseUrl = "https://loc.gov/pictures/"
  var httpClient = new Windows.Web.Http.HttpClient();
  function searchPictures(query) {
    var url = baseUrl + "search/?q=" + query + "&fo=json";
    var queryURL = encodeURI(url);
    return httpClient.getStringAsync(
      new Windows.Foundation.Uri(queryURL)).
      then(function (response) {
        return JSON.parse(response).results.map(function (result) {
          return new SearchResult(result);
        });
     });
  }
  function getCollections() {
    var url = baseUrl + "?fo=json";
    return httpClient.getStringAsync(new Windows.Foundation.Uri(url)).
      then(function (response) {
         return JSON.parse(response).featured.
           map(function (collection) {
             return new Collection(collection);
         });
      });
  }
  function getCollection(collection) {
    var url = baseUrl + "search/?co=" + collection.code + "&fo=json";
    var queryUrl = encodeURI(url);
    return httpClient.getStringAsync(new Windows.Foundation.Uri(queryurl)).
      then(function (response) {
        collection.pictures = JSON.parse(response).
          results.map(function (picture) {
            return new SearchResult(picture);
        });
        return collection;
      });
  }
  function Collection(info) {
    this.title = info.title;
    this.featuredThumb = info.thumb_featured;
    this.code = info.code;
    this.pictures = [];
  }
  function SearchResult(data) {
    this.pictureThumb = data.image.thumb;
    this.title = data.title;
    this.date = data.created_published_date;
  }
  WinJS.Namespace.define("LOCPictures", {
    Collection: Collection,
    searchPictures: searchPictures,
    getCollections: getCollections,
    getCollection: getCollection
  });
})();

Remember to link to the searchLOC.js file from default.html at the root of your project before you try to call into it.

Disposing of Objects

In JavaScript, an object remains in memory as long as it can be reached through a lexical environment or chain of references. Once all references to the object have been removed, the Garbage Collector de-allocates memory from the object. As long as a reference to the object remains, the object stays in memory. A memory leak occurs if a reference to an object (and therefore the object itself) remains beyond when it’s needed.

One common cause of memory leaks in JavaScript applications are “zombie” objects, which typically occur when a JavaScript object references a DOM object and that DOM object is removed from the document (through a call to removeChild or innerHTML). The corresponding JavaScript object remains in memory, even though the corresponding HTML has vanished:

var newSpan = document.createElement("span");
document.getElementById("someDiv").appendChild(newSpan);
document.getElementById("someDiv").innerHTML = "";
WinJS.log && WinJS.log(newSpan === "undefined");
// The previous statement outputs false to the JavaScript console.
// The variable "newSpan" still remains even though the corresponding
// DOM object is gone.

For a normal Web page, the life of an object extends only for as long as the browser displays the page. Windows Store apps can’t ignore these sorts of memory leaks. Apps commonly use a single HTML page as a content host, where that page persists throughout the app session (which could last for days, or even months). If an app changes state (the user navigates from one page to another, for example, or a ListView control is scrolled so that some items fall out of visibility) without cleaning up memory allocated to unneeded JavaScript objects, that memory can become unavailable to the app.

Checking for Memory Leaks

Luckily, Visual Studio 2013 has new features that can help developers track down memory leaks—in particular the Performance and Diagnostics window. For this test case and the next, I’ll demonstrate a couple of the tools that it surfaces.

For this test case, I’ll add a custom control to my solution that purposely allows memory leaks. This control, named SearchLOCControl (/js/SearchLOCControl.js), creates a search text box and then displays results after a response to a query has been received. Figure 3 shows the code for SearchLOCControl.js. Again, remember to link to this new JavaScript file from default.html.

Figure 3 Custom SearchLOCControl

(function () {
  "use strict";
  WinJS.Namespace.define("SearchLOCControl", {
    Control: WinJS.Class.define(function (element) {
      this.element = element;
      this.element.winControl = this;
      var htmlString = "<h3>Library of Congress Picture Search</h3>" +
        "<div id='searchQuery' data-win-control='WinJS.UI.SearchBox'" +
          "data-win-options='{ placeholderText: \"Browse pictures\" }'></div>" +
          "<br/><br/>" +
          "<div id='searchResults' class='searchList'></div>" +
          "<div id='searchResultsTemplate'" +
            "data-win-control='WinJS.Binding.Template'>" +
            "<div class='searchResultsItem'>" +
              "<img src='#' data-win-bind='src: pictureThumb' />" +
              "<div class='details'>" +
                "<p data-win-bind='textContent: title'></p>" +
                "<p data-win-bind='textContent: date'></p>" +
              "</div>" +
            "</div>"+
        "</div>";
   // NOTE: This is an unusual technique for accomplishing this
   // task. The code here is written for extreme brevity.       
      MSApp.execUnsafeLocalFunction(function () {
        $(element).append(htmlString);
        WinJS.UI.processAll();
      });
      this.searchQuery = $("#searchQuery")[0];
      searchQuery.winControl.addEventListener("querysubmitted", this.submitQuery);
      }, {
        submitQuery: function (evt) {
          var queryString = evt.target.winControl.queryText;
          var searchResultsList = $("#searchResults")[0];
          $(searchResultsList).append("<progress class='win-ring'></progress>");
          if (queryString != "") {
            var searchResults = LOCPictures.searchPictures(queryString).
              then(function (response) {
                var searchList = new WinJS.Binding.List(response),
                  searchListView;
                if (searchResultsList.winControl) {
                  searchListView = searchResultsList.winControl;
                  searchListView.itemDataSource = searchList.dataSource;
                }
                else {
                  searchListView = new WinJS.UI.ListView(searchResultsList, {
                    itemDataSource: searchList.dataSource,
                    itemTemplate: $("#searchResultsTemplate")[0],
                    layout: { type: WinJS.UI.CellSpanningLayout}
                  });
                }
                WinJS.UI.process(searchListView);
             });
           }
         }
      })
   })
})();

Note that I use jQuery to build my custom control, which I add to my solution using the NuGet Package Manager. Once you’ve downloaded the NuGet package to your solution, you’ll need to manually add a reference to the jQuery library in default.html.

The SearchLOCControl relies on some styling I’ve added to default.css (/css/default.css), which is shown in Figure 4.

Figure 4 Styling Added to Default.css

.searchList {
  height: 700px !important;
  width: auto !important;
}
.searchResultsItem {
  display: -ms-inline-grid;
  -ms-grid-columns: 200px;
  -ms-grid-rows: 150px 150px
}
  .searchResultsItem img {
    -ms-grid-row: 1;
    max-height: 150px;
    max-width: 150px;
  }
  .searchResultsItem .details {
    -ms-grid-row: 2;
  }

Now I add a new page control named dispose.html (/pages/­dispose/dispose.html) to the solution and add the following HTML markup inside the <section> tag of dispose to create the custom control:

<button id="dispose">Dispose</button><br/><br/>
<div id="searchControl" data-win-control="SearchLOCControl.Control"></div>

Finally, I add code to the PageControl.ready event handler in the dispose.js file (/pages/dispose/dispose.js) that naively destroys the control and creates a memory leak by setting the innerHTML of the control’s host <div> to an empty string, as shown in Figure 5.

Figure 5 Code in Dispose.js to "Destroy” the Custom Control

(function () {
  "use strict";
  WinJS.UI.Pages.define("/pages/dispose/dispose.html", {
    ready: function (element, options) {
      WinJS.UI.processAll();
      $("#dispose").click(function () {
        var searchControl = $("#searchControl")[0];
        searchControl.innerHTML = "";
      });
    }
  // Other page control code.
  });
})();

Now I can test the control’s memory usage. The Performance and Diagnostics window provides several tools for measuring the performance of a Windows Store app, including CPU sampling, app energy consumption, UI responsiveness and JavaScript function timing. (You can read more about these tools on the Visual Studio team’s blog at bit.ly/1bESdOH.) If it’s not already visible, you’ll need to open the Performance and Diagnostics pane, either through the Debug menu (Visual Studio Express 2013 for Windows) or through the Analyze menu (Visual Studio Professional 2013 and Visual Studio Ultimate 2013).

For this test, I use the JavaScript memory monitoring tool. Here are the steps for performing the test:

  1. In the Performance and Diagnostics window, select JavaScript Memory and then click Start. The project then runs in debugging mode. If prompted by a User Account Control dialog box, click Yes.
  2. With the app project running, navigate to the dispose page, then switch to the desktop. In Visual Studio, in the current diagnostic session (a tab titled “Report*.diagsession”), click Take Heap Snapshot.
  3. Switch back to the running app. In the search box, enter a query (for example, “Lincoln”) and then press Enter. A ListView control appears that displays the image search results.
  4. Switch back to the desktop. In Visual Studio, in the current diagnostic session (a tab titled “Report*.diagsession”), click Take Heap Snapshot.
  5. Switch back to the running app. Click the Dispose button. The custom control disappears from the page.
  6. Switch back to the desktop. In Visual Studio, in the current diagnostic session (a tab titled “Report*.diagsession”) click Take Heap Snapshot and then click Stop. There are now three snapshots listed in the diagnostic session, as shown in Figure 6.

Memory Usage Before Implementing the Dispose Pattern
Figure 6 Memory Usage Before Implementing the Dispose Pattern

With the diagnostics data in hand, I can analyze the memory usage of the custom control. From a quick glance at the diagnostics session, I suspect that “disposing” the control didn’t free up all of the memory associated with it.

In the report, I can examine the JavaScript objects on the heap for each snapshot. I want to know what remained in memory after the custom control was removed from the DOM. I’ll click the link associated with the number of objects on the heap in the third snapshot (Snapshot #3 in Figure 6).

First I’ll look at the Dominators view, which shows a sorted list of the objects by retained size. The objects consuming the most memory that are potentially easiest to free are listed at the top. In the Dominators view, I see a reference to the <div> with an id value “searchControl.” When I expand it, I see that the search box, ListView and data associated with it are all in memory.

When I right-click the row for the searchControl <div> and select Show in root view, I see the event handlers for the button clicks are still in memory, too, as Figure 7 shows.

Unattached Event Handler Code Taking up Memory
Figure 7 Unattached Event Handler Code Taking up Memory

Thankfully, I can fix this easily with only a few changes to my code.

Implementing the Dispose Pattern in WinJS

In WinJS 2.0, all of the WinJS controls implement a “dispose” pattern to address the issue of memory leaks. Whenever a WinJS control falls out of scope (for example, when the user navigates to another page), WinJS cleans up all references to it. The control is marked for disposal, meaning that the internal Garbage Collector knows to release all the memory allocated to the object.

The dispose pattern in WinJS has three important characteristics that a control must provide in order to be properly disposed:

  • The top-level container DOM element must have the CSS class “win-disposable.”
  • The control’s class must include a field called _disposed that is initially set to false. You can add this member to a control (along with the win-disposable CSS class) by calling WinJS.Utilities.markDisposable.
  • The JavaScript class that defines the control must expose a “dispose” method. In the dispose method:
    • All memory allocated to objects associated with the control needs to be released.
    • All event handlers need to be detached from the child DOM objects.
    • All children of the control must have their dispose methods called. The best way to do this is by calling WinJS.Utilities.disposeSubTree on the host element.
    • All outstanding promises that might be referenced within the control need to be canceled (by calling the Promise.cancel method and then nulling the variable out).

So, in the constructor function for SearchLOCControl.Control, I add the following lines of code:

this._disposed = false;
WinJS.Utilities.addClass(element, "win-disposable");

Next, inside the SearchLOCControl class definition (the call to WinJS.Class.define), I add a new instance member named dispose. Here’s the code for the dispose method:

dispose: function () {
  this._disposed = true;
  this.searchQuery.winControl.removeEventListener("querysubmitted",
    this.submitQuery);
  WinJS.Utilities.disposeSubTree(this.element);
  this.searchQuery = null;
  this._element.winControl = null;
  this._element = null;
}

Generally speaking, you don’t need to clean up any variable in your code that’s entirely contained within the code for the element. When the code for the control goes away, so do all of the internal variables. However, if the code for the control references something outside of itself—such as a DOM element, for example—then that reference will need to be nulled out.

Finally, I add an explicit call to the dispose method in dispose.js (/pages/dispose/dispose.js). Here’s the updated click event handler for the button in dispose.html:

$("#dispose").click(function () {
  var searchControl = $("#searchControl")[0];
  searchControl.winControl.dispose();
  searchControl.innerHTML = "";
});

Now when I run the same JavaScript memory test, the diagnostics session looks much better (see Figure 8).

Memory Usage After Implementing Dispose
Figure 8 Memory Usage After Implementing Dispose

Examining the memory heap, I can see the “searchControl” <div> no longer has child elements associated with it (see Figure 9). None of the sub controls remain in memory and the associated event handlers are gone, too (see Figure 10).

Dominators View After Implementing Dispose
Figure 9 Dominators View After Implementing Dispose

Roots View After Implementing Dispose
Figure 10 Roots View After Implementing Dispose

Improving Responsiveness: Scheduler and Web Workers

Apps can become unresponsive when the UI is waiting to be updated based on an external process. For example, if an app makes multiple requests to a Web service in order to populate a UI control, the control—the entire UI, for that matter—can get stuck while waiting on the requests. This can cause the app to stutter or seem unresponsive.

To demonstrate this, I create another test case where I populate a Hub control with the “featured collections” provided by the Library of Congress Web service. I add a new Page Control named scheduler.html for the test case to my project (/pages/scheduler/scheduler.js). In the HTML for the page, I declare a Hub control that contains six HubSection controls (one for each featured collection). The HTML for the Hub control inside the <section> tags in scheduler.html is shown in Figure 11.

Figure 11 Hub and HubSection Controls Declared in Scheduler.html

<div id="featuredHub" data-win-control="WinJS.UI.Hub">
  <div data-win-control="WinJS.UI.HubSection"
    data-win-options="{
      header: 'Featured Collection 1'
    }"
    class="section">
  </div>
  <div data-win-control="WinJS.UI.HubSection"
    data-win-options="{
      header: 'Featured Collection 2'
    }"
    class="section">
  </div>
  <div data-win-control="WinJS.UI.HubSection"
    data-win-options="{
      header: 'Featured Collection 3'
    }"
    class="section">
  </div>
  <div data-win-control="WinJS.UI.HubSection"
    data-win-options="{
      header: 'Featured Collection 4'
    }"
    class="section">
  </div>
  <div data-win-control="WinJS.UI.HubSection"
    data-win-options="{
      header: 'Featured Collection 5'
    }"
    class="section">
  </div>
  <div data-win-control="WinJS.UI.HubSection"
    data-win-options="{
      header: 'Featured Collection 6'
    }"
    class="section">
  </div>
</div>

Next, I get the featured collections data from the Web service. I’ll add a new file named data.js to my solution (/js/data.js) that calls the Web service and returns a WinJS.Binding.List object. Figure 12 shows the code for getting the featured collections data. Again, remember to link to data.js from default.html.

Figure 12 Getting the Data from the Web Service

(function () {
  "use strict";
  var data = LOCPictures.getCollections().
  then(function (message) {
    var data = message;
    var dataList = new WinJS.Binding.List(data);
    var collectionTasks = [];
    for (var i = 0; i < 6; i++) {
      collectionTasks.push(getFeaturedCollection(data[i]));
    }
    return WinJS.Promise.join(collectionTasks).then(function () {
      return dataList;
    });
  });
  function getFeaturedCollection(collection) {
    return LOCPictures.getCollection(collection);
  }
 WinJS.Namespace.define("Data", {
   featuredCollections: data
 });
})();

Now I need to insert the data into the Hub control. In the scheduler.js file (/pages/scheduler/scheduler.js), I’ll add some code to the PageControl.ready function and define a new function, populateSection. The complete code is shown in Figure 13.

Figure 13 Populating the Hub Control Dynamically

(function () {
  "use strict";
  var dataRequest;
  WinJS.UI.Pages.define("/pages/scheduler/scheduler.html", {
    ready: function (element, options) {
      performance.mark("navigated to scheduler");
      dataRequest = Data.featuredCollections.
        then(function (collections) {
          performance.mark("got collection");
          var hub = element.querySelector("#featuredHub");
            if (!hub) { return; }
            var hubSections = hub.winControl.sections,
            hubSection, collection;
            for (var i = 0; i < hubSections.length; i++) {
              hubSection = hubSections.getItem(i);
              collection = collections.getItem(i);
              populateSection(hubSection, collection);
            }
        });
    },
    unload: function () {
      dataRequest.cancel();
    }
    // Other PageControl members ...
  });
  function populateSection(section, collection) {
    performance.mark("creating a hub section");
    section.data.header = collection.data.title;
    var contentElement = section.data.contentElement;
    contentElement.innerHTML = "";
    var pictures = collection.data.pictures;
    for (var i = 0; i < 6; i++) {
      $(contentElement).append("<img src='" 
        + pictures[i].pictureThumb + "' />");
      (i % 2) && $(contentElement).append("<br/>")
    }
    }
})();

Note in Figure 13 that I capture a reference to the promise returned by the call to Data.getFeaturedCollections and then explicitly cancel the promise when the page unloads. This avoids a possible race condition in a scenario where the user navigates to the page and then navigates away before the call to getFeaturedCollections has returned.

When I press F5 and navigate to scheduler.html, I notice the Hub control populates slowly after the page loads. It may be merely annoying on my machine, but on less powerful machines the lag could be significant.

Visual Studio 2013 includes tools for measuring the responsiveness of the UI in a Windows Store app. In the Performance and Diagnostics pane, I select the HTML UI Responsiveness test and then click Start. After the app starts running, I navigate to scheduler.html and watch the results appear in the Hub control. Once I’ve completed the task, I switch back to the Desktop and then click Stop in the diagnostics session tab. Figure 14 displays the results.

HTML UI Responsiveness for Scheduler.html
Figure 14 HTML UI Responsiveness for Scheduler.html

I see the frame rate dropped to 3 FPS for roughly half a second. I select the period of low frame rate to see more details (see Figure 15).

Timeline Details Where the UI Thread Evaluates Scheduler.js
Figure 15 Timeline Details Where the UI Thread Evaluates Scheduler.js

At this point in the timeline (Figure 15), the UI thread is absorbed in running scheduler.js. If you look closely at the timeline details, you see several user marks (orange “tick” marks). These indicate specific calls to performance.mark in the code. In scheduler.js, the first call to performance.mark occurs when scheduler.html loads. Populating each HubSection control with content invokes a subsequent call. From the results, more than half of the time spent evaluating scheduler.js occurred between when I navigated to the page (the first user mark) and when the sixth HubSection was populated with images (the last user mark).

(Keep in mind results will vary depending on your hardware. The HTML UI responsiveness tests shown in this article were run on a Microsoft Surface Pro with a third-generation Intel Core i5-3317U processor, running at 1.7Ghz, and Intel HD Graphics 400. )

To reduce lag, I might refactor my code so the HubSection controls are populated in a staggered manner. Users see content in the app soon after they navigate to it. The content for the first one to two hub sections should load immediately after navigation and the other HubSections can load afterward.

Scheduler

JavaScript is a single-threaded environment, meaning everything is on the UI thread. In WinJS 2.0, Microsoft introduced the WinJS.Utilities.Scheduler to organize work performed on the UI thread (see bit.ly/1bFbpfb for more information).

The Scheduler creates a single queue of jobs to be run on the UI thread in the app. Jobs are completed based on priority, where higher priority jobs can preempt or postpone lower-priority jobs. Jobs are scheduled around actual user interaction on the UI thread, where the Scheduler slices up the time between calls and completes as many of the queued jobs as it can.

As mentioned, the scheduler executes jobs based on their priority, as set using the WinJS.Utilities.Scheduler.Priority enumeration. The enumeration has seven values (in descending order): max, high, aboveNormal, normal, belowNormal, idle and min. Jobs of equal priority are run on a first-in, first-out basis.

Turning to the test case, I create a job on the Scheduler to populate each HubSection when scheduler.html loads. For each HubSection, I call Scheduler.schedule and pass in a function that populates the HubSection. The first two jobs are run at normal priority and all the others are run when the UI thread is idle. In the third parameter for the schedule method, thisArg, I pass in some context for the job.

The schedule method returns a Job object, which lets me monitor a job’s progress or cancel it. For each job, I assign the same OwnerToken object to its owner property. This lets me cancel all scheduled jobs attributed to that owner token. See Figure 16.

Figure 16 Updated Scheduler.js Using Scheduler API

(function () {
  "use strict";
  var dataRequest, jobOwnerToken;
  var scheduler = WinJS.Utilities.Scheduler;
  WinJS.UI.Pages.define("/pages/scheduler/scheduler.html", {
    ready: function (element, options) {
      performance.mark("navigated to scheduler");
      dataRequest = Data.featuredCollections.
        then(function (collections) {
          performance.mark("got collection");
          var hub = element.querySelector("#featuredHub");
          if (!hub) { return; }
          var hubSections = hub.winControl.sections,
          hubSection, collection, priority;
          jobOwnerToken = scheduler.createOwnerToken();
          for (var i = 0; i < hubSections.length; i++) {
            hubSection = hubSections.getItem(i);
            collection = collections.getItem(i);
            priority ==  (i < 2) ? scheduler.Priority.normal :
              scheduler.Priority.idle;
            scheduler.schedule(function () {
                populateSection(this.section, this.collection)
              },
              priority,
              { section: hubSection, collection: collection },
              "adding hub section").
            owner = jobOwnerToken;
          }
        });
      },
      unload: function () {
       dataRequest && dataRequest.cancel();
       jobOwnerToken && jobOwnerToken.cancelAll();
    }
    // Other PageControl members ...
  });
  function populateSection(section, collection) {
    performance.mark("creating a hub section");
    section.data.header = collection.data.title;
    var contentElement = section.data.contentElement;
    contentElement.innerHTML = "";
    var pictures = collection.data.pictures;
    for (var i = 0; i < 6; i++) {
      $(contentElement).append("<img src='" 
        + pictures[i].pictureThumb + "' />");
      (i % 2) && $(contentElement).append("<br/>")
    }
  }
})();

Now when I run the HTML UI Responsiveness diagnostics test, I should see some different results. Figure 17 shows the results of the second test.

HTML UI Responsiveness After Using Scheduler.js
Figure 17 HTML UI Responsiveness After Using Scheduler.js

During the second test, the app dropped fewer frames over a shorter period of time. The experience in the app was better, too: the Hub control populated more quickly and there was almost no lag.

Manipulating the DOM Affects UI Responsiveness

Adding new elements to the DOM on an HTML page can hurt performance, particularly if you’re adding more than a handful of new elements. The page needs to recalculate the positions of the other items on the page, then reapply styles, and, finally, repaint the page. For example, a CSS instruction that sets the top, left, width, height, or display style for an element will cause the page to be recalculated. (I recommend using either the built-in animation features in WinJS or the animation transforms available in CSS3 for manipulating the position of HTML elements instead.)

Yet injecting and displaying dynamic content is a common app design. Your best option for performance, when possible, is to use the data binding provided by the platform. Data binding in WinJS is optimized for a quick and responsive UX.

Otherwise, you’ll need to decide between injecting raw HTML as a string into another element with innerHTML, or adding individual elements one at a time using createElement and appendChild. Using innerHTML will most often provide better performance, but you might not be able to manipulate the HTML once it’s been inserted.

In my examples, I chose the $.append method in jQuery. With append, I can pass along raw HTML as a string and get immediate programmatic access to the new DOM nodes. (It also provides pretty good performance.)

Web Workers

The standard Web platform includes the Web Worker API that lets an app run background tasks off the UI thread. In short, Web Workers (or just Workers) allow for multi-threading in JavaScript applications. You pass simple messages (a string or simple JavaScript object) to the Worker thread and the Worker returns messages back to the main thread using the postMessage method.

Workers run in a different script context from the rest of the app, so they can’t access the UI. You can’t create new HTML ele­ments using createElement or leverage features of third-party libraries that rely on the document object (for example, the jQuery function—$). However, Workers can access the Windows Runtime APIs, which means they can write to app data, issue toasts and tile updates, or even save files. They’re well-suited for background tasks that require no input from the user, are computationally expensive or require multiple calls to a Web service. If you want more information about the Web Worker API, see the Worker reference documentation at bit.ly/1fllmip.

The benefit of using a Worker thread is that UI responsiveness won’t be affected by the background work. The UI remains responsive and practically no frames are dropped. Also, Workers can import other JavaScript libraries that don’t rely on the DOM, including the fundamental library for WinJS (base.js). So, you can, for instance, create promises in a Worker thread.

On the other hand, Workers aren’t a cure-all for performance issues. The cycles for the Worker threads are still being allocated from the total CPU cycles available on the machine, even if they aren’t coming from the UI thread. You need to be judicious about using Workers.

For the next test case, I’ll use a Worker thread to retrieve a collection of images from the Library of Congress and populate a ListView control with those pictures. First, I’ll add a new script to store the Worker thread named LOC-worker.js to my project, as follows:

(function () {
  "use strict";
  self.addEventListener("message", function (message) {
    importScripts("//Microsoft.WinJS.2.0/js/base.js", "searchLoC.js");
    LOCPictures.getCollection(message.data).
      then(
        function (response) {
          postMessage(response);
        });
  });
})();

I use the importScripts function to bring base.js from the WinJS library and seachLOC.js scripts into the Worker’s context, making them available for use.

Next, I add a new Page Control item named worker.html to my project (/pages/worker/worker.html). I add a little markup within the <section> tags in worker.html to contain the ListView control and define its layout. The control will be created dynamically when the Worker returns:

<div id="collection" class='searchList'>
  <progress class="win-ring"></progress>
</div>
<div id='searchResultsTemplate' data-win-control='WinJS.Binding.Template'>
  <div class='searchResultsItem'>
    <img src='#' data-win-bind='src: pictureThumb' />
    <div class='details'>
      <p data-win-bind='textContent: title'></p>
      <p data-win-bind='textContent: date'></p>
    </div>
  </div>
</div>

Finally, I add the code to worker.js that creates a new Worker thread and then populates the HTML based on the response. The code in worker.js is shown in Figure 18.

Figure 18 Creating a Worker Thread and Then Populating the UI

(function () {
  "use strict";
  WinJS.UI.Pages.define("/pages/worker/worker.html", {
    ready: function (element, options) {
      performance.mark("navigated to Worker");
      var getBaseballCards = new Worker('/js/LOC-worker.js'),
        baseballCards = new LOCPictures.Collection({
          title: "Baseball cards",
          thumbFeatured: null,
          code: "bbc"
      });
      getBaseballCards.onmessage = function (message) {
         createCollection(message.data);
         getBaseballCards.terminate();
      }
      getBaseballCards.postMessage(baseballCards);
    }
  // Other PageControl members ...
  });
  function createCollection(info) {
    var collection = new WinJS.Binding.List(info.pictures),
      collectionElement = $("# searchResultsTemplate")[0],
      collectionList = new WinJS.UI.ListView(collectionElement, {
        itemDataSource: collection.dataSource,
        itemTemplate: $('#collectionTemplate')[0],
        layout: {type: WinJS.UI.GridLayout}
      });
  }
})();

When you run the app and navigate to the page, you’ll notice minimal lag between navigation and populating the ListView control with images. If you run this test case through the HTML UI responsiveness tool, you’ll see output similar to what’s shown in Figure 19.

HTML UI Responsiveness When Using a Worker Thread
Figure 19 HTML UI Responsiveness When Using a Worker Thread

Notice the app dropped very few frames after I navigated to the worker.html page (after the first user mark in the timeline). The UI remained incredibly responsive because the fetching of data was off-loaded to the Worker thread.

When to Choose between the Scheduler and Worker APIs

Because both the Scheduler and the Worker APIs let you manage background tasks in your code, you might be wondering when to use one over the other. (Please note that the two code samples I’ve provided in this article aren’t a fair “apples to apples” comparison of the two APIs.)

The Worker API, because it runs on a different thread, provides better performance than Scheduler in a head-to-head comparison. However, because the Scheduler uses timeslices of the UI thread, it has the context of the current page. You can use the Scheduler to update UI elements on a page or dynamically create new elements on the page.

If your background code needs to interact with the UI in any kind of meaningful way, you should use Scheduler. However, if your code doesn’t rely on the context of the app and only passes simple data back and forth, consider using a Worker. The benefit of using a Worker thread is that UI responsiveness won’t be affected by the background work.

The Scheduler and Web Worker APIs aren’t your only choices for spawning multiple threads in a Windows Store app. You can also build a Windows Runtime Component in C++, C# or Visual Basic .NET that can create new threads. WinRT Components can expose APIs that the JavaScript code can call. For more information, see bit.ly/19DfFaO.

I doubt many developers set out to write buggy, glitchy or unresponsive apps (unless they’re writing an article about buggy, glitchy and unresponsive apps). The trick is to find those bugs in your app code and fix them, preferably before the app goes in front of users. In this series of articles, I’ve demonstrated several tools for catching problems in your code and techniques for writing more efficient app code.

Of course, none of these techniques or tools is a silver bullet that will automatically fix a problem. They can help improve the experience, but they don’t eliminate the need for good coding practices. The fundamentals of programming, in general, remain as true for Window Store apps built with JavaScript as for any other platform.


Eric Schmidt is a content developer in the Microsoft Windows Developer Content team, writing about the Windows Library for JavaScript (WinJS). When previously in the Microsoft Office Division, he built code samples for the apps for Office platform. Otherwise, he spends time with his family, plays string bass, builds HTML5 video games, or blogs about plastic building toys (historybricks.com).

THANKS to the following Microsoft technical experts for reviewing this article: Kraig Brockschmidt, Greg Bulmash and Josh Williams
Kraig Brockschmidt is a Senior Program Manager in the Windows Ecosystem team, working directly with the developer community and key partners on building Windows Store apps. He is author of Programming Windows Store Apps in HTML, CSS, and JavaScript (now in its second edition).

Josh Williams is a Principal Software Development Engineer Lead in the Windows Developer Experience team. He and his team built the Windows Library for JavaScript (WinJS).

Greg Bulmash writes for the Internet Explorer Developer Center (https://msdn.microsoft.com/ie) and documents the F12 Developer Tools for IE11. In his spare time he tries to keep his coding skills up to date and helps kids learn to code by organizing the Seattle chapter of CoderDojo.