systrace: embed js & css when generating html

This change makes the systrace.py script embed the CSS and Javascript in the
HTML file that is generated.

Change-Id: I1ac7456f9a9d35a8c55a564432b01ea6966f8092
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..25e8ab1
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,91 @@
+# Names should be added to this file like so:
+# Name or Organization <email address>
+
+Google Inc.
+Seo Sanghyeon <sanxiyn@gmail.com>
+Alex Scheele <alexscheele@gmail.com>
+Andrew Brampton <me@bramp.net>
+Paweł Hajdan jr <phajdan.jr@gmail.com>
+Jesse Miller <jesse@jmiller.biz>
+Szymon Piechowicz <szymonpiechowicz@o2.pl>
+James Vega <vega.james@gmail.com>
+Marco Rodrigues <gothicx@gmail.com>
+Matthias Reitinger <reimarvin@gmail.com>
+Peter Bright <drpizza@quiscalusmexicanus.org>
+Arthur Lussos <developer0420@gmail.com>
+Masahiro Yado <yado.masa@gmail.com>
+Yarin Kaul <yarin.kaul@gmail.com>
+Gaetano Mendola <mendola@gmail.com>
+Comodo CA Limited
+Torchmobile Inc.
+Craig Schlenter <craig.schlenter@gmail.com>
+Ibrar Ahmed <ibrar.ahmad@gmail.com>
+Naoki Takano <takano.naoki@gmail.com>
+Fabien Tassin <fta@sofaraway.org>
+Kunal Thakar <kunalt@gmail.com>
+Mohamed Mansour <m0.interactive@gmail.com>
+Joshua Roesslein <jroesslein@gmail.com>
+Yong Shin <sy3620@gmail.com>
+Laszlo Radanyi <bekkra@gmail.com>
+Raman Tenneti <raman.tenneti@gmail.com>
+Kyle Nahrgang <kpn24@drexel.edu>
+Kim Christensen <kimworking@gmail.com>
+Paul Robinson <paulrobinson85@googlemail.com>
+Josué Ratelle <jorat1346@gmail.com>
+Edward Crossman <tedoc2000@gmail.com>
+Nikita Ofitserov <himikof@gmail.com>
+Sean Bryant <sean@cyberwang.net>
+Robert Sesek <rsesek@bluestatic.org>
+Janwar Dinata <j.dinata@gmail.com>
+Will Hirsch <chromium@willhirsch.co.uk>
+Yoav Zilberberg <yoav.zilberberg@gmail.com>
+Joel Stanley <joel@jms.id.au>
+Jacob Mandelson <jacob@mandelson.org>
+Yuri Gorobets <yuri.gorobets@gmail.com>
+Paul Wicks <pwicks86@gmail.com>
+Thiago Farina <thiago.farina@gmail.com>
+Viet-Trung Luu <viettrungluu@gmail.com>
+Pierre-Antoine LaFayette <pierre.lafayette@gmail.com>
+Song YeWen <ffmpeg@gmail.com>
+Philippe Beauchamp <philippe.beauchamp@gmail.com>
+Vedran Šajatović <vedran.sajatovic@gmail.com>
+Randy Posynick <randy.posynick@gmail.com>
+Bruno Calvignac <brunocalvignac@gmail.com>
+Jaime Soriano Pastor <jsorianopastor@gmail.com>
+Bryan Donlan <bdonlan@gmail.com>
+Ramkumar Ramachandra <artagnon@gmail.com>
+Dominic Jodoin <dominic.jodoin@gmail.com>
+Kaspar Brand <googlecontrib@velox.ch>
+Clemens Fruhwirth <clemens@endorphin.org>
+Kevin Lee Helpingstine <sig11@reprehensible.net>
+Bernard Cafarelli <voyageur@gentoo.org>
+Vernon Tang <vt@foilhead.net>
+Alexander Sulfrian <alexander@sulfrian.net>
+Philippe Beaudoin <philippe.beaudoin@gmail.com>
+Mark Hahnenberg <mhahnenb@gmail.com>
+Alex Gartrell <alexgartrell@gmail.com>
+Leith Bade <leith@leithalweapon.geek.nz>
+James Choi <jchoi42@pha.jhu.edu>
+Paul Kehrer <paul.l.kehrer@gmail.com>
+Chamal De Silva <chamal.desilva@gmail.com>
+Jay Soffian <jaysoffian@gmail.com>
+Brian G. Merrell <bgmerrell@gmail.com>
+Matthew Willis <appamatto@gmail.com>
+Novell Inc.
+Ryan Sleevi <ryan.sleevi@gmail.com>
+Satoshi Matsuzaki <satoshi.matsuzaki@gmail.com>
+Benjamin Jemlich <pcgod99@gmail.com>
+Ningxin Hu <ningxin.hu@intel.com>
+Jared Wein <weinjared@gmail.com>
+Mingmin Xie <melvinxie@gmail.com>
+Michael Gilbert <floppymaster@gmail.com>
+Giuseppe Iuculano <giuseppe@iuculano.it>
+litl LLC
+James Willcox <jwillcox@litl.com>
+Shreyas VA <v.a.shreyas@gmail.com>
+Steven Pennington <spenn@engr.uvic.ca>
+Jorge Villatoro <jorge@tomatocannon.com>
+Paul Nettleship <pnettleship@gmail.com>
+David Benjamin <davidben@mit.edu>
+Sevan Janiyan <venture37@geeklan.co.uk>
+Peter Beverloo <peter@lvp-media.com>
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8dc3504
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MODULE_LICENSE_BSD_LIKE b/MODULE_LICENSE_BSD_LIKE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_BSD_LIKE
diff --git a/README b/README
index e1e3c56..cedfff2 100644
--- a/README
+++ b/README
@@ -1,11 +1,10 @@
-The systrace.py tool and the Javascript / HTML that goes along with it are
-being checked in under the vendor/google repository for now.  This is because
-the trace HTML files generated by systrace.py contain references to files that
-can only be accessed on the corp network.  Ultimately these files will be
-served publicly at which point this tool should move to the development
-repository.  However, to enable use of the tool sooner I'm putting it here for
-now.
+The things in the www subdirectory were forked from the Chrome tracing tool.
+This should end up getting merged upstream at some point into either the
+Chromium code base or else a separate shared project.
 
-Also, the things in the www subdirectory were forked from the Chrome tracing
-tool.  This may end up getting merged upstream into either the Chromium code
-base or else a separate shared project.
+The update.sh script should be used to package the CSS and Javascript files in
+the www directory into the style.css and script.js files.  These files are
+being checked in, and get embedded in each trace HTML file the systrace.py
+generates.  Each time a file in the www directory gets updated, this script
+will need to be rerun, and the generated files must be committed along with the
+changes to the www directory.
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..993791c
--- /dev/null
+++ b/script.js
@@ -0,0 +1,4762 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+const cr = (function() {
+
+  /**
+   * Whether we are using a Mac or not.
+   * @type {boolean}
+   */
+  const isMac = /Mac/.test(navigator.platform);
+
+  /**
+   * Whether this is on the Windows platform or not.
+   * @type {boolean}
+   */
+  const isWindows = /Win/.test(navigator.platform);
+
+  /**
+   * Whether this is on chromeOS or not.
+   * @type {boolean}
+   */
+  const isChromeOS = /CrOS/.test(navigator.userAgent);
+
+  /**
+   * Whether this is on vanilla Linux (not chromeOS).
+   * @type {boolean}
+   */
+  const isLinux = /Linux/.test(navigator.userAgent);
+
+  /**
+   * Whether this uses GTK or not.
+   * @type {boolean}
+   */
+  const isGTK = /GTK/.test(chrome.toolkit);
+
+  /**
+   * Whether this uses the views toolkit or not.
+   * @type {boolean}
+   */
+  const isViews = /views/.test(chrome.toolkit);
+
+  /**
+   * Whether this window is optimized for touch-based input.
+   * @type {boolean}
+   */
+  const isTouchOptimized = !!chrome.touchOptimized;
+
+  /**
+   * Sets the os and toolkit attributes in the <html> element so that platform
+   * specific css rules can be applied.
+   */
+  function enablePlatformSpecificCSSRules() {
+    if (isMac)
+      doc.documentElement.setAttribute('os', 'mac');
+    if (isWindows)
+      doc.documentElement.setAttribute('os', 'windows');
+    if (isChromeOS)
+      doc.documentElement.setAttribute('os', 'chromeos');
+    if (isLinux)
+      doc.documentElement.setAttribute('os', 'linux');
+    if (isGTK)
+      doc.documentElement.setAttribute('toolkit', 'gtk');
+    if (isViews)
+      doc.documentElement.setAttribute('toolkit', 'views');
+    if (isTouchOptimized)
+      doc.documentElement.setAttribute('touch-optimized', '');
+  }
+
+  /**
+   * Builds an object structure for the provided namespace path,
+   * ensuring that names that already exist are not overwritten. For
+   * example:
+   * "a.b.c" -> a = {};a.b={};a.b.c={};
+   * @param {string} name Name of the object that this file defines.
+   * @param {*=} opt_object The object to expose at the end of the path.
+   * @param {Object=} opt_objectToExportTo The object to add the path to;
+   *     default is {@code window}.
+   * @private
+   */
+  function exportPath(name, opt_object, opt_objectToExportTo) {
+    var parts = name.split('.');
+    var cur = opt_objectToExportTo || window /* global */;
+
+    for (var part; parts.length && (part = parts.shift());) {
+      if (!parts.length && opt_object !== undefined) {
+        // last part and we have an object; use it
+        cur[part] = opt_object;
+      } else if (part in cur) {
+        cur = cur[part];
+      } else {
+        cur = cur[part] = {};
+      }
+    }
+    return cur;
+  };
+
+  // cr.Event is called CrEvent in here to prevent naming conflicts. We also
+  // store the original Event in case someone does a global alias of cr.Event.
+  const DomEvent = Event;
+
+  /**
+   * Creates a new event to be used with cr.EventTarget or DOM EventTarget
+   * objects.
+   * @param {string} type The name of the event.
+   * @param {boolean=} opt_bubbles Whether the event bubbles. Default is false.
+   * @param {boolean=} opt_preventable Whether the default action of the event
+   *     can be prevented.
+   * @constructor
+   * @extends {DomEvent}
+   */
+  function CrEvent(type, opt_bubbles, opt_preventable) {
+    var e = cr.doc.createEvent('Event');
+    e.initEvent(type, !!opt_bubbles, !!opt_preventable);
+    e.__proto__ = CrEvent.prototype;
+    return e;
+  }
+
+  CrEvent.prototype = {
+    __proto__: DomEvent.prototype
+  };
+
+  /**
+   * Fires a property change event on the target.
+   * @param {EventTarget} target The target to dispatch the event on.
+   * @param {string} propertyName The name of the property that changed.
+   * @param {*} newValue The new value for the property.
+   * @param {*} oldValue The old value for the property.
+   */
+  function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
+    var e = new CrEvent(propertyName + 'Change');
+    e.propertyName = propertyName;
+    e.newValue = newValue;
+    e.oldValue = oldValue;
+    target.dispatchEvent(e);
+  }
+
+  /**
+   * Converts a camelCase javascript property name to a hyphenated-lower-case
+   * attribute name.
+   * @param {string} jsName The javascript camelCase property name.
+   * @return {string} The equivalent hyphenated-lower-case attribute name.
+   */
+  function getAttributeName(jsName) {
+    return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
+  }
+
+  /**
+   * The kind of property to define in {@code defineProperty}.
+   * @enum {number}
+   */
+  const PropertyKind = {
+    /**
+     * Plain old JS property where the backing data is stored as a "private"
+     * field on the object.
+     */
+    JS: 'js',
+
+    /**
+     * The property backing data is stored as an attribute on an element.
+     */
+    ATTR: 'attr',
+
+    /**
+     * The property backing data is stored as an attribute on an element. If the
+     * element has the attribute then the value is true.
+     */
+    BOOL_ATTR: 'boolAttr'
+  };
+
+  /**
+   * Helper function for defineProperty that returns the getter to use for the
+   * property.
+   * @param {string} name
+   * @param {cr.PropertyKind} kind
+   * @return {function():*} The getter for the property.
+   */
+  function getGetter(name, kind) {
+    switch (kind) {
+      case PropertyKind.JS:
+        var privateName = name + '_';
+        return function() {
+          return this[privateName];
+        };
+      case PropertyKind.ATTR:
+        var attributeName = getAttributeName(name);
+        return function() {
+          return this.getAttribute(attributeName);
+        };
+      case PropertyKind.BOOL_ATTR:
+        var attributeName = getAttributeName(name);
+        return function() {
+          return this.hasAttribute(attributeName);
+        };
+    }
+  }
+
+  /**
+   * Helper function for defineProperty that returns the setter of the right
+   * kind.
+   * @param {string} name The name of the property we are defining the setter
+   *     for.
+   * @param {cr.PropertyKind} kind The kind of property we are getting the
+   *     setter for.
+   * @param {function(*):void} opt_setHook A function to run after the property
+   *     is set, but before the propertyChange event is fired.
+   * @return {function(*):void} The function to use as a setter.
+   */
+  function getSetter(name, kind, opt_setHook) {
+    switch (kind) {
+      case PropertyKind.JS:
+        var privateName = name + '_';
+        return function(value) {
+          var oldValue = this[privateName];
+          if (value !== oldValue) {
+            this[privateName] = value;
+            if (opt_setHook)
+              opt_setHook.call(this, value, oldValue);
+            dispatchPropertyChange(this, name, value, oldValue);
+          }
+        };
+
+      case PropertyKind.ATTR:
+        var attributeName = getAttributeName(name);
+        return function(value) {
+          var oldValue = this[attributeName];
+          if (value !== oldValue) {
+            if (value == undefined)
+              this.removeAttribute(attributeName);
+            else
+              this.setAttribute(attributeName, value);
+            if (opt_setHook)
+              opt_setHook.call(this, value, oldValue);
+            dispatchPropertyChange(this, name, value, oldValue);
+          }
+        };
+
+      case PropertyKind.BOOL_ATTR:
+        var attributeName = getAttributeName(name);
+        return function(value) {
+          var oldValue = this[attributeName];
+          if (value !== oldValue) {
+            if (value)
+              this.setAttribute(attributeName, name);
+            else
+              this.removeAttribute(attributeName);
+            if (opt_setHook)
+              opt_setHook.call(this, value, oldValue);
+            dispatchPropertyChange(this, name, value, oldValue);
+          }
+        };
+    }
+  }
+
+  /**
+   * Defines a property on an object. When the setter changes the value a
+   * property change event with the type {@code name + 'Change'} is fired.
+   * @param {!Object} obj The object to define the property for.
+   * @param {string} name The name of the property.
+   * @param {cr.PropertyKind=} opt_kind What kind of underlying storage to use.
+   * @param {function(*):void} opt_setHook A function to run after the
+   *     property is set, but before the propertyChange event is fired.
+   */
+  function defineProperty(obj, name, opt_kind, opt_setHook) {
+    if (typeof obj == 'function')
+      obj = obj.prototype;
+
+    var kind = opt_kind || PropertyKind.JS;
+
+    if (!obj.__lookupGetter__(name)) {
+      obj.__defineGetter__(name, getGetter(name, kind));
+    }
+
+    if (!obj.__lookupSetter__(name)) {
+      obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
+    }
+  }
+
+  /**
+   * Counter for use with createUid
+   */
+  var uidCounter = 1;
+
+  /**
+   * @return {number} A new unique ID.
+   */
+  function createUid() {
+    return uidCounter++;
+  }
+
+  /**
+   * Returns a unique ID for the item. This mutates the item so it needs to be
+   * an object
+   * @param {!Object} item The item to get the unique ID for.
+   * @return {number} The unique ID for the item.
+   */
+  function getUid(item) {
+    if (item.hasOwnProperty('uid'))
+      return item.uid;
+    return item.uid = createUid();
+  }
+
+  /**
+   * Dispatches a simple event on an event target.
+   * @param {!EventTarget} target The event target to dispatch the event on.
+   * @param {string} type The type of the event.
+   * @param {boolean=} opt_bubbles Whether the event bubbles or not.
+   * @param {boolean=} opt_cancelable Whether the default action of the event
+   *     can be prevented.
+   * @return {boolean} If any of the listeners called {@code preventDefault}
+   *     during the dispatch this will return false.
+   */
+  function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
+    var e = new cr.Event(type, opt_bubbles, opt_cancelable);
+    return target.dispatchEvent(e);
+  }
+
+  /**
+   * @param {string} name
+   * @param {!Function} fun
+   */
+  function define(name, fun) {
+    var obj = exportPath(name);
+    var exports = fun();
+    for (var propertyName in exports) {
+      // Maybe we should check the prototype chain here? The current usage
+      // pattern is always using an object literal so we only care about own
+      // properties.
+      var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
+                                                               propertyName);
+      if (propertyDescriptor)
+        Object.defineProperty(obj, propertyName, propertyDescriptor);
+    }
+  }
+
+  /**
+   * Document used for various document related operations.
+   * @type {!Document}
+   */
+  var doc = document;
+
+
+  /**
+   * Allows you to run func in the context of a different document.
+   * @param {!Document} document The document to use.
+   * @param {function():*} func The function to call.
+   */
+  function withDoc(document, func) {
+    var oldDoc = doc;
+    doc = document;
+    try {
+      func();
+    } finally {
+      doc = oldDoc;
+    }
+  }
+
+  /**
+   * Adds a {@code getInstance} static method that always return the same
+   * instance object.
+   * @param {!Function} ctor The constructor for the class to add the static
+   *     method to.
+   */
+  function addSingletonGetter(ctor) {
+    ctor.getInstance = function() {
+      return ctor.instance_ || (ctor.instance_ = new ctor());
+    };
+  }
+
+  return {
+    addSingletonGetter: addSingletonGetter,
+    isChromeOS: isChromeOS,
+    isMac: isMac,
+    isWindows: isWindows,
+    isLinux: isLinux,
+    isViews: isViews,
+    isTouchOptimized: isTouchOptimized,
+    enablePlatformSpecificCSSRules: enablePlatformSpecificCSSRules,
+    define: define,
+    defineProperty: defineProperty,
+    PropertyKind: PropertyKind,
+    createUid: createUid,
+    getUid: getUid,
+    dispatchSimpleEvent: dispatchSimpleEvent,
+    dispatchPropertyChange: dispatchPropertyChange,
+
+    /**
+     * The document that we are currently using.
+     * @type {!Document}
+     */
+    get doc() {
+      return doc;
+    },
+    withDoc: withDoc,
+    Event: CrEvent
+  };
+})();
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This contains an implementation of the EventTarget interface
+ * as defined by DOM Level 2 Events.
+ */
+
+cr.define('cr', function() {
+
+  /**
+   * Creates a new EventTarget. This class implements the DOM level 2
+   * EventTarget interface and can be used wherever those are used.
+   * @constructor
+   */
+  function EventTarget() {
+  }
+
+  EventTarget.prototype = {
+
+    /**
+     * Adds an event listener to the target.
+     * @param {string} type The name of the event.
+     * @param {!Function|{handleEvent:Function}} handler The handler for the
+     *     event. This is called when the event is dispatched.
+     */
+    addEventListener: function(type, handler) {
+      if (!this.listeners_)
+        this.listeners_ = Object.create(null);
+      if (!(type in this.listeners_)) {
+        this.listeners_[type] = [handler];
+      } else {
+        var handlers = this.listeners_[type];
+        if (handlers.indexOf(handler) < 0)
+          handlers.push(handler);
+      }
+    },
+
+    /**
+     * Removes an event listener from the target.
+     * @param {string} type The name of the event.
+     * @param {!Function|{handleEvent:Function}} handler The handler for the
+     *     event.
+     */
+    removeEventListener: function(type, handler) {
+      if (!this.listeners_)
+        return;
+      if (type in this.listeners_) {
+        var handlers = this.listeners_[type];
+        var index = handlers.indexOf(handler);
+        if (index >= 0) {
+          // Clean up if this was the last listener.
+          if (handlers.length == 1)
+            delete this.listeners_[type];
+          else
+            handlers.splice(index, 1);
+        }
+      }
+    },
+
+    /**
+     * Dispatches an event and calls all the listeners that are listening to
+     * the type of the event.
+     * @param {!cr.event.Event} event The event to dispatch.
+     * @return {boolean} Whether the default action was prevented. If someone
+     *     calls preventDefault on the event object then this returns false.
+     */
+    dispatchEvent: function(event) {
+      if (!this.listeners_)
+        return true;
+
+      // Since we are using DOM Event objects we need to override some of the
+      // properties and methods so that we can emulate this correctly.
+      var self = this;
+      event.__defineGetter__('target', function() {
+        return self;
+      });
+      event.preventDefault = function() {
+        this.returnValue = false;
+      };
+
+      var type = event.type;
+      var prevented = 0;
+      if (type in this.listeners_) {
+        // Clone to prevent removal during dispatch
+        var handlers = this.listeners_[type].concat();
+        for (var i = 0, handler; handler = handlers[i]; i++) {
+          if (handler.handleEvent)
+            prevented |= handler.handleEvent.call(handler, event) === false;
+          else
+            prevented |= handler.call(this, event) === false;
+        }
+      }
+
+      return !prevented && event.returnValue;
+    }
+  };
+
+  // Export
+  return {
+    EventTarget: EventTarget
+  };
+});
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('cr.ui', function() {
+
+  /**
+   * Decorates elements as an instance of a class.
+   * @param {string|!Element} source The way to find the element(s) to decorate.
+   *     If this is a string then {@code querySeletorAll} is used to find the
+   *     elements to decorate.
+   * @param {!Function} constr The constructor to decorate with. The constr
+   *     needs to have a {@code decorate} function.
+   */
+  function decorate(source, constr) {
+    var elements;
+    if (typeof source == 'string')
+      elements = cr.doc.querySelectorAll(source);
+    else
+      elements = [source];
+
+    for (var i = 0, el; el = elements[i]; i++) {
+      if (!(el instanceof constr))
+        constr.decorate(el);
+    }
+  }
+
+  /**
+   * Helper function for creating new element for define.
+   */
+  function createElementHelper(tagName, opt_bag) {
+    // Allow passing in ownerDocument to create in a different document.
+    var doc;
+    if (opt_bag && opt_bag.ownerDocument)
+      doc = opt_bag.ownerDocument;
+    else
+      doc = cr.doc;
+    return doc.createElement(tagName);
+  }
+
+  /**
+   * Creates the constructor for a UI element class.
+   *
+   * Usage:
+   * <pre>
+   * var List = cr.ui.define('list');
+   * List.prototype = {
+   *   __proto__: HTMLUListElement.prototype,
+   *   decorate: function() {
+   *     ...
+   *   },
+   *   ...
+   * };
+   * </pre>
+   *
+   * @param {string|Function} tagNameOrFunction The tagName or
+   *     function to use for newly created elements. If this is a function it
+   *     needs to return a new element when called.
+   * @return {function(Object=):Element} The constructor function which takes
+   *     an optional property bag. The function also has a static
+   *     {@code decorate} method added to it.
+   */
+  function define(tagNameOrFunction) {
+    var createFunction, tagName;
+    if (typeof tagNameOrFunction == 'function') {
+      createFunction = tagNameOrFunction;
+      tagName = '';
+    } else {
+      createFunction = createElementHelper;
+      tagName = tagNameOrFunction;
+    }
+
+    /**
+     * Creates a new UI element constructor.
+     * @param {Object=} opt_propertyBag Optional bag of properties to set on the
+     *     object after created. The property {@code ownerDocument} is special
+     *     cased and it allows you to create the element in a different
+     *     document than the default.
+     * @constructor
+     */
+    function f(opt_propertyBag) {
+      var el = createFunction(tagName, opt_propertyBag);
+      f.decorate(el);
+      for (var propertyName in opt_propertyBag) {
+        el[propertyName] = opt_propertyBag[propertyName];
+      }
+      return el;
+    }
+
+    /**
+     * Decorates an element as a UI element class.
+     * @param {!Element} el The element to decorate.
+     */
+    f.decorate = function(el) {
+      el.__proto__ = f.prototype;
+      el.decorate();
+    };
+
+    return f;
+  }
+
+  /**
+   * Input elements do not grow and shrink with their content. This is a simple
+   * (and not very efficient) way of handling shrinking to content with support
+   * for min width and limited by the width of the parent element.
+   * @param {HTMLElement} el The element to limit the width for.
+   * @param {number} parentEl The parent element that should limit the size.
+   * @param {number} min The minimum width.
+   */
+  function limitInputWidth(el, parentEl, min) {
+    // Needs a size larger than borders
+    el.style.width = '10px';
+    var doc = el.ownerDocument;
+    var win = doc.defaultView;
+    var computedStyle = win.getComputedStyle(el);
+    var parentComputedStyle = win.getComputedStyle(parentEl);
+    var rtl = computedStyle.direction == 'rtl';
+
+    // To get the max width we get the width of the treeItem minus the position
+    // of the input.
+    var inputRect = el.getBoundingClientRect();  // box-sizing
+    var parentRect = parentEl.getBoundingClientRect();
+    var startPos = rtl ? parentRect.right - inputRect.right :
+        inputRect.left - parentRect.left;
+
+    // Add up border and padding of the input.
+    var inner = parseInt(computedStyle.borderLeftWidth, 10) +
+        parseInt(computedStyle.paddingLeft, 10) +
+        parseInt(computedStyle.paddingRight, 10) +
+        parseInt(computedStyle.borderRightWidth, 10);
+
+    // We also need to subtract the padding of parent to prevent it to overflow.
+    var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) :
+        parseInt(parentComputedStyle.paddingRight, 10);
+
+    var max = parentEl.clientWidth - startPos - inner - parentPadding;
+
+    function limit() {
+      if (el.scrollWidth > max) {
+        el.style.width = max + 'px';
+      } else {
+        el.style.width = 0;
+        var sw = el.scrollWidth;
+        if (sw < min) {
+          el.style.width = min + 'px';
+        } else {
+          el.style.width = sw + 'px';
+        }
+      }
+    }
+
+    el.addEventListener('input', limit);
+    limit();
+  }
+
+  return {
+    decorate: decorate,
+    define: define,
+    limitInputWidth: limitInputWidth
+  };
+});
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * The global object.
+ * @type {!Object}
+ */
+const global = this;
+
+/**
+ * Alias for document.getElementById.
+ * @param {string} id The ID of the element to find.
+ * @return {HTMLElement} The found element or null if not found.
+ */
+function $(id) {
+  return document.getElementById(id);
+}
+
+/**
+ * Calls chrome.send with a callback and restores the original afterwards.
+ * @param {string} name The name of the message to send.
+ * @param {!Array} params The parameters to send.
+ * @param {string} callbackName The name of the function that the backend calls.
+ * @param {!Function} The function to call.
+ */
+function chromeSend(name, params, callbackName, callback) {
+  var old = global[callbackName];
+  global[callbackName] = function() {
+    // restore
+    global[callbackName] = old;
+
+    var args = Array.prototype.slice.call(arguments);
+    return callback.apply(global, args);
+  };
+  chrome.send(name, params);
+}
+
+/**
+ * Generates a CSS url string.
+ * @param {string} s The URL to generate the CSS url for.
+ * @return {string} The CSS url string.
+ */
+function url(s) {
+  // http://www.w3.org/TR/css3-values/#uris
+  // Parentheses, commas, whitespace characters, single quotes (') and double
+  // quotes (") appearing in a URI must be escaped with a backslash
+  var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
+  // WebKit has a bug when it comes to URLs that end with \
+  // https://bugs.webkit.org/show_bug.cgi?id=28885
+  if (/\\\\$/.test(s2)) {
+    // Add a space to work around the WebKit bug.
+    s2 += ' ';
+  }
+  return 'url("' + s2 + '")';
+}
+
+/**
+ * Parses query parameters from Location.
+ * @param {string} s The URL to generate the CSS url for.
+ * @return {object} Dictionary containing name value pairs for URL
+ */
+function parseQueryParams(location) {
+  var params = {};
+  var query = unescape(location.search.substring(1));
+  var vars = query.split("&");
+  for (var i=0; i < vars.length; i++) {
+    var pair = vars[i].split("=");
+    params[pair[0]] = pair[1];
+  }
+  return params;
+}
+
+function findAncestorByClass(el, className) {
+  return findAncestor(el, function(el) {
+    if (el.classList)
+      return el.classList.contains(className);
+    return null;
+  });
+}
+
+/**
+ * Return the first ancestor for which the {@code predicate} returns true.
+ * @param {Node} node The node to check.
+ * @param {function(Node) : boolean} predicate The function that tests the
+ *     nodes.
+ * @return {Node} The found ancestor or null if not found.
+ */
+function findAncestor(node, predicate) {
+  var last = false;
+  while (node != null && !(last = predicate(node))) {
+    node = node.parentNode;
+  }
+  return last ? node : null;
+}
+
+function swapDomNodes(a, b) {
+  var afterA = a.nextSibling;
+  if (afterA == b) {
+    swapDomNodes(b, a);
+    return;
+  }
+  var aParent = a.parentNode;
+  b.parentNode.replaceChild(a, b);
+  aParent.insertBefore(b, afterA);
+}
+
+/**
+ * Disables text selection and dragging.
+ */
+function disableTextSelectAndDrag() {
+  // Disable text selection.
+  document.onselectstart = function(e) {
+    e.preventDefault();
+  }
+
+  // Disable dragging.
+  document.ondragstart = function(e) {
+    e.preventDefault();
+  }
+}
+
+/**
+ * Check the directionality of the page.
+ * @return {boolean} True if Chrome is running an RTL UI.
+ */
+function isRTL() {
+  return document.documentElement.dir == 'rtl';
+}
+
+/**
+ * Simple common assertion API
+ * @param {*} condition The condition to test.  Note that this may be used to
+ *     test whether a value is defined or not, and we don't want to force a
+ *     cast to Boolean.
+ * @param {string=} opt_message A message to use in any error.
+ */
+function assert(condition, opt_message) {
+  'use strict';
+  if (!condition) {
+    var msg = 'Assertion failed';
+    if (opt_message)
+      msg = msg + ': ' + opt_message;
+    throw new Error(msg);
+  }
+}
+
+/**
+ * Get an element that's known to exist by its ID. We use this instead of just
+ * calling getElementById and not checking the result because this lets us
+ * satisfy the JSCompiler type system.
+ * @param {string} id The identifier name.
+ * @return {!Element} the Element.
+ */
+function getRequiredElement(id) {
+  var element = $(id);
+  assert(element, 'Missing required element: ' + id);
+  return element;
+}
+
+// Handle click on a link. If the link points to a chrome: or file: url, then
+// call into the browser to do the navigation.
+document.addEventListener('click', function(e) {
+  // Allow preventDefault to work.
+  if (!e.returnValue)
+    return;
+
+  var el = e.target;
+  if (el.nodeType == Node.ELEMENT_NODE &&
+      el.webkitMatchesSelector('A, A *')) {
+    while (el.tagName != 'A') {
+      el = el.parentElement;
+    }
+
+    if ((el.protocol == 'file:' || el.protocol == 'about:') &&
+        (e.button == 0 || e.button == 1)) {
+      chrome.send('navigateToUrl', [
+        el.href,
+        el.target,
+        e.button,
+        e.altKey,
+        e.ctrlKey,
+        e.metaKey,
+        e.shiftKey
+      ]);
+      e.preventDefault();
+    }
+  }
+});
+
+/**
+ * Creates a new URL which is the old URL with a GET param of key=value.
+ * @param {string} url The base URL. There is not sanity checking on the URL so
+ *     it must be passed in a proper format.
+ * @param {string} key The key of the param.
+ * @param {string} value The value of the param.
+ * @return {string}
+ */
+function appendParam(url, key, value) {
+  var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
+
+  if (url.indexOf('?') == -1)
+    return url + '?' + param;
+  return url + '&' + param;
+}
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+/**
+ * @fileoverview TimelineModel is a parsed representation of the
+ * TraceEvents obtained from base/trace_event in which the begin-end
+ * tokens are converted into a hierarchy of processes, threads,
+ * subrows, and slices.
+ *
+ * The building block of the model is a slice. A slice is roughly
+ * equivalent to function call executing on a specific thread. As a
+ * result, slices may have one or more subslices.
+ *
+ * A thread contains one or more subrows of slices. Row 0 corresponds to
+ * the "root" slices, e.g. the topmost slices. Row 1 contains slices that
+ * are nested 1 deep in the stack, and so on. We use these subrows to draw
+ * nesting tasks.
+ *
+ */
+cr.define('tracing', function() {
+  /**
+   * A TimelineSlice represents an interval of time on a given resource plus
+   * parameters associated with that interval.
+   *
+   * A slice is typically associated with a specific trace event pair on a
+   * specific thread.
+   * For example,
+   *   TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
+   *   TRACE_EVENT_END()                  at time=0.3ms
+   * This results in a single timeline slice from 0.1 with duration 0.2 on a
+   * specific thread.
+   *
+   * A slice can also be an interval of time on a Cpu on a TimelineCpu.
+   *
+   * All time units are stored in milliseconds.
+   * @constructor
+   */
+  function TimelineSlice(title, colorId, start, args, opt_duration) {
+    this.title = title;
+    this.start = start;
+    this.colorId = colorId;
+    this.args = args;
+    this.didNotFinish = false;
+    this.subSlices = [];
+    if (opt_duration !== undefined)
+      this.duration = opt_duration;
+  }
+
+  TimelineSlice.prototype = {
+    selected: false,
+
+    duration: undefined,
+
+    get end() {
+      return this.start + this.duration;
+    }
+  };
+
+  /**
+   * A TimelineThread stores all the trace events collected for a particular
+   * thread. We organize the slices on a thread by "subrows," where subrow 0
+   * has all the root slices, subrow 1 those nested 1 deep, and so on. There
+   * is also a set of non-nested subrows.
+   *
+   * @constructor
+   */
+  function TimelineThread(parent, tid) {
+    this.parent = parent;
+    this.tid = tid;
+    this.subRows = [[]];
+    this.nonNestedSubRows = [];
+  }
+
+  TimelineThread.prototype = {
+    /**
+     * Name of the thread, if present.
+     */
+    name: undefined,
+
+    getSubrow: function(i) {
+      while (i >= this.subRows.length)
+        this.subRows.push([]);
+      return this.subRows[i];
+    },
+
+    addNonNestedSlice: function(slice) {
+      for (var i = 0; i < this.nonNestedSubRows.length; i++) {
+        var currSubRow = this.nonNestedSubRows[i];
+        var lastSlice = currSubRow[currSubRow.length - 1];
+        if (slice.start >= lastSlice.start + lastSlice.duration) {
+          currSubRow.push(slice);
+          return;
+        }
+      }
+      this.nonNestedSubRows.push([slice]);
+    },
+
+    /**
+     * Updates the minTimestamp and maxTimestamp fields based on the
+     * current slices and nonNestedSubRows attached to the thread.
+     */
+    updateBounds: function() {
+      var values = [];
+      var slices;
+      if (this.subRows[0].length != 0) {
+        slices = this.subRows[0];
+        values.push(slices[0].start);
+        values.push(slices[slices.length - 1].end);
+      }
+      for (var i = 0; i < this.nonNestedSubRows.length; ++i) {
+        slices = this.nonNestedSubRows[i];
+        values.push(slices[0].start);
+        values.push(slices[slices.length - 1].end);
+      }
+      if (values.length) {
+        this.minTimestamp = Math.min.apply(Math, values);
+        this.maxTimestamp = Math.max.apply(Math, values);
+      } else {
+        this.minTimestamp = undefined;
+        this.maxTimestamp = undefined;
+      }
+    },
+
+    /**
+     * @return {String} A user-friendly name for this thread.
+     */
+    get userFriendlyName() {
+      var tname = this.name || this.tid;
+      return this.parent.pid + ': ' + tname;
+    },
+
+    /**
+     * @return {String} User friendly details about this thread.
+     */
+    get userFriendlyDetials() {
+      return 'pid: ' + this.parent.pid +
+          ', tid: ' + this.tid +
+          (this.name ? ', name: ' + this.name : '');
+    }
+
+  };
+
+  /**
+   * Comparison between threads that orders first by pid,
+   * then by names, then by tid.
+   */
+  TimelineThread.compare = function(x, y) {
+    if (x.parent.pid != y.parent.pid) {
+      return TimelineProcess.compare(x.parent, y.parent.pid);
+    }
+
+    if (x.name && y.name) {
+      var tmp = x.name.localeCompare(y.name);
+      if (tmp == 0)
+        return x.tid - y.tid;
+      return tmp;
+    } else if (x.name) {
+      return -1;
+    } else if (y.name) {
+      return 1;
+    } else {
+      return x.tid - y.tid;
+    }
+  };
+
+  /**
+   * Stores all the samples for a given counter.
+   * @constructor
+   */
+  function TimelineCounter(parent, id, name) {
+    this.parent = parent;
+    this.id = id;
+    this.name = name;
+    this.seriesNames = [];
+    this.seriesColors = [];
+    this.timestamps = [];
+    this.samples = [];
+  }
+
+  TimelineCounter.prototype = {
+    __proto__: Object.prototype,
+
+    get numSeries() {
+      return this.seriesNames.length;
+    },
+
+    get numSamples() {
+      return this.timestamps.length;
+    },
+
+    /**
+     * Updates the bounds for this counter based on the samples it contains.
+     */
+    updateBounds: function() {
+      if (this.seriesNames.length != this.seriesColors.length)
+        throw 'seriesNames.length must match seriesColors.length';
+      if (this.numSeries * this.numSamples != this.samples.length)
+        throw 'samples.length must be a multiple of numSamples.';
+
+      this.totals = [];
+      if (this.samples.length == 0) {
+        this.minTimestamp = undefined;
+        this.maxTimestamp = undefined;
+        this.maxTotal = 0;
+        return;
+      }
+      this.minTimestamp = this.timestamps[0];
+      this.maxTimestamp = this.timestamps[this.timestamps.length - 1];
+
+      var numSeries = this.numSeries;
+      var maxTotal = -Infinity;
+      for (var i = 0; i < this.timestamps.length; i++) {
+        var total = 0;
+        for (var j = 0; j < numSeries; j++) {
+          total += this.samples[i * numSeries + j];
+          this.totals.push(total);
+        }
+        if (total > maxTotal)
+          maxTotal = total;
+      }
+
+      if (this.maxTotal === undefined) {
+        this.maxTotal = maxTotal;
+      }
+    }
+
+  };
+
+  /**
+   * Comparison between counters that orders by pid, then name.
+   */
+  TimelineCounter.compare = function(x, y) {
+    if (x.parent.pid != y.parent.pid) {
+      return TimelineProcess.compare(x.parent, y.parent.pid);
+    }
+    var tmp = x.name.localeCompare(y.name);
+    if (tmp == 0)
+      return x.tid - y.tid;
+    return tmp;
+  };
+
+  /**
+   * The TimelineProcess represents a single process in the
+   * trace. Right now, we keep this around purely for bookkeeping
+   * reasons.
+   * @constructor
+   */
+  function TimelineProcess(pid) {
+    this.pid = pid;
+    this.threads = {};
+    this.counters = {};
+  };
+
+  TimelineProcess.prototype = {
+    get numThreads() {
+      var n = 0;
+      for (var p in this.threads) {
+        n++;
+      }
+      return n;
+    },
+
+    /**
+     * @return {TimlineThread} The thread identified by tid on this process,
+     * creating it if it doesn't exist.
+     */
+    getOrCreateThread: function(tid) {
+      if (!this.threads[tid])
+        this.threads[tid] = new TimelineThread(this, tid);
+      return this.threads[tid];
+    },
+
+    /**
+     * @return {TimlineCounter} The counter on this process named 'name',
+     * creating it if it doesn't exist.
+     */
+    getOrCreateCounter: function(cat, name) {
+      var id = cat + '.' + name;
+      if (!this.counters[id])
+        this.counters[id] = new TimelineCounter(this, id, name);
+      return this.counters[id];
+    }
+  };
+
+  /**
+   * Comparison between processes that orders by pid.
+   */
+  TimelineProcess.compare = function(x, y) {
+    return x.pid - y.pid;
+  };
+
+  /**
+   * The TimelineCpu represents a Cpu from the kernel's point of view.
+   * @constructor
+   */
+  function TimelineCpu(number) {
+    this.cpuNumber = number;
+    this.slices = [];
+    this.counters = {};
+  };
+
+  TimelineCpu.prototype = {
+    /**
+     * @return {TimlineCounter} The counter on this process named 'name',
+     * creating it if it doesn't exist.
+     */
+    getOrCreateCounter: function(cat, name) {
+      var id;
+      if (cat.length)
+        id = cat + '.' + name;
+      else
+        id = name;
+      if (!this.counters[id])
+        this.counters[id] = new TimelineCounter(this, id, name);
+      return this.counters[id];
+    },
+
+    /**
+     * Updates the minTimestamp and maxTimestamp fields based on the
+     * current slices attached to the cpu.
+     */
+    updateBounds: function() {
+      var values = [];
+      if (this.slices.length) {
+        this.minTimestamp = this.slices[0].start;
+        this.maxTimestamp = this.slices[this.slices.length - 1].end;
+      } else {
+        this.minTimestamp = undefined;
+        this.maxTimestamp = undefined;
+      }
+    }
+  };
+
+  /**
+   * Comparison between processes that orders by cpuNumber.
+   */
+  TimelineCpu.compare = function(x, y) {
+    return x.cpuNumber - y.cpuNumber;
+  };
+
+  // The color pallette is split in half, with the upper
+  // half of the pallette being the "highlighted" verison
+  // of the base color. So, color 7's highlighted form is
+  // 7 + (pallette.length / 2).
+  //
+  // These bright versions of colors are automatically generated
+  // from the base colors.
+  //
+  // Within the color pallette, there are "regular" colors,
+  // which can be used for random color selection, and
+  // reserved colors, which are used when specific colors
+  // need to be used, e.g. where red is desired.
+  const palletteBase = [
+    {r: 138, g: 113, b: 152},
+    {r: 175, g: 112, b: 133},
+    {r: 127, g: 135, b: 225},
+    {r: 93, g: 81, b: 137},
+    {r: 116, g: 143, b: 119},
+    {r: 178, g: 214, b: 122},
+    {r: 87, g: 109, b: 147},
+    {r: 119, g: 155, b: 95},
+    {r: 114, g: 180, b: 160},
+    {r: 132, g: 85, b: 103},
+    {r: 157, g: 210, b: 150},
+    {r: 148, g: 94, b: 86},
+    {r: 164, g: 108, b: 138},
+    {r: 139, g: 191, b: 150},
+    {r: 110, g: 99, b: 145},
+    {r: 80, g: 129, b: 109},
+    {r: 125, g: 140, b: 149},
+    {r: 93, g: 124, b: 132},
+    {r: 140, g: 85, b: 140},
+    {r: 104, g: 163, b: 162},
+    {r: 132, g: 141, b: 178},
+    {r: 131, g: 105, b: 147},
+    {r: 135, g: 183, b: 98},
+    {r: 152, g: 134, b: 177},
+    {r: 141, g: 188, b: 141},
+    {r: 133, g: 160, b: 210},
+    {r: 126, g: 186, b: 148},
+    {r: 112, g: 198, b: 205},
+    {r: 180, g: 122, b: 195},
+    {r: 203, g: 144, b: 152},
+    // Reserved Entires
+    {r: 182, g: 125, b: 143},
+    {r: 126, g: 200, b: 148},
+    {r: 133, g: 160, b: 210},
+    {r: 240, g: 240, b: 240}];
+
+  // Make sure this number tracks the number of reserved entries in the
+  // pallette.
+  const numReservedColorIds = 4;
+
+  function brighten(c) {
+    var k;
+    if (c.r >= 240 && c.g >= 240 && c.b >= 240)
+      k = -0.20;
+    else
+      k = 0.45;
+
+    return {r: Math.min(255, c.r + Math.floor(c.r * k)),
+      g: Math.min(255, c.g + Math.floor(c.g * k)),
+      b: Math.min(255, c.b + Math.floor(c.b * k))};
+  }
+  function colorToString(c) {
+    return 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')';
+  }
+
+  /**
+   * The number of color IDs that getStringColorId can choose from.
+   */
+  const numRegularColorIds = palletteBase.length - numReservedColorIds;
+  const highlightIdBoost = palletteBase.length;
+
+  const pallette = palletteBase.concat(palletteBase.map(brighten)).
+      map(colorToString);
+  /**
+   * Computes a simplistic hashcode of the provide name. Used to chose colors
+   * for slices.
+   * @param {string} name The string to hash.
+   */
+  function getStringHash(name) {
+    var hash = 0;
+    for (var i = 0; i < name.length; ++i)
+      hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF;
+    return hash;
+  }
+
+  /**
+   * Gets the color pallette.
+   */
+  function getPallette() {
+    return pallette;
+  }
+
+  /**
+   * @return {Number} The value to add to a color ID to get its highlighted
+   * colro ID. E.g. 7 + getPalletteHighlightIdBoost() yields a brightened from
+   * of 7's base color.
+   */
+  function getPalletteHighlightIdBoost() {
+    return highlightIdBoost;
+  }
+
+  /**
+   * @param {String} name The color name.
+   * @return {Number} The color ID for the given color name.
+   */
+  function getColorIdByName(name) {
+    if (name == 'iowait')
+      return numRegularColorIds;
+    if (name == 'running')
+      return numRegularColorIds + 1;
+    if (name == 'runnable')
+      return numRegularColorIds + 2;
+    if (name == 'sleeping')
+      return numRegularColorIds + 3;
+    throw 'Unrecognized color ' + name;
+  }
+
+  // Previously computed string color IDs. They are based on a stable hash, so
+  // it is safe to save them throughout the program time.
+  var stringColorIdCache = {};
+
+  /**
+   * @return {Number} A color ID that is stably associated to the provided via
+   * the getStringHash method. The color ID will be chosen from the regular
+   * ID space only, e.g. no reserved ID will be used.
+   */
+  function getStringColorId(string) {
+    if (stringColorIdCache[string] === undefined) {
+      var hash = getStringHash(string);
+      stringColorIdCache[string] = hash % numRegularColorIds;
+    }
+    return stringColorIdCache[string];
+  }
+
+  /**
+   * Builds a model from an array of TraceEvent objects.
+   * @param {Object=} opt_data The event data to import into the new model.
+   *     See TimelineModel.importEvents for details and more advanced ways to
+   *     import data.
+   * @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the
+   *     by 15%. Defaults to true.
+   * @constructor
+   */
+  function TimelineModel(opt_eventData, opt_zeroAndBoost) {
+    this.cpus = {};
+    this.processes = {};
+    this.importErrors = [];
+
+    if (opt_eventData)
+      this.importEvents(opt_eventData, opt_zeroAndBoost);
+  }
+
+  var importerConstructors = [];
+
+  /**
+   * Registers an importer. All registered importers are considered
+   * when processing an import request.
+   *
+   * @param {Function} importerConstructor The importer's constructor function.
+   */
+  TimelineModel.registerImporter = function(importerConstructor) {
+    importerConstructors.push(importerConstructor);
+  }
+
+  TimelineModel.prototype = {
+    __proto__: cr.EventTarget.prototype,
+
+    get numProcesses() {
+      var n = 0;
+      for (var p in this.processes)
+        n++;
+      return n;
+    },
+
+    /**
+     * @return {TimelineProcess} Gets a specific TimelineCpu or creates one if
+     * it does not exist.
+     */
+    getOrCreateCpu: function(cpuNumber) {
+      if (!this.cpus[cpuNumber])
+        this.cpus[cpuNumber] = new TimelineCpu(cpuNumber);
+      return this.cpus[cpuNumber];
+    },
+
+    /**
+     * @return {TimelineProcess} Gets a TimlineProcess for a specified pid or
+     * creates one if it does not exist.
+     */
+    getOrCreateProcess: function(pid) {
+      if (!this.processes[pid])
+        this.processes[pid] = new TimelineProcess(pid);
+      return this.processes[pid];
+    },
+
+    /**
+     * The import takes an array of json-ified TraceEvents and adds them into
+     * the TimelineModel as processes, threads, and slices.
+     */
+
+    /**
+     * Removes threads from the model that are fully empty.
+     */
+    pruneEmptyThreads: function() {
+      for (var pid in this.processes) {
+        var process = this.processes[pid];
+        var prunedThreads = {};
+        for (var tid in process.threads) {
+          var thread = process.threads[tid];
+
+          // Begin-events without matching end events leave a thread in a state
+          // where the toplevel subrows are empty but child subrows have
+          // entries. The autocloser will fix this up later. But, for the
+          // purposes of pruning, such threads need to be treated as having
+          // content.
+          var hasNonEmptySubrow = false;
+          for (var s = 0; s < thread.subRows.length; s++)
+            hasNonEmptySubrow |= thread.subRows[s].length > 0;
+
+          if (hasNonEmptySubrow || thread.nonNestedSubRows.legnth)
+            prunedThreads[tid] = thread;
+        }
+        process.threads = prunedThreads;
+      }
+    },
+
+    updateBounds: function() {
+      var wmin = Infinity;
+      var wmax = -wmin;
+      var hasData = false;
+
+      var threads = this.getAllThreads();
+      for (var tI = 0; tI < threads.length; tI++) {
+        var thread = threads[tI];
+        thread.updateBounds();
+        if (thread.minTimestamp != undefined &&
+            thread.maxTimestamp != undefined) {
+          wmin = Math.min(wmin, thread.minTimestamp);
+          wmax = Math.max(wmax, thread.maxTimestamp);
+          hasData = true;
+        }
+      }
+      var counters = this.getAllCounters();
+      for (var tI = 0; tI < counters.length; tI++) {
+        var counter = counters[tI];
+        counter.updateBounds();
+        if (counter.minTimestamp != undefined &&
+            counter.maxTimestamp != undefined) {
+          hasData = true;
+          wmin = Math.min(wmin, counter.minTimestamp);
+          wmax = Math.max(wmax, counter.maxTimestamp);
+        }
+      }
+
+      for (var cpuNumber in this.cpus) {
+        var cpu = this.cpus[cpuNumber];
+        cpu.updateBounds();
+        if (cpu.minTimestamp != undefined &&
+            cpu.maxTimestamp != undefined) {
+          hasData = true;
+          wmin = Math.min(wmin, cpu.minTimestamp);
+          wmax = Math.max(wmax, cpu.maxTimestamp);
+        }
+      }
+
+      if (hasData) {
+        this.minTimestamp = wmin;
+        this.maxTimestamp = wmax;
+      } else {
+        this.maxTimestamp = undefined;
+        this.minTimestamp = undefined;
+      }
+    },
+
+    shiftWorldToZero: function() {
+      if (this.minTimestamp === undefined)
+        return;
+      var timeBase = this.minTimestamp;
+      var threads = this.getAllThreads();
+      for (var tI = 0; tI < threads.length; tI++) {
+        var thread = threads[tI];
+        var shiftSubRow = function(subRow) {
+          for (var tS = 0; tS < subRow.length; tS++) {
+            var slice = subRow[tS];
+            slice.start = (slice.start - timeBase);
+          }
+        };
+
+        if (thread.cpuSlices)
+          shiftSubRow(thread.cpuSlices);
+
+        for (var tSR = 0; tSR < thread.subRows.length; tSR++) {
+          shiftSubRow(thread.subRows[tSR]);
+        }
+        for (var tSR = 0; tSR < thread.nonNestedSubRows.length; tSR++) {
+          shiftSubRow(thread.nonNestedSubRows[tSR]);
+        }
+      }
+      var counters = this.getAllCounters();
+      for (var tI = 0; tI < counters.length; tI++) {
+        var counter = counters[tI];
+        for (var sI = 0; sI < counter.timestamps.length; sI++)
+          counter.timestamps[sI] = (counter.timestamps[sI] - timeBase);
+      }
+      var cpus = this.getAllCpus();
+      for (var tI = 0; tI < cpus.length; tI++) {
+        var cpu = cpus[tI];
+        for (var sI = 0; sI < cpu.slices.length; sI++)
+          cpu.slices[sI].start = (cpu.slices[sI].start - timeBase);
+      }
+      this.updateBounds();
+    },
+
+    getAllThreads: function() {
+      var threads = [];
+      for (var pid in this.processes) {
+        var process = this.processes[pid];
+        for (var tid in process.threads) {
+          threads.push(process.threads[tid]);
+        }
+      }
+      return threads;
+    },
+
+    /**
+     * @return {Array} An array of all cpus in the model.
+     */
+    getAllCpus: function() {
+      var cpus = [];
+      for (var cpu in this.cpus)
+        cpus.push(this.cpus[cpu]);
+      return cpus;
+    },
+
+    /**
+     * @return {Array} An array of all processes in the model.
+     */
+    getAllProcesses: function() {
+      var processes = [];
+      for (var pid in this.processes)
+        processes.push(this.processes[pid]);
+      return processes;
+    },
+
+    /**
+     * @return {Array} An array of all the counters in the model.
+     */
+    getAllCounters: function() {
+      var counters = [];
+      for (var pid in this.processes) {
+        var process = this.processes[pid];
+        for (var tid in process.counters) {
+          counters.push(process.counters[tid]);
+        }
+      }
+      for (var cpuNumber in this.cpus) {
+        var cpu = this.cpus[cpuNumber];
+        for (var counterName in cpu.counters)
+          counters.push(cpu.counters[counterName]);
+      }
+      return counters;
+    },
+
+    /**
+     * Imports the provided events into the model. The eventData type
+     * is undefined and will be passed to all the timeline importers registered
+     * via TimelineModel.registerImporter. The first importer that returns true
+     * for canImport(events) will be used to import the events.
+     *
+     * @param {Object} events Events to import.
+     * @param {boolean} isChildImport True the eventData being imported is an
+     *     additional trace after the primary eventData.
+     */
+    importOneTrace_: function(eventData, isAdditionalImport) {
+      var importerConstructor;
+      for (var i = 0; i < importerConstructors.length; ++i) {
+        if (importerConstructors[i].canImport(eventData)) {
+          importerConstructor = importerConstructors[i];
+          break;
+        }
+      }
+      if (!importerConstructor)
+        throw 'Could not find an importer for the provided eventData.';
+
+      var importer = new importerConstructor(
+          this, eventData, isAdditionalImport);
+      importer.importEvents();
+      this.pruneEmptyThreads();
+    },
+
+    /**
+     * Imports the provided traces into the model. The eventData type
+     * is undefined and will be passed to all the timeline importers registered
+     * via TimelineModel.registerImporter. The first importer that returns true
+     * for canImport(events) will be used to import the events.
+     *
+     * The primary trace is provided via the eventData variable. If multiple
+     * traces are to be imported, specify the first one as events, and the
+     * remainder in the opt_additionalEventData array.
+     *
+     * @param {Object} eventData Events to import.
+     * @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the
+     *     by 15%. Defaults to true.
+     * @param {Array=} opt_additionalEventData An array of eventData objects
+     *     (e.g. array of arrays) to
+     * import after importing the primary events.
+     */
+    importEvents: function(eventData,
+                           opt_zeroAndBoost, opt_additionalEventData) {
+      if (opt_zeroAndBoost === undefined)
+        opt_zeroAndBoost = true;
+
+      this.importOneTrace_(eventData, false);
+      if (opt_additionalEventData) {
+        for (var i = 0; i < opt_additionalEventData.length; ++i) {
+          this.importOneTrace_(opt_additionalEventData[i], true);
+        }
+      }
+
+      this.updateBounds();
+
+      if (opt_zeroAndBoost)
+        this.shiftWorldToZero();
+
+      if (opt_zeroAndBoost &&
+          this.minTimestamp !== undefined &&
+          this.maxTimestamp !== undefined) {
+        var boost = (this.maxTimestamp - this.minTimestamp) * 0.15;
+        this.minTimestamp = this.minTimestamp - boost;
+        this.maxTimestamp = this.maxTimestamp + boost;
+      }
+    }
+  };
+
+  return {
+    getPallette: getPallette,
+    getPalletteHighlightIdBoost: getPalletteHighlightIdBoost,
+    getColorIdByName: getColorIdByName,
+    getStringHash: getStringHash,
+    getStringColorId: getStringColorId,
+
+    TimelineSlice: TimelineSlice,
+    TimelineThread: TimelineThread,
+    TimelineCounter: TimelineCounter,
+    TimelineProcess: TimelineProcess,
+    TimelineCpu: TimelineCpu,
+    TimelineModel: TimelineModel
+  };
+});
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Imports text files in the Linux event trace format into the
+ * timeline model. This format is output both by sched_trace and by Linux's perf
+ * tool.
+ *
+ * This importer assumes the events arrive as a string. The unit tests provide
+ * examples of the trace format.
+ *
+ * Linux scheduler traces use a definition for 'pid' that is different than
+ * tracing uses. Whereas tracing uses pid to identify a specific process, a pid
+ * in a linux trace refers to a specific thread within a process. Within this
+ * file, we the definition used in Linux traces, as it improves the importing
+ * code's readability.
+ */
+cr.define('tracing', function() {
+  /**
+   * Represents the scheduling state for a single thread.
+   * @constructor
+   */
+  function CpuState(cpu) {
+    this.cpu = cpu;
+  }
+
+  CpuState.prototype = {
+    __proto__: Object.prototype,
+
+    /**
+     * Switches the active pid on this Cpu. If necessary, add a TimelineSlice
+     * to the cpu representing the time spent on that Cpu since the last call to
+     * switchRunningLinuxPid.
+     */
+    switchRunningLinuxPid: function(importer, prevState, ts, pid, comm, prio) {
+      // Generate a slice if the last active pid was not the idle task
+      if (this.lastActivePid !== undefined && this.lastActivePid != 0) {
+        var duration = ts - this.lastActiveTs;
+        var thread = importer.threadsByLinuxPid[this.lastActivePid];
+        if (thread)
+          name = thread.userFriendlyName;
+        else
+          name = this.lastActiveComm;
+
+        var slice = new tracing.TimelineSlice(name,
+                                              tracing.getStringColorId(name),
+                                              this.lastActiveTs,
+                                              {comm: this.lastActiveComm,
+                                               tid: this.lastActivePid,
+                                               prio: this.lastActivePrio,
+                                               stateWhenDescheduled: prevState
+                                              },
+                                              duration);
+        this.cpu.slices.push(slice);
+      }
+
+      this.lastActiveTs = ts;
+      this.lastActivePid = pid;
+      this.lastActiveComm = comm;
+      this.lastActivePrio = prio;
+    }
+  };
+
+  function ThreadState(tid) {
+    this.openSlices = [];
+  }
+
+  /**
+   * Imports linux perf events into a specified model.
+   * @constructor
+   */
+  function LinuxPerfImporter(model, events, isAdditionalImport) {
+    this.isAdditionalImport_ = isAdditionalImport;
+    this.model_ = model;
+    this.events_ = events;
+    this.clockSyncRecords_ = [];
+    this.cpuStates_ = {};
+    this.kernelThreadStates_ = {};
+    this.buildMapFromLinuxPidsToTimelineThreads();
+
+    // To allow simple indexing of threads, we store all the threads by their
+    // kernel KPID. The KPID is a unique key for a thread in the trace.
+    this.threadStateByKPID_ = {};
+  }
+
+  TestExports = {};
+
+  // Matches the generic trace record:
+  //          <idle>-0     [001]  1.23: sched_switch
+  var lineRE = /^\s*(.+?)\s+\[(\d+)\]\s*(\d+\.\d+):\s+(\S+):\s(.*)$/;
+  TestExports.lineRE = lineRE;
+
+  // Matches the sched_switch record
+  var schedSwitchRE = new RegExp(
+      'prev_comm=(.+) prev_pid=(\\d+) prev_prio=(\\d+) prev_state=(\\S) ==> ' +
+      'next_comm=(.+) next_pid=(\\d+) next_prio=(\\d+)');
+  TestExports.schedSwitchRE = schedSwitchRE;
+
+  // Matches the sched_wakeup record
+  var schedWakeupRE =
+      /comm=(.+) pid=(\d+) prio=(\d+) success=(\d+) target_cpu=(\d+)/;
+  TestExports.schedWakeupRE = schedWakeupRE;
+
+  // Matches the trace_event_clock_sync record
+  //  0: trace_event_clock_sync: parent_ts=19581477508
+  var traceEventClockSyncRE = /trace_event_clock_sync: parent_ts=(\d+\.?\d*)/;
+  TestExports.traceEventClockSyncRE = traceEventClockSyncRE;
+
+  // Matches the workqueue_execute_start record
+  //  workqueue_execute_start: work struct c7a8a89c: function MISRWrapper
+  var workqueueExecuteStartRE = /work struct (.+): function (\S+)/;
+
+  // Matches the workqueue_execute_start record
+  //  workqueue_execute_end: work struct c7a8a89c
+  var workqueueExecuteEndRE = /work struct (.+)/;
+
+  /**
+   * Guesses whether the provided events is a Linux perf string.
+   * Looks for the magic string "# tracer" at the start of the file,
+   * or the typical task-pid-cpu-timestamp-function sequence of a typical
+   * trace's body.
+   *
+   * @return {boolean} True when events is a linux perf array.
+   */
+  LinuxPerfImporter.canImport = function(events) {
+    if (!(typeof(events) === 'string' || events instanceof String))
+      return false;
+
+    if (/^# tracer:/.exec(events))
+      return true;
+
+    var m = /^(.+)\n/.exec(events);
+    if (m)
+      events = m[1];
+    if (lineRE.exec(events))
+      return true;
+
+    return false;
+  };
+
+  LinuxPerfImporter.prototype = {
+    __proto__: Object.prototype,
+
+    /**
+     * Precomputes a lookup table from linux pids back to existing
+     * TimelineThreads. This is used during importing to add information to each
+     * timeline thread about whether it was running, descheduled, sleeping, et
+     * cetera.
+     */
+    buildMapFromLinuxPidsToTimelineThreads: function() {
+      this.threadsByLinuxPid = {};
+      this.model_.getAllThreads().forEach(
+          function(thread) {
+            this.threadsByLinuxPid[thread.tid] = thread;
+          }.bind(this));
+    },
+
+    /**
+     * @return {CpuState} A CpuState corresponding to the given cpuNumber.
+     */
+    getOrCreateCpuState: function(cpuNumber) {
+      if (!this.cpuStates_[cpuNumber]) {
+        var cpu = this.model_.getOrCreateCpu(cpuNumber);
+        this.cpuStates_[cpuNumber] = new CpuState(cpu);
+      }
+      return this.cpuStates_[cpuNumber];
+    },
+
+    /**
+     * @return {number} The pid extracted from the kernel thread name.
+     */
+    parsePid: function(kernelThreadName) {
+        var pid = /.+-(\d+)/.exec(kernelThreadName)[1];
+        pid = parseInt(pid);
+        return pid;
+    },
+
+    /**
+     * @return {number} The string portion of the thread extracted from the
+     * kernel thread name.
+     */
+    parseThreadName: function(kernelThreadName) {
+        return /(.+)-\d+/.exec(kernelThreadName)[1];
+    },
+
+    /**
+     * @return {TimelinThread} A thread corresponding to the kernelThreadName
+     */
+    getOrCreateKernelThread: function(kernelThreadName) {
+      if (!this.kernelThreadStates_[kernelThreadName]) {
+        pid = this.parsePid(kernelThreadName);
+
+        var thread = this.model_.getOrCreateProcess(pid).getOrCreateThread(pid);
+        thread.name = kernelThreadName;
+        this.kernelThreadStates_[kernelThreadName] = {
+          pid: pid,
+          thread: thread,
+          openSlice: undefined,
+          openSliceTS: undefined
+        };
+        this.threadsByLinuxPid[pid] = thread;
+      }
+      return this.kernelThreadStates_[kernelThreadName];
+    },
+
+    /**
+     * Imports the data in this.events_ into model_.
+     */
+    importEvents: function() {
+      this.importCpuData();
+      if (!this.alignClocks())
+        return;
+      this.buildPerThreadCpuSlicesFromCpuState();
+    },
+
+    /**
+     * Builds the cpuSlices array on each thread based on our knowledge of what
+     * each Cpu is doing.  This is done only for TimelineThreads that are
+     * already in the model, on the assumption that not having any traced data
+     * on a thread means that it is not of interest to the user.
+     */
+    buildPerThreadCpuSlicesFromCpuState: function() {
+      // Push the cpu slices to the threads that they run on.
+      for (var cpuNumber in this.cpuStates_) {
+        var cpuState = this.cpuStates_[cpuNumber];
+        var cpu = cpuState.cpu;
+
+        for (var i = 0; i < cpu.slices.length; i++) {
+          var slice = cpu.slices[i];
+
+          var thread = this.threadsByLinuxPid[slice.args.tid];
+          if (!thread)
+            continue;
+          if (!thread.tempCpuSlices)
+            thread.tempCpuSlices = [];
+
+          // Because Chrome's Array.sort is not a stable sort, we need to keep
+          // the slice index around to keep slices with identical start times in
+          // the proper order when sorting them.
+          slice.index = i;
+
+          thread.tempCpuSlices.push(slice);
+        }
+      }
+
+      // Create slices for when the thread is not running.
+      var runningId = tracing.getColorIdByName('running');
+      var runnableId = tracing.getColorIdByName('runnable');
+      var sleepingId = tracing.getColorIdByName('sleeping');
+      var ioWaitId = tracing.getColorIdByName('iowait');
+      this.model_.getAllThreads().forEach(function(thread) {
+        if (!thread.tempCpuSlices)
+          return;
+        var origSlices = thread.tempCpuSlices;
+        delete thread.tempCpuSlices;
+
+        origSlices.sort(function(x, y) {
+          var delta = x.start - y.start;
+          if (delta == 0) {
+            // Break ties using the original slice ordering.
+            return x.index - y.index;
+          } else {
+            return delta;
+          }
+        });
+
+        // Walk the slice list and put slices between each original slice
+        // to show when the thread isn't running
+        var slices = [];
+        if (origSlices.length) {
+          var slice = origSlices[0];
+          slices.push(new tracing.TimelineSlice('Running', runningId,
+              slice.start, {}, slice.duration));
+        }
+        for (var i = 1; i < origSlices.length; i++) {
+          var prevSlice = origSlices[i - 1];
+          var nextSlice = origSlices[i];
+          var midDuration = nextSlice.start - prevSlice.end;
+          if (prevSlice.args.stateWhenDescheduled == 'S') {
+            slices.push(new tracing.TimelineSlice('Sleeping', sleepingId,
+                prevSlice.end, {}, midDuration));
+          } else if (prevSlice.args.stateWhenDescheduled == 'R') {
+            slices.push(new tracing.TimelineSlice('Runnable', runnableId,
+                prevSlice.end, {}, midDuration));
+          } else if (prevSlice.args.stateWhenDescheduled == 'D') {
+            slices.push(new tracing.TimelineSlice('I/O Wait', ioWaitId,
+                prevSlice.end, {}, midDuration));
+          } else if (prevSlice.args.stateWhenDescheduled == 'T') {
+            slices.push(new tracing.TimelineSlice('__TASK_STOPPED', ioWaitId,
+                prevSlice.end, {}, midDuration));
+          } else if (prevSlice.args.stateWhenDescheduled == 't') {
+            slices.push(new tracing.TimelineSlice('debug', ioWaitId,
+                prevSlice.end, {}, midDuration));
+          } else if (prevSlice.args.stateWhenDescheduled == 'Z') {
+            slices.push(new tracing.TimelineSlice('Zombie', ioWaitId,
+                prevSlice.end, {}, midDuration));
+          } else if (prevSlice.args.stateWhenDescheduled == 'X') {
+            slices.push(new tracing.TimelineSlice('Exit Dead', ioWaitId,
+                prevSlice.end, {}, midDuration));
+          } else if (prevSlice.args.stateWhenDescheduled == 'x') {
+            slices.push(new tracing.TimelineSlice('Task Dead', ioWaitId,
+                prevSlice.end, {}, midDuration));
+          } else if (prevSlice.args.stateWhenDescheduled == 'W') {
+            slices.push(new tracing.TimelineSlice('WakeKill', ioWaitId,
+                prevSlice.end, {}, midDuration));
+          } else {
+            throw 'Unrecognized state: ' + prevSlice.args.stateWhenDescheduled;
+          }
+
+          slices.push(new tracing.TimelineSlice('Running', runningId,
+              nextSlice.start, {}, nextSlice.duration));
+        }
+        thread.cpuSlices = slices;
+      });
+    },
+
+    /**
+     * Walks the slices stored on this.cpuStates_ and adjusts their timestamps
+     * based on any alignment metadata we discovered.
+     */
+    alignClocks: function() {
+      if (this.clockSyncRecords_.length == 0) {
+        // If this is an additional import, and no clock syncing records were
+        // found, then abort the import. Otherwise, just skip clock alignment.
+        if (!this.isAdditionalImport_)
+          return;
+
+        // Remove the newly imported CPU slices from the model.
+        this.abortImport();
+        return false;
+      }
+
+      // Shift all the slice times based on the sync record.
+      var sync = this.clockSyncRecords_[0];
+      var timeShift = sync.parentTS - sync.perfTS;
+      for (var cpuNumber in this.cpuStates_) {
+        var cpuState = this.cpuStates_[cpuNumber];
+        var cpu = cpuState.cpu;
+
+        for (var i = 0; i < cpu.slices.length; i++) {
+          var slice = cpu.slices[i];
+          slice.start = slice.start + timeShift;
+          slice.duration = slice.duration;
+        }
+
+        for (var counterName in cpu.counters) {
+          var counter = cpu.counters[counterName];
+          for (var sI = 0; sI < counter.timestamps.length; sI++)
+            counter.timestamps[sI] = (counter.timestamps[sI] + timeShift);
+        }
+      }
+      for (var kernelThreadName in this.kernelThreadStates_) {
+        var kthread = this.kernelThreadStates_[kernelThreadName];
+        var thread = kthread.thread;
+        for (var i = 0; i < thread.subRows[0].length; i++) {
+          thread.subRows[0][i].start += timeShift;
+        }
+      }
+      return true;
+    },
+
+    /**
+     * Removes any data that has been added to the model because of an error
+     * detected during the import.
+     */
+    abortImport: function() {
+      if (this.pushedEventsToThreads)
+        throw 'Cannot abort, have alrady pushedCpuDataToThreads.';
+
+      for (var cpuNumber in this.cpuStates_)
+        delete this.model_.cpus[cpuNumber];
+      for (var kernelThreadName in this.kernelThreadStates_) {
+        var kthread = this.kernelThreadStates_[kernelThreadName];
+        var thread = kthread.thread;
+        var process = thread.parent;
+        delete process.threads[thread.tid];
+        delete this.model_.processes[process.pid];
+      }
+      this.model_.importErrors.push(
+          'Cannot import kernel trace without a clock sync.');
+    },
+
+    /**
+     * Records the fact that a pid has become runnable. This data will
+     * eventually get used to derive each thread's cpuSlices array.
+     */
+    markPidRunnable: function(ts, pid, comm, prio) {
+      // TODO(nduca): implement this functionality.
+    },
+
+    /**
+     * Helper to process a 'begin' event (e.g. initiate a slice).
+     * @param {ThreadState} state Thread state (holds slices).
+     * @param {string} name The trace event name.
+     * @param {number} ts The trace event begin timestamp.
+     */
+    processBegin: function(state, tname, name, ts, pid, tid) {
+      var colorId = tracing.getStringColorId(name);
+      var slice = new tracing.TimelineSlice(name, colorId, ts, null);
+      // XXX: Should these be removed from the slice before putting it into the
+      // model?
+      slice.pid = pid;
+      slice.tid = tid;
+      slice.threadName = tname;
+      state.openSlices.push(slice);
+    },
+
+    /**
+     * Helper to process an 'end' event (e.g. close a slice).
+     * @param {ThreadState} state Thread state (holds slices).
+     * @param {number} ts The trace event begin timestamp.
+     */
+    processEnd: function(state, ts) {
+      if (state.openSlices.length == 0) {
+        // Ignore E events that are unmatched.
+        return;
+      }
+      var slice = state.openSlices.pop();
+      slice.duration = ts - slice.start;
+
+      // Store the slice on the correct subrow.
+      var thread = this.model_.getOrCreateProcess(slice.pid).
+          getOrCreateThread(slice.tid);
+      if (!thread.name)
+        thread.name = slice.threadName;
+      this.threadsByLinuxPid[slice.tid] = thread;
+      var subRowIndex = state.openSlices.length;
+      thread.getSubrow(subRowIndex).push(slice);
+
+      // Add the slice to the subSlices array of its parent.
+      if (state.openSlices.length) {
+        var parentSlice = state.openSlices[state.openSlices.length - 1];
+        parentSlice.subSlices.push(slice);
+      }
+    },
+
+    /**
+     * Helper function that closes any open slices. This happens when a trace
+     * ends before an 'E' phase event can get posted. When that happens, this
+     * closes the slice at the highest timestamp we recorded and sets the
+     * didNotFinish flag to true.
+     */
+    autoCloseOpenSlices: function() {
+      // We need to know the model bounds in order to assign an end-time to
+      // the open slices.
+      this.model_.updateBounds();
+
+      // The model's max value in the trace is wrong at this point if there are
+      // un-closed events. To close those events, we need the true global max
+      // value. To compute this, build a list of timestamps that weren't
+      // included in the max calculation, then compute the real maximum based
+      // on that.
+      var openTimestamps = [];
+      for (var kpid in this.threadStateByKPID_) {
+        var state = this.threadStateByKPID_[kpid];
+        for (var i = 0; i < state.openSlices.length; i++) {
+          var slice = state.openSlices[i];
+          openTimestamps.push(slice.start);
+          for (var s = 0; s < slice.subSlices.length; s++) {
+            var subSlice = slice.subSlices[s];
+            openTimestamps.push(subSlice.start);
+            if (subSlice.duration)
+              openTimestamps.push(subSlice.end);
+          }
+        }
+      }
+
+      // Figure out the maximum value of model.maxTimestamp and
+      // Math.max(openTimestamps). Made complicated by the fact that the model
+      // timestamps might be undefined.
+      var realMaxTimestamp;
+      if (this.model_.maxTimestamp) {
+        realMaxTimestamp = Math.max(this.model_.maxTimestamp,
+                                    Math.max.apply(Math, openTimestamps));
+      } else {
+        realMaxTimestamp = Math.max.apply(Math, openTimestamps);
+      }
+
+      // Automatically close any slices are still open. These occur in a number
+      // of reasonable situations, e.g. deadlock. This pass ensures the open
+      // slices make it into the final model.
+      for (var kpid in this.threadStateByKPID_) {
+        var state = this.threadStateByKPID_[kpid];
+        while (state.openSlices.length > 0) {
+          var slice = state.openSlices.pop();
+          slice.duration = realMaxTimestamp - slice.start;
+          slice.didNotFinish = true;
+
+          // Store the slice on the correct subrow.
+          var thread = this.model_.getOrCreateProcess(slice.pid)
+                           .getOrCreateThread(slice.tid);
+          var subRowIndex = state.openSlices.length;
+          thread.getSubrow(subRowIndex).push(slice);
+
+          // Add the slice to the subSlices array of its parent.
+          if (state.openSlices.length) {
+            var parentSlice = state.openSlices[state.openSlices.length - 1];
+            parentSlice.subSlices.push(slice);
+          }
+        }
+      }
+    },
+
+    /**
+     * Helper that creates and adds samples to a TimelineCounter object based on
+     * 'C' phase events.
+     */
+    processCounter: function(name, ts, value, pid) {
+      var ctr = this.model_.getOrCreateProcess(pid)
+          .getOrCreateCounter('', name);
+
+      // Initialize the counter's series fields if needed.
+      //
+      if (ctr.numSeries == 0) {
+        ctr.seriesNames.push('state');
+        ctr.seriesColors.push(
+            tracing.getStringColorId(ctr.name + '.' + 'state'));
+      }
+
+      // Add the sample values.
+      ctr.timestamps.push(ts);
+      ctr.samples.push(value);
+    },
+
+
+    /**
+     * Walks the this.events_ structure and creates TimelineCpu objects.
+     */
+    importCpuData: function() {
+      this.lines_ = this.events_.split('\n');
+
+      for (var lineNumber = 0; lineNumber < this.lines_.length; ++lineNumber) {
+        var line = this.lines_[lineNumber];
+        if (/^#/.exec(line) || line.length == 0)
+          continue;
+        var eventBase = lineRE.exec(line);
+        if (!eventBase) {
+          this.model_.importErrors.push('Line ' + (lineNumber + 1) +
+              ': Unrecognized line: ' + line);
+          continue;
+        }
+
+        var cpuState = this.getOrCreateCpuState(parseInt(eventBase[2]));
+        var ts = parseFloat(eventBase[3]) * 1000;
+
+        var eventName = eventBase[4];
+
+        if (eventName == 'sched_switch') {
+          var event = schedSwitchRE.exec(eventBase[5]);
+          if (!event) {
+            this.model_.importErrors.push('Line ' + (lineNumber + 1) +
+                ': Malformed sched_switch event');
+            continue;
+          }
+
+          var prevState = event[4];
+          var nextComm = event[5];
+          var nextPid = parseInt(event[6]);
+          var nextPrio = parseInt(event[7]);
+          cpuState.switchRunningLinuxPid(
+              this, prevState, ts, nextPid, nextComm, nextPrio);
+
+        } else if (eventName == 'sched_wakeup') {
+          var event = schedWakeupRE.exec(eventBase[5]);
+          if (!event) {
+            this.model_.importErrors.push('Line ' + (lineNumber + 1) +
+                ': Malformed sched_wakeup event');
+            continue;
+          }
+
+          var comm = event[1];
+          var pid = parseInt(event[2]);
+          var prio = parseInt(event[3]);
+          this.markPidRunnable(ts, pid, comm, prio);
+
+        } else if (eventName == 'cpu_frequency') {
+          var event = /state=(\d+) cpu_id=(\d+)/.exec(eventBase[5]);
+          if (!event) {
+            this.model_.importErrors.push('Line ' + (lineNumber + 1) +
+                ': Malformed cpu_frequency event');
+            continue;
+          }
+          var targetCpuNumber = parseInt(event[2]);
+          var targetCpu = this.getOrCreateCpuState(targetCpuNumber);
+          var freqCounter =
+              targetCpu.cpu.getOrCreateCounter('', 'Frequency');
+          if (freqCounter.numSeries == 0) {
+            freqCounter.seriesNames.push('state');
+            freqCounter.seriesColors.push(
+                tracing.getStringColorId(freqCounter.name + '.' + 'state'));
+          }
+          var freqState = parseInt(event[1]);
+          freqCounter.timestamps.push(ts);
+          freqCounter.samples.push(freqState);
+        } else if (eventName == 'cpufreq_interactive_already' ||
+                   eventName == 'cpufreq_interactive_target') {
+          var event = /cpu=(\d+) load=(\d+) cur=(\d+) targ=(\d+)/.
+              exec(eventBase[5]);
+          if (!event) {
+            this.model_.importErrors.push('Line ' + (lineNumber + 1) +
+                ': Malformed cpufreq_interactive_* event');
+            continue;
+          }
+          var targetCpuNumber = parseInt(event[1]);
+          var targetCpu = this.getOrCreateCpuState(targetCpuNumber);
+          var loadCounter =
+              targetCpu.cpu.getOrCreateCounter('', 'Load');
+          if (loadCounter.numSeries == 0) {
+            loadCounter.seriesNames.push('state');
+            loadCounter.seriesColors.push(
+                tracing.getStringColorId(loadCounter.name + '.' + 'state'));
+          }
+          var loadState = parseInt(event[2]);
+          loadCounter.timestamps.push(ts);
+          loadCounter.samples.push(loadState);
+          loadCounter.maxTotal = 100;
+          loadCounter.skipUpdateBounds = true;
+        } else if (eventName == 'workqueue_execute_start') {
+          var event = workqueueExecuteStartRE.exec(eventBase[5]);
+          if (!event) {
+            this.model_.importErrors.push('Line ' + (lineNumber + 1) +
+                ': Malformed workqueue_execute_start event');
+            continue;
+          }
+          var kthread = this.getOrCreateKernelThread(eventBase[1]);
+          kthread.openSliceTS = ts;
+          kthread.openSlice = event[2];
+
+        } else if (eventName == 'workqueue_execute_end') {
+          var event = workqueueExecuteEndRE.exec(eventBase[5]);
+          if (!event) {
+            this.model_.importErrors.push('Line ' + (lineNumber + 1) +
+                ': Malformed workqueue_execute_start event');
+            continue;
+          }
+          var kthread = this.getOrCreateKernelThread(eventBase[1]);
+          if (kthread.openSlice) {
+            var slice = new tracing.TimelineSlice(kthread.openSlice,
+                tracing.getStringColorId(kthread.openSlice),
+                kthread.openSliceTS,
+                {},
+                ts - kthread.openSliceTS);
+
+            kthread.thread.subRows[0].push(slice);
+          }
+          kthread.openSlice = undefined;
+
+        } else if (eventName == '0') { // trace_mark's show up with 0 prefixes.
+          var event = traceEventClockSyncRE.exec(eventBase[5]);
+          if (event)
+            this.clockSyncRecords_.push({
+              perfTS: ts,
+              parentTS: event[1] * 1000
+            });
+          else {
+            var tid = this.parsePid(eventBase[1]);
+            var tname = this.parseThreadName(eventBase[1]);
+            var kpid = tid;
+
+            if (!(kpid in this.threadStateByKPID_))
+              this.threadStateByKPID_[kpid] = new ThreadState();
+            var state = this.threadStateByKPID_[kpid];
+
+            var event = eventBase[5].split('|')
+            switch (event[0]) {
+            case 'B':
+              var pid = parseInt(event[1]);
+              var name = event[2];
+              this.processBegin(state, tname, name, ts, pid, tid);
+              break;
+            case 'E':
+              this.processEnd(state, ts);
+              break;
+            case 'C':
+              var pid = parseInt(event[1]);
+              var name = event[2];
+              var value = parseInt(event[3]);
+              this.processCounter(name, ts, value, pid);
+              break;
+            default:
+              this.model_.importErrors.push('Line ' + (lineNumber + 1) +
+                  ': Unrecognized event: ' + eventBase[5]);
+            }
+          }
+        }
+      }
+
+      // Autoclose any open slices.
+      var hasOpenSlices = false;
+      for (var kpid in this.threadStateByKPID_) {
+        var state = this.threadStateByKPID_[kpid];
+        hasOpenSlices |= state.openSlices.length > 0;
+      }
+      if (hasOpenSlices)
+        this.autoCloseOpenSlices();
+    }
+  };
+
+  tracing.TimelineModel.registerImporter(LinuxPerfImporter);
+
+  return {
+    LinuxPerfImporter: LinuxPerfImporter,
+    _LinuxPerfImporterTestExports: TestExports
+  };
+
+});
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview TraceEventImporter imports TraceEvent-formatted data
+ * into the provided timeline model.
+ */
+cr.define('tracing', function() {
+  function ThreadState(tid) {
+    this.openSlices = [];
+    this.openNonNestedSlices = {};
+  }
+
+  function TraceEventImporter(model, eventData) {
+    this.model_ = model;
+
+    if (typeof(eventData) === 'string' || eventData instanceof String) {
+      // If the event data begins with a [, then we know it should end with a ].
+      // The reason we check for this is because some tracing implementations
+      // cannot guarantee that a ']' gets written to the trace file. So, we are
+      // forgiving and if this is obviously the case, we fix it up before
+      // throwing the string at JSON.parse.
+      if (eventData[0] == '[') {
+        n = eventData.length;
+        if (eventData[n - 1] != ']' && eventData[n - 1] != '\n') {
+          eventData = eventData + ']';
+        } else if (eventData[n - 2] != ']' && eventData[n - 1] == '\n') {
+          eventData = eventData + ']';
+        } else if (eventData[n - 3] != ']' && eventData[n - 2] == '\r' &&
+            eventData[n - 1] == '\n') {
+          eventData = eventData + ']';
+        }
+      }
+      this.events_ = JSON.parse(eventData);
+
+    } else {
+      this.events_ = eventData;
+    }
+
+    // Some trace_event implementations put the actual trace events
+    // inside a container. E.g { ... , traceEvents: [ ] }
+    //
+    // If we see that, just pull out the trace events.
+    if (this.events_.traceEvents)
+      this.events_ = this.events_.traceEvents;
+
+    // To allow simple indexing of threads, we store all the threads by a
+    // PTID. A ptid is a pid and tid joined together x:y fashion, eg
+    // 1024:130. The ptid is a unique key for a thread in the trace.
+    this.threadStateByPTID_ = {};
+  }
+
+  /**
+   * @return {boolean} Whether obj is a TraceEvent array.
+   */
+  TraceEventImporter.canImport = function(eventData) {
+    // May be encoded JSON. But we dont want to parse it fully yet.
+    // Use a simple heuristic:
+    //   - eventData that starts with [ are probably trace_event
+    //   - eventData that starts with { are probably trace_event
+    // May be encoded JSON. Treat files that start with { as importable by us.
+    if (typeof(eventData) === 'string' || eventData instanceof String) {
+      return eventData[0] == '{' || eventData[0] == '[';
+    }
+
+    // Might just be an array of events
+    if (eventData instanceof Array && eventData[0].ph)
+      return true;
+
+    // Might be an object with a traceEvents field in it.
+    if (eventData.traceEvents)
+      return eventData.traceEvents instanceof Array &&
+          eventData.traceEvents[0].ph;
+
+    return false;
+  };
+
+  TraceEventImporter.prototype = {
+
+    __proto__: Object.prototype,
+
+    /**
+     * Helper to process a 'begin' event (e.g. initiate a slice).
+     * @param {ThreadState} state Thread state (holds slices).
+     * @param {Object} event The current trace event.
+     */
+    processBegin: function(index, state, event) {
+      var colorId = tracing.getStringColorId(event.name);
+      var slice =
+          { index: index,
+            slice: new tracing.TimelineSlice(event.name, colorId,
+                                             event.ts / 1000,
+                                             event.args) };
+
+      if (event.uts)
+        slice.slice.startInUserTime = event.uts / 1000;
+
+      if (event.args['ui-nest'] === '0') {
+        var sliceID = event.name;
+        for (var x in event.args)
+          sliceID += ';' + event.args[x];
+        if (state.openNonNestedSlices[sliceID])
+          this.model_.importErrors.push('Event ' + sliceID + ' already open.');
+        state.openNonNestedSlices[sliceID] = slice;
+      } else {
+        state.openSlices.push(slice);
+      }
+    },
+
+    /**
+     * Helper to process an 'end' event (e.g. close a slice).
+     * @param {ThreadState} state Thread state (holds slices).
+     * @param {Object} event The current trace event.
+     */
+    processEnd: function(state, event) {
+      if (event.args['ui-nest'] === '0') {
+        var sliceID = event.name;
+        for (var x in event.args)
+          sliceID += ';' + event.args[x];
+        var slice = state.openNonNestedSlices[sliceID];
+        if (!slice)
+          return;
+        slice.slice.duration = (event.ts / 1000) - slice.slice.start;
+        if (event.uts)
+          slice.durationInUserTime = (event.uts / 1000) -
+              slice.slice.startInUserTime;
+
+        // Store the slice in a non-nested subrow.
+        var thread =
+            this.model_.getOrCreateProcess(event.pid).
+                getOrCreateThread(event.tid);
+        thread.addNonNestedSlice(slice.slice);
+        delete state.openNonNestedSlices[name];
+      } else {
+        if (state.openSlices.length == 0) {
+          // Ignore E events that are unmatched.
+          return;
+        }
+        var slice = state.openSlices.pop().slice;
+        slice.duration = (event.ts / 1000) - slice.start;
+        if (event.uts)
+          slice.durationInUserTime = (event.uts / 1000) - slice.startInUserTime;
+
+        // Store the slice on the correct subrow.
+        var thread = this.model_.getOrCreateProcess(event.pid).
+            getOrCreateThread(event.tid);
+        var subRowIndex = state.openSlices.length;
+        thread.getSubrow(subRowIndex).push(slice);
+
+        // Add the slice to the subSlices array of its parent.
+        if (state.openSlices.length) {
+          var parentSlice = state.openSlices[state.openSlices.length - 1];
+          parentSlice.slice.subSlices.push(slice);
+        }
+      }
+    },
+
+    /**
+     * Helper function that closes any open slices. This happens when a trace
+     * ends before an 'E' phase event can get posted. When that happens, this
+     * closes the slice at the highest timestamp we recorded and sets the
+     * didNotFinish flag to true.
+     */
+    autoCloseOpenSlices: function() {
+      // We need to know the model bounds in order to assign an end-time to
+      // the open slices.
+      this.model_.updateBounds();
+
+      // The model's max value in the trace is wrong at this point if there are
+      // un-closed events. To close those events, we need the true global max
+      // value. To compute this, build a list of timestamps that weren't
+      // included in the max calculation, then compute the real maximum based
+      // on that.
+      var openTimestamps = [];
+      for (var ptid in this.threadStateByPTID_) {
+        var state = this.threadStateByPTID_[ptid];
+        for (var i = 0; i < state.openSlices.length; i++) {
+          var slice = state.openSlices[i];
+          openTimestamps.push(slice.slice.start);
+          for (var s = 0; s < slice.slice.subSlices.length; s++) {
+            var subSlice = slice.slice.subSlices[s];
+            openTimestamps.push(subSlice.start);
+            if (subSlice.duration)
+              openTimestamps.push(subSlice.end);
+          }
+        }
+      }
+
+      // Figure out the maximum value of model.maxTimestamp and
+      // Math.max(openTimestamps). Made complicated by the fact that the model
+      // timestamps might be undefined.
+      var realMaxTimestamp;
+      if (this.model_.maxTimestamp) {
+        realMaxTimestamp = Math.max(this.model_.maxTimestamp,
+                                    Math.max.apply(Math, openTimestamps));
+      } else {
+        realMaxTimestamp = Math.max.apply(Math, openTimestamps);
+      }
+
+      // Automatically close any slices are still open. These occur in a number
+      // of reasonable situations, e.g. deadlock. This pass ensures the open
+      // slices make it into the final model.
+      for (var ptid in this.threadStateByPTID_) {
+        var state = this.threadStateByPTID_[ptid];
+        while (state.openSlices.length > 0) {
+          var slice = state.openSlices.pop();
+          slice.slice.duration = realMaxTimestamp - slice.slice.start;
+          slice.slice.didNotFinish = true;
+          var event = this.events_[slice.index];
+
+          // Store the slice on the correct subrow.
+          var thread = this.model_.getOrCreateProcess(event.pid)
+                           .getOrCreateThread(event.tid);
+          var subRowIndex = state.openSlices.length;
+          thread.getSubrow(subRowIndex).push(slice.slice);
+
+          // Add the slice to the subSlices array of its parent.
+          if (state.openSlices.length) {
+            var parentSlice = state.openSlices[state.openSlices.length - 1];
+            parentSlice.slice.subSlices.push(slice.slice);
+          }
+        }
+      }
+    },
+
+    /**
+     * Helper that creates and adds samples to a TimelineCounter object based on
+     * 'C' phase events.
+     */
+    processCounter: function(event) {
+      var ctr_name;
+      if (event.id !== undefined)
+        ctr_name = event.name + '[' + event.id + ']';
+      else
+        ctr_name = event.name;
+
+      var ctr = this.model_.getOrCreateProcess(event.pid)
+          .getOrCreateCounter(event.cat, ctr_name);
+      // Initialize the counter's series fields if needed.
+      if (ctr.numSeries == 0) {
+        for (var seriesName in event.args) {
+          ctr.seriesNames.push(seriesName);
+          ctr.seriesColors.push(
+              tracing.getStringColorId(ctr.name + '.' + seriesName));
+        }
+        if (ctr.numSeries == 0) {
+          this.model_.importErrors.push('Expected counter ' + event.name +
+              ' to have at least one argument to use as a value.');
+          // Drop the counter.
+          delete ctr.parent.counters[ctr.name];
+          return;
+        }
+      }
+
+      // Add the sample values.
+      ctr.timestamps.push(event.ts / 1000);
+      for (var i = 0; i < ctr.numSeries; i++) {
+        var seriesName = ctr.seriesNames[i];
+        if (event.args[seriesName] === undefined) {
+          ctr.samples.push(0);
+          continue;
+        }
+        ctr.samples.push(event.args[seriesName]);
+      }
+    },
+
+    /**
+     * Walks through the events_ list and outputs the structures discovered to
+     * model_.
+     */
+    importEvents: function() {
+      // Walk through events
+      var events = this.events_;
+      for (var eI = 0; eI < events.length; eI++) {
+        var event = events[eI];
+        var ptid = event.pid + ':' + event.tid;
+
+        if (!(ptid in this.threadStateByPTID_))
+          this.threadStateByPTID_[ptid] = new ThreadState();
+        var state = this.threadStateByPTID_[ptid];
+
+        if (event.ph == 'B') {
+          this.processBegin(eI, state, event);
+        } else if (event.ph == 'E') {
+          this.processEnd(state, event);
+        } else if (event.ph == 'I') {
+          // Treat an Instant event as a duration 0 slice.
+          // TimelineSliceTrack's redraw() knows how to handle this.
+          this.processBegin(eI, state, event);
+          this.processEnd(state, event);
+        } else if (event.ph == 'C') {
+          this.processCounter(event);
+        } else if (event.ph == 'M') {
+          if (event.name == 'thread_name') {
+            var thread = this.model_.getOrCreateProcess(event.pid)
+                             .getOrCreateThread(event.tid);
+            thread.name = event.args.name;
+          } else {
+            this.model_.importErrors.push(
+                'Unrecognized metadata name: ' + event.name);
+          }
+        } else {
+          this.model_.importErrors.push(
+              'Unrecognized event phase: ' + event.ph +
+              '(' + event.name + ')');
+        }
+      }
+
+      // Autoclose any open slices.
+      var hasOpenSlices = false;
+      for (var ptid in this.threadStateByPTID_) {
+        var state = this.threadStateByPTID_[ptid];
+        hasOpenSlices |= state.openSlices.length > 0;
+      }
+      if (hasOpenSlices)
+        this.autoCloseOpenSlices();
+    }
+  };
+
+  tracing.TimelineModel.registerImporter(TraceEventImporter);
+
+  return {
+    TraceEventImporter: TraceEventImporter
+  };
+});
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+/**
+ * @fileoverview Helper functions for doing intersections and iteration
+ * over sorted arrays and intervals.
+ *
+ */
+cr.define('tracing', function() {
+  /**
+   * Finds the first index in the array whose value is >= loVal.
+   *
+   * The key for the search is defined by the mapFn. This array must
+   * be prearranged such that ary.map(mapFn) would also be sorted in
+   * ascending order.
+   *
+   * @param {Array} ary An array of arbitrary objects.
+   * @param {function():*} mapFn Callback that produces a key value
+   *     from an element in ary.
+   * @param {number} loVal Value for which to search.
+   * @return {Number} Offset o into ary where all ary[i] for i <= o
+   *     are < loVal, or ary.length if loVal is greater than all elements in
+   *     the array.
+   */
+  function findLowIndexInSortedArray(ary, mapFn, loVal) {
+    if (ary.length == 0)
+      return 1;
+
+    var low = 0;
+    var high = ary.length - 1;
+    var i, comparison;
+    var hitPos = -1;
+    while (low <= high) {
+      i = Math.floor((low + high) / 2);
+      comparison = mapFn(ary[i]) - loVal;
+      if (comparison < 0) {
+        low = i + 1; continue;
+      } else if (comparison > 0) {
+        high = i - 1; continue;
+      } else {
+        hitPos = i;
+        high = i - 1;
+      }
+    }
+    // return where we hit, or failing that the low pos
+    return hitPos != -1 ? hitPos : low;
+  }
+
+  /**
+   * Finds an index in an array of intervals that either
+   * intersects the provided loVal, or if no intersection is found,
+   * the index of the first interval whose start is > loVal.
+   *
+   * The array of intervals is defined implicitly via two mapping functions
+   * over the provided ary. mapLoFn determines the lower value of the interval,
+   * mapWidthFn the width. Intersection is lower-inclusive, e.g. [lo,lo+w).
+   *
+   * The array of intervals formed by this mapping must be non-overlapping and
+   * sorted in ascending order by loVal.
+   *
+   * @param {Array} ary An array of objects that can be converted into sorted
+   *     nonoverlapping ranges [x,y) using the mapLoFn and mapWidth.
+   * @param {function():*} mapLoFn Callback that produces the low value for the
+   *     interval represented by an  element in the array.
+   * @param {function():*} mapLoFn Callback that produces the width for the
+   *     interval represented by an  element in the array.
+   * @param {number} loVal The low value for the search.
+   * @return {Number} An index in the array that intersects or is first-above
+   *     loVal, -1 if none found and loVal is below than all the intervals,
+   *     ary.length if loVal is greater than all the intervals.
+   */
+  function findLowIndexInSortedIntervals(ary, mapLoFn, mapWidthFn, loVal) {
+    var first = findLowIndexInSortedArray(ary, mapLoFn, loVal);
+    if (first == 0) {
+      if (loVal >= mapLoFn(ary[0]) &&
+          loVal < mapLoFn(ary[0] + mapWidthFn(ary[0]))) {
+        return 0;
+      } else {
+        return -1;
+      }
+    } else if (first <= ary.length &&
+               loVal >= mapLoFn(ary[first - 1]) &&
+               loVal < mapLoFn(ary[first - 1]) + mapWidthFn(ary[first - 1])) {
+      return first - 1;
+    } else {
+      return ary.length;
+    }
+  }
+
+  /**
+   * Calls cb for all intervals in the implicit array of intervals
+   * defnied by ary, mapLoFn and mapHiFn that intersect the range
+   * [loVal,hiVal)
+   *
+   * This function uses the same scheme as findLowIndexInSortedArray
+   * to define the intervals. The same restrictions on sortedness and
+   * non-overlappingness apply.
+   *
+   * @param {Array} ary An array of objects that can be converted into sorted
+   * nonoverlapping ranges [x,y) using the mapLoFn and mapWidth.
+   * @param {function():*} mapLoFn Callback that produces the low value for the
+   * interval represented by an element in the array.
+   * @param {function():*} mapLoFn Callback that produces the width for the
+   * interval represented by an element in the array.
+   * @param {number} The low value for the search, inclusive.
+   * @param {number} loVal The high value for the search, non inclusive.
+   * @param {function():*} cb The function to run for intersecting intervals.
+   */
+  function iterateOverIntersectingIntervals(ary, mapLoFn, mapWidthFn, loVal,
+                                            hiVal, cb) {
+    if (loVal > hiVal) return;
+
+    var i = findLowIndexInSortedArray(ary, mapLoFn, loVal);
+    if (i == -1) {
+      return;
+    }
+    if (i > 0) {
+      var hi = mapLoFn(ary[i - 1]) + mapWidthFn(ary[i - 1]);
+      if (hi >= loVal) {
+        cb(ary[i - 1]);
+      }
+    }
+    if (i == ary.length) {
+      return;
+    }
+
+    for (var n = ary.length; i < n; i++) {
+      var lo = mapLoFn(ary[i]);
+      if (lo >= hiVal)
+        break;
+      cb(ary[i]);
+    }
+  }
+
+  /**
+   * Non iterative version of iterateOverIntersectingIntervals.
+   *
+   * @return {Array} Array of elements in ary that intersect loVal, hiVal.
+   */
+  function getIntersectingIntervals(ary, mapLoFn, mapWidthFn, loVal, hiVal) {
+    var tmp = [];
+    iterateOverIntersectingIntervals(ary, mapLoFn, mapWidthFn, loVal, hiVal,
+                                     function(d) {
+                                       tmp.push(d);
+                                     });
+    return tmp;
+  }
+
+  return {
+    findLowIndexInSortedArray: findLowIndexInSortedArray,
+    findLowIndexInSortedIntervals: findLowIndexInSortedIntervals,
+    iterateOverIntersectingIntervals: iterateOverIntersectingIntervals,
+    getIntersectingIntervals: getIntersectingIntervals
+  };
+});
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('tracing', function() {
+  /**
+   * Uses an embedded iframe to measure provided elements without forcing layout
+   * on the main document.
+   * @constructor
+   * @extends {Object}
+   */
+  function MeasuringStick() {
+    var iframe = document.createElement('iframe');
+    iframe.style.cssText = 'width:100%;height:0;border:0;visibility:hidden';
+    document.body.appendChild(iframe);
+    this._doc = iframe.contentDocument;
+    this._window = iframe.contentWindow;
+    this._doc.body.style.cssText = 'padding:0;margin:0;overflow:hidden';
+
+    var stylesheets = document.querySelectorAll('link[rel=stylesheet]');
+    for (var i = 0; i < stylesheets.length; i++) {
+      var stylesheet = stylesheets[i];
+      var link = this._doc.createElement('link');
+      link.rel = 'stylesheet';
+      link.href = stylesheet.href;
+      this._doc.head.appendChild(link);
+    }
+  }
+
+  MeasuringStick.prototype = {
+    __proto__: Object.prototype,
+
+    /**
+     * Measures the provided element without forcing layout on the main
+     * document.
+     */
+    measure: function(element) {
+      this._doc.body.appendChild(element);
+      var style = this._window.getComputedStyle(element);
+      var width = parseInt(style.width, 10);
+      var height = parseInt(style.height, 10);
+      this._doc.body.removeChild(element);
+      return { width: width, height: height };
+    }
+  };
+
+  return {
+    MeasuringStick: MeasuringStick
+  };
+});
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Interactive visualizaiton of TimelineModel objects
+ * based loosely on gantt charts. Each thread in the TimelineModel is given a
+ * set of TimelineTracks, one per subrow in the thread. The Timeline class
+ * acts as a controller, creating the individual tracks, while TimelineTracks
+ * do actual drawing.
+ *
+ * Visually, the Timeline produces (prettier) visualizations like the following:
+ *    Thread1:  AAAAAAAAAA         AAAAA
+ *                  BBBB              BB
+ *    Thread2:     CCCCCC                 CCCCC
+ *
+ */
+cr.define('tracing', function() {
+
+  /**
+   * The TimelineViewport manages the transform used for navigating
+   * within the timeline. It is a simple transform:
+   *   x' = (x+pan) * scale
+   *
+   * The timeline code tries to avoid directly accessing this transform,
+   * instead using this class to do conversion between world and view space,
+   * as well as the math for centering the viewport in various interesting
+   * ways.
+   *
+   * @constructor
+   * @extends {cr.EventTarget}
+   */
+  function TimelineViewport(parentEl) {
+    this.parentEl_ = parentEl;
+    this.scaleX_ = 1;
+    this.panX_ = 0;
+    this.gridTimebase_ = 0;
+    this.gridStep_ = 1000 / 60;
+    this.gridEnabled_ = false;
+    this.hasCalledSetupFunction_ = false;
+
+    this.onResizeBoundToThis_ = this.onResize_.bind(this);
+
+    // The following code uses an interval to detect when the parent element
+    // is attached to the document. That is a trigger to run the setup function
+    // and install a resize listener.
+    this.checkForAttachInterval_ = setInterval(
+        this.checkForAttach_.bind(this), 250);
+  }
+
+  TimelineViewport.prototype = {
+    __proto__: cr.EventTarget.prototype,
+
+    /**
+     * Allows initialization of the viewport when the viewport's parent element
+     * has been attached to the document and given a size.
+     * @param {Function} fn Function to call when the viewport can be safely
+     * initialized.
+     */
+    setWhenPossible: function(fn) {
+      this.pendingSetFunction_ = fn;
+    },
+
+    /**
+     * @return {boolean} Whether the current timeline is attached to the
+     * document.
+     */
+    get isAttachedToDocument_() {
+      var cur = this.parentEl_;
+      while (cur.parentNode)
+        cur = cur.parentNode;
+      return cur == this.parentEl_.ownerDocument;
+    },
+
+    onResize_: function() {
+      this.dispatchChangeEvent();
+    },
+
+    /**
+     * Checks whether the parentNode is attached to the document.
+     * When it is, it installs the iframe-based resize detection hook
+     * and then runs the pendingSetFunction_, if present.
+     */
+    checkForAttach_: function() {
+      if (!this.isAttachedToDocument_ || this.clientWidth == 0)
+        return;
+
+      if (!this.iframe_) {
+        this.iframe_ = document.createElement('iframe');
+        this.iframe_.style.cssText =
+            'position:absolute;width:100%;height:0;border:0;visibility:hidden;';
+        this.parentEl_.appendChild(this.iframe_);
+
+        this.iframe_.contentWindow.addEventListener('resize',
+                                                    this.onResizeBoundToThis_);
+      }
+
+      var curSize = this.clientWidth + 'x' + this.clientHeight;
+      if (this.pendingSetFunction_) {
+        this.lastSize_ = curSize;
+        this.pendingSetFunction_();
+        this.pendingSetFunction_ = undefined;
+      }
+
+      window.clearInterval(this.checkForAttachInterval_);
+      this.checkForAttachInterval_ = undefined;
+    },
+
+    /**
+     * Fires the change event on this viewport. Used to notify listeners
+     * to redraw when the underlying model has been mutated.
+     */
+    dispatchChangeEvent: function() {
+      cr.dispatchSimpleEvent(this, 'change');
+    },
+
+    detach: function() {
+      if (this.checkForAttachInterval_) {
+        window.clearInterval(this.checkForAttachInterval_);
+        this.checkForAttachInterval_ = undefined;
+      }
+      this.iframe_.removeEventListener('resize', this.onResizeBoundToThis_);
+      this.parentEl_.removeChild(this.iframe_);
+    },
+
+    get scaleX() {
+      return this.scaleX_;
+    },
+    set scaleX(s) {
+      var changed = this.scaleX_ != s;
+      if (changed) {
+        this.scaleX_ = s;
+        this.dispatchChangeEvent();
+      }
+    },
+
+    get panX() {
+      return this.panX_;
+    },
+    set panX(p) {
+      var changed = this.panX_ != p;
+      if (changed) {
+        this.panX_ = p;
+        this.dispatchChangeEvent();
+      }
+    },
+
+    setPanAndScale: function(p, s) {
+      var changed = this.scaleX_ != s || this.panX_ != p;
+      if (changed) {
+        this.scaleX_ = s;
+        this.panX_ = p;
+        this.dispatchChangeEvent();
+      }
+    },
+
+    xWorldToView: function(x) {
+      return (x + this.panX_) * this.scaleX_;
+    },
+
+    xWorldVectorToView: function(x) {
+      return x * this.scaleX_;
+    },
+
+    xViewToWorld: function(x) {
+      return (x / this.scaleX_) - this.panX_;
+    },
+
+    xViewVectorToWorld: function(x) {
+      return x / this.scaleX_;
+    },
+
+    xPanWorldPosToViewPos: function(worldX, viewX, viewWidth) {
+      if (typeof viewX == 'string') {
+        if (viewX == 'left') {
+          viewX = 0;
+        } else if (viewX == 'center') {
+          viewX = viewWidth / 2;
+        } else if (viewX == 'right') {
+          viewX = viewWidth - 1;
+        } else {
+          throw Error('unrecognized string for viewPos. left|center|right');
+        }
+      }
+      this.panX = (viewX / this.scaleX_) - worldX;
+    },
+
+    get gridEnabled() {
+      return this.gridEnabled_;
+    },
+
+    set gridEnabled(enabled) {
+      if (this.gridEnabled_ == enabled)
+        return;
+      this.gridEnabled_ = enabled && true;
+      this.dispatchChangeEvent();
+    },
+
+    get gridTimebase() {
+      return this.gridTimebase_;
+    },
+
+    set gridTimebase(timebase) {
+      if (this.gridTimebase_ == timebase)
+        return;
+      this.gridTimebase_ = timebase;
+      cr.dispatchSimpleEvent(this, 'change');
+    },
+
+    get gridStep() {
+      return this.gridStep_;
+    },
+
+    applyTransformToCanavs: function(ctx) {
+      ctx.transform(this.scaleX_, 0, 0, 1, this.panX_ * this.scaleX_, 0);
+    }
+  };
+
+  /**
+   * Renders a TimelineModel into a div element, making one
+   * TimelineTrack for each subrow in each thread of the model, managing
+   * overall track layout, and handling user interaction with the
+   * viewport.
+   *
+   * @constructor
+   * @extends {HTMLDivElement}
+   */
+  Timeline = cr.ui.define('div');
+
+  Timeline.prototype = {
+    __proto__: HTMLDivElement.prototype,
+
+    model_: null,
+
+    decorate: function() {
+      this.classList.add('timeline');
+
+      this.viewport_ = new TimelineViewport(this);
+
+      this.tracks_ = this.ownerDocument.createElement('div');
+      this.appendChild(this.tracks_);
+
+      this.dragBox_ = this.ownerDocument.createElement('div');
+      this.dragBox_.className = 'timeline-drag-box';
+      this.appendChild(this.dragBox_);
+      this.hideDragBox_();
+
+      this.bindEventListener_(document, 'keypress', this.onKeypress_, this);
+      this.bindEventListener_(document, 'keydown', this.onKeydown_, this);
+      this.bindEventListener_(document, 'mousedown', this.onMouseDown_, this);
+      this.bindEventListener_(document, 'mousemove', this.onMouseMove_, this);
+      this.bindEventListener_(document, 'mouseup', this.onMouseUp_, this);
+      this.bindEventListener_(document, 'dblclick', this.onDblClick_, this);
+
+      this.lastMouseViewPos_ = {x: 0, y: 0};
+
+      this.selection_ = [];
+    },
+
+    /**
+     * Wraps the standard addEventListener but automatically binds the provided
+     * func to the provided target, tracking the resulting closure. When detach
+     * is called, these listeners will be automatically removed.
+     */
+    bindEventListener_: function(object, event, func, target) {
+      if (!this.boundListeners_)
+        this.boundListeners_ = [];
+      var boundFunc = func.bind(target);
+      this.boundListeners_.push({object: object,
+        event: event,
+        boundFunc: boundFunc});
+      object.addEventListener(event, boundFunc);
+    },
+
+    detach: function() {
+      for (var i = 0; i < this.tracks_.children.length; i++)
+        this.tracks_.children[i].detach();
+
+      for (var i = 0; i < this.boundListeners_.length; i++) {
+        var binding = this.boundListeners_[i];
+        binding.object.removeEventListener(binding.event, binding.boundFunc);
+      }
+      this.boundListeners_ = undefined;
+      this.viewport_.detach();
+    },
+
+    get viewport() {
+      return this.viewport_;
+    },
+
+    get model() {
+      return this.model_;
+    },
+
+    set model(model) {
+      if (!model)
+        throw Error('Model cannot be null');
+      if (this.model) {
+        throw Error('Cannot set model twice.');
+      }
+      this.model_ = model;
+
+      // Figure out all the headings.
+      var allHeadings = [];
+      model.getAllThreads().forEach(function(t) {
+        allHeadings.push(t.userFriendlyName);
+      });
+      model.getAllCounters().forEach(function(c) {
+        allHeadings.push(c.name);
+      });
+      model.getAllCpus().forEach(function(c) {
+        allHeadings.push('CPU ' + c.cpuNumber);
+      });
+
+      // Figure out the maximum heading size.
+      var maxHeadingWidth = 0;
+      var measuringStick = new tracing.MeasuringStick();
+      var headingEl = document.createElement('div');
+      headingEl.style.position = 'fixed';
+      headingEl.className = 'timeline-canvas-based-track-title';
+      allHeadings.forEach(function(text) {
+        headingEl.textContent = text + ':__';
+        var w = measuringStick.measure(headingEl).width;
+        // Limit heading width to 300px.
+        if (w > 300)
+          w = 300;
+        if (w > maxHeadingWidth)
+          maxHeadingWidth = w;
+      });
+      maxHeadingWidth = maxHeadingWidth + 'px';
+
+      // Reset old tracks.
+      for (var i = 0; i < this.tracks_.children.length; i++)
+        this.tracks_.children[i].detach();
+      this.tracks_.textContent = '';
+
+      // Get a sorted list of CPUs
+      var cpus = model.getAllCpus();
+      cpus.sort(tracing.TimelineCpu.compare);
+
+      // Create tracks for each CPU.
+      cpus.forEach(function(cpu) {
+        var track = new tracing.TimelineCpuTrack();
+        track.heading = 'CPU ' + cpu.cpuNumber + ':';
+        track.headingWidth = maxHeadingWidth;
+        track.viewport = this.viewport_;
+        track.cpu = cpu;
+        this.tracks_.appendChild(track);
+
+        for (var counterName in cpu.counters) {
+          var counter = cpu.counters[counterName];
+          track = new tracing.TimelineCounterTrack();
+          track.heading = 'CPU ' + cpu.cpuNumber + ' ' + counter.name + ':';
+          track.headingWidth = maxHeadingWidth;
+          track.viewport = this.viewport_;
+          track.counter = counter;
+          this.tracks_.appendChild(track);
+        }
+      }.bind(this));
+
+      // Get a sorted list of processes.
+      var processes = model.getAllProcesses();
+      processes.sort(tracing.TimelineProcess.compare);
+
+      // Create tracks for each process.
+      processes.forEach(function(process) {
+        // Add counter tracks for this process.
+        var counters = [];
+        for (var tid in process.counters)
+          counters.push(process.counters[tid]);
+        counters.sort(tracing.TimelineCounter.compare);
+
+        // Create the counters for this process.
+        counters.forEach(function(counter) {
+          var track = new tracing.TimelineCounterTrack();
+          track.heading = counter.name + ':';
+          track.headingWidth = maxHeadingWidth;
+          track.viewport = this.viewport_;
+          track.counter = counter;
+          this.tracks_.appendChild(track);
+        }.bind(this));
+
+        // Get a sorted list of threads.
+        var threads = [];
+        for (var tid in process.threads)
+          threads.push(process.threads[tid]);
+        threads.sort(tracing.TimelineThread.compare);
+
+        // Create the threads.
+        threads.forEach(function(thread) {
+          var track = new tracing.TimelineThreadTrack();
+          track.heading = thread.userFriendlyName + ':';
+          track.tooltip = thread.userFriendlyDetials;
+          track.headingWidth = maxHeadingWidth;
+          track.viewport = this.viewport_;
+          track.thread = thread;
+          this.tracks_.appendChild(track);
+        }.bind(this));
+      }.bind(this));
+
+      // Set up a reasonable viewport.
+      this.viewport_.setWhenPossible(function() {
+        var rangeTimestamp = this.model_.maxTimestamp -
+            this.model_.minTimestamp;
+        var w = this.firstCanvas.width;
+        var scaleX = w / rangeTimestamp;
+        var panX = -this.model_.minTimestamp;
+        this.viewport_.setPanAndScale(panX, scaleX);
+      }.bind(this));
+    },
+
+    /**
+     * @return {Element} The element whose focused state determines
+     * whether to respond to keyboard inputs.
+     * Defaults to the parent element.
+     */
+    get focusElement() {
+      if (this.focusElement_)
+        return this.focusElement_;
+      return this.parentElement;
+    },
+
+    /**
+     * Sets the element whose focus state will determine whether
+     * to respond to keybaord input.
+     */
+    set focusElement(value) {
+      this.focusElement_ = value;
+    },
+
+    get listenToKeys_() {
+      if (!this.focusElement_)
+        return true;
+      if (this.focusElement.tabIndex >= 0)
+        return document.activeElement == this.focusElement;
+      return true;
+    },
+
+    onKeypress_: function(e) {
+      var vp = this.viewport_;
+      if (!this.firstCanvas)
+        return;
+      if (!this.listenToKeys_)
+        return;
+      var viewWidth = this.firstCanvas.clientWidth;
+      var curMouseV, curCenterW;
+      switch (e.keyCode) {
+        case 101: // e
+          var vX = this.lastMouseViewPos_.x;
+          var wX = vp.xViewToWorld(this.lastMouseViewPos_.x);
+          var distFromCenter = vX - (viewWidth / 2);
+          var percFromCenter = distFromCenter / viewWidth;
+          var percFromCenterSq = percFromCenter * percFromCenter;
+          vp.xPanWorldPosToViewPos(wX, 'center', viewWidth);
+          break;
+        case 119:  // w
+          this.zoomBy_(1.5);
+          break;
+        case 115:  // s
+          this.zoomBy_(1 / 1.5);
+          break;
+        case 103:  // g
+          this.onGridToggle_(true);
+          break;
+        case 71:  // G
+          this.onGridToggle_(false);
+          break;
+        case 87:  // W
+          this.zoomBy_(10);
+          break;
+        case 83:  // S
+          this.zoomBy_(1 / 10);
+          break;
+        case 97:  // a
+          vp.panX += vp.xViewVectorToWorld(viewWidth * 0.1);
+          break;
+        case 100:  // d
+          vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.1);
+          break;
+        case 65:  // A
+          vp.panX += vp.xViewVectorToWorld(viewWidth * 0.5);
+          break;
+        case 68:  // D
+          vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.5);
+          break;
+      }
+    },
+
+    // Not all keys send a keypress.
+    onKeydown_: function(e) {
+      if (!this.listenToKeys_)
+        return;
+      switch (e.keyCode) {
+        case 37:   // left arrow
+          this.selectPrevious_(e);
+          e.preventDefault();
+          break;
+        case 39:   // right arrow
+          this.selectNext_(e);
+          e.preventDefault();
+          break;
+        case 9:    // TAB
+          if (this.focusElement.tabIndex == -1) {
+            if (e.shiftKey)
+              this.selectPrevious_(e);
+            else
+              this.selectNext_(e);
+            e.preventDefault();
+          }
+          break;
+      }
+    },
+
+    /**
+     * Zoom in or out on the timeline by the given scale factor.
+     * @param {integer} scale The scale factor to apply.  If <1, zooms out.
+     */
+    zoomBy_: function(scale) {
+      if (!this.firstCanvas)
+        return;
+      var vp = this.viewport_;
+      var viewWidth = this.firstCanvas.clientWidth;
+      var curMouseV = this.lastMouseViewPos_.x;
+      var curCenterW = vp.xViewToWorld(curMouseV);
+      vp.scaleX = vp.scaleX * scale;
+      vp.xPanWorldPosToViewPos(curCenterW, curMouseV, viewWidth);
+    },
+
+    /** Select the next slice on the timeline.  Applies to each track. */
+    selectNext_: function(e) {
+      this.selectAdjoining_(e, true);
+    },
+
+    /** Select the previous slice on the timeline.  Applies to each track. */
+    selectPrevious_: function(e) {
+      this.selectAdjoining_(e, false);
+    },
+
+    /**
+     * Helper for selection previous or next.
+     * @param {Event} The current event.
+     * @param {boolean} forwardp If true, select one forward (next).
+     *   Else, select previous.
+     */
+    selectAdjoining_: function(e, forwardp) {
+      var i, track, slice, adjoining;
+      var selection = [];
+      // Clear old selection; try and select next.
+      for (i = 0; i < this.selection_.length; i++) {
+        adjoining = undefined;
+        this.selection_[i].slice.selected = false;
+        track = this.selection_[i].track;
+        slice = this.selection_[i].slice;
+        if (slice) {
+          if (forwardp)
+            adjoining = track.pickNext(slice);
+          else
+            adjoining = track.pickPrevious(slice);
+        }
+        if (adjoining != undefined)
+          selection.push({track: track, slice: adjoining});
+      }
+      this.selection = selection;
+      e.preventDefault();
+    },
+
+    get keyHelp() {
+      var help = 'Keyboard shortcuts:\n' +
+          ' w/s     : Zoom in/out    (with shift: go faster)\n' +
+          ' a/d     : Pan left/right\n' +
+          ' e       : Center on mouse\n' +
+          ' g/G     : Shows grid at the start/end of the selected task\n';
+
+      if (this.focusElement.tabIndex) {
+        help += ' <-      : Select previous event on current timeline\n' +
+            ' ->      : Select next event on current timeline\n';
+      } else {
+        help += ' <-,^TAB : Select previous event on current timeline\n' +
+            ' ->, TAB : Select next event on current timeline\n';
+      }
+      help +=
+          '\n' +
+          'Dbl-click to zoom in; Shift dbl-click to zoom out\n';
+      return help;
+    },
+
+    get selection() {
+      return this.selection_;
+    },
+
+    set selection(selection) {
+      // Clear old selection.
+      for (i = 0; i < this.selection_.length; i++)
+        this.selection_[i].slice.selected = false;
+
+      this.selection_ = selection;
+
+      cr.dispatchSimpleEvent(this, 'selectionChange');
+      for (i = 0; i < this.selection_.length; i++)
+        this.selection_[i].slice.selected = true;
+      this.viewport_.dispatchChangeEvent(); // Triggers a redraw.
+    },
+
+    get firstCanvas() {
+      return this.tracks_.firstChild ?
+          this.tracks_.firstChild.firstCanvas : undefined;
+    },
+
+    hideDragBox_: function() {
+      this.dragBox_.style.left = '-1000px';
+      this.dragBox_.style.top = '-1000px';
+      this.dragBox_.style.width = 0;
+      this.dragBox_.style.height = 0;
+    },
+
+    setDragBoxPosition_: function(eDown, eCur) {
+      var loX = Math.min(eDown.clientX, eCur.clientX);
+      var hiX = Math.max(eDown.clientX, eCur.clientX);
+      var loY = Math.min(eDown.clientY, eCur.clientY);
+      var hiY = Math.max(eDown.clientY, eCur.clientY);
+
+      this.dragBox_.style.left = loX + 'px';
+      this.dragBox_.style.top = loY + 'px';
+      this.dragBox_.style.width = hiX - loX + 'px';
+      this.dragBox_.style.height = hiY - loY + 'px';
+
+      var canv = this.firstCanvas;
+      var loWX = this.viewport_.xViewToWorld(loX - canv.offsetLeft);
+      var hiWX = this.viewport_.xViewToWorld(hiX - canv.offsetLeft);
+
+      var roundedDuration = Math.round((hiWX - loWX) * 100) / 100;
+      this.dragBox_.textContent = roundedDuration + 'ms';
+
+      var e = new cr.Event('selectionChanging');
+      e.loWX = loWX;
+      e.hiWX = hiWX;
+      this.dispatchEvent(e);
+    },
+
+    onGridToggle_: function(left) {
+      var tb;
+      if (left)
+        tb = Math.min.apply(Math, this.selection_.map(
+            function(x) { return x.slice.start; }));
+      else
+        tb = Math.max.apply(Math, this.selection_.map(
+            function(x) { return x.slice.end; }));
+
+      // Shift the timebase left until its just left of minTimestamp.
+      var numInterfvalsSinceStart = Math.ceil((tb - this.model_.minTimestamp) /
+          this.viewport_.gridStep_);
+      this.viewport_.gridTimebase = tb -
+          (numInterfvalsSinceStart + 1) * this.viewport_.gridStep_;
+      this.viewport_.gridEnabled = true;
+    },
+
+    onMouseDown_: function(e) {
+      rect = this.tracks_.getClientRects()[0];
+      var inside = rect &&
+          e.clientX >= rect.left &&
+          e.clientX < rect.right &&
+          e.clientY >= rect.top &&
+          e.clientY < rect.bottom;
+      if (!inside)
+        return;
+
+      var canv = this.firstCanvas;
+      var pos = {
+        x: e.clientX - canv.offsetLeft,
+        y: e.clientY - canv.offsetTop
+      };
+
+      var wX = this.viewport_.xViewToWorld(pos.x);
+
+      this.dragBeginEvent_ = e;
+      e.preventDefault();
+      if (this.focusElement.tabIndex >= 0)
+        this.focusElement.focus();
+    },
+
+    onMouseMove_: function(e) {
+      if (!this.firstCanvas)
+        return;
+      var canv = this.firstCanvas;
+      var pos = {
+        x: e.clientX - canv.offsetLeft,
+        y: e.clientY - canv.offsetTop
+      };
+
+      // Remember position. Used during keyboard zooming.
+      this.lastMouseViewPos_ = pos;
+
+      // Update the drag box
+      if (this.dragBeginEvent_) {
+        this.setDragBoxPosition_(this.dragBeginEvent_, e);
+      }
+    },
+
+    onMouseUp_: function(e) {
+      var i;
+      if (this.dragBeginEvent_) {
+        // Stop the dragging.
+        this.hideDragBox_();
+        var eDown = this.dragBeginEvent_;
+        this.dragBeginEvent_ = null;
+
+        // Figure out extents of the drag.
+        var loX = Math.min(eDown.clientX, e.clientX);
+        var hiX = Math.max(eDown.clientX, e.clientX);
+        var loY = Math.min(eDown.clientY, e.clientY);
+        var hiY = Math.max(eDown.clientY, e.clientY);
+
+        // Convert to worldspace.
+        var canv = this.firstCanvas;
+        var loWX = this.viewport_.xViewToWorld(loX - canv.offsetLeft);
+        var hiWX = this.viewport_.xViewToWorld(hiX - canv.offsetLeft);
+
+        // Figure out what has been hit.
+        var selection = [];
+        function addHit(type, track, slice) {
+          selection.push({track: track, slice: slice});
+        }
+        for (i = 0; i < this.tracks_.children.length; i++) {
+          var track = this.tracks_.children[i];
+
+          // Only check tracks that insersect the rect.
+          var trackClientRect = track.getBoundingClientRect();
+          var a = Math.max(loY, trackClientRect.top);
+          var b = Math.min(hiY, trackClientRect.bottom);
+          if (a <= b) {
+            track.pickRange(loWX, hiWX, loY, hiY, addHit);
+          }
+        }
+        // Activate the new selection.
+        this.selection = selection;
+      }
+    },
+
+    onDblClick_: function(e) {
+      var scale = 4;
+      if (e.shiftKey)
+        scale = 1 / scale;
+      this.zoomBy_(scale);
+      e.preventDefault();
+    }
+  };
+
+  /**
+   * The TimelineModel being viewed by the timeline
+   * @type {TimelineModel}
+   */
+  cr.defineProperty(Timeline, 'model', cr.PropertyKind.JS);
+
+  return {
+    Timeline: Timeline,
+    TimelineViewport: TimelineViewport
+  };
+});
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+/**
+ * @fileoverview Renders an array of slices into the provided div,
+ * using a child canvas element. Uses a FastRectRenderer to draw only
+ * the visible slices.
+ */
+cr.define('tracing', function() {
+
+  var pallette = tracing.getPallette();
+  var highlightIdBoost = tracing.getPalletteHighlightIdBoost();
+
+  var textWidthMap = { };
+  function quickMeasureText(ctx, text) {
+    var w = textWidthMap[text];
+    if (!w) {
+      w = ctx.measureText(text).width;
+      textWidthMap[text] = w;
+    }
+    return w;
+  }
+
+  /**
+   * A generic track that contains other tracks as its children.
+   * @constructor
+   */
+  var TimelineContainerTrack = cr.ui.define('div');
+  TimelineContainerTrack.prototype = {
+    __proto__: HTMLDivElement.prototype,
+
+    decorate: function() {
+      this.tracks_ = [];
+    },
+
+    detach: function() {
+      for (var i = 0; i < this.tracks_.length; i++)
+        this.tracks_[i].detach();
+    },
+
+    get viewport() {
+      return this.viewport_;
+    },
+
+    set viewport(v) {
+      this.viewport_ = v;
+      for (var i = 0; i < this.tracks_.length; i++)
+        this.tracks_[i].viewport = v;
+      this.updateChildTracks_();
+    },
+
+    get firstCanvas() {
+      if (this.tracks_.length)
+        return this.tracks_[0].firstCanvas;
+      return undefined;
+    },
+
+    /**
+     * Picks a slice, if any, at a given location.
+     * @param {number} wX X location to search at, in worldspace.
+     * @param {number} wY Y location to search at, in offset space.
+     *     offset space.
+     * @param {function():*} onHitCallback Callback to call with the slice,
+     *     if one is found.
+     * @return {boolean} true if a slice was found, otherwise false.
+     */
+    pick: function(wX, wY, onHitCallback) {
+      for (var i = 0; i < this.tracks_.length; i++) {
+        var trackClientRect = this.tracks_[i].getBoundingClientRect();
+        if (wY >= trackClientRect.top && wY < trackClientRect.bottom)
+          return this.tracks_[i].pick(wX, onHitCallback);
+      }
+      return false;
+    },
+
+    /**
+     * Finds slices intersecting the given interval.
+     * @param {number} loWX Lower X bound of the interval to search, in
+     *     worldspace.
+     * @param {number} hiWX Upper X bound of the interval to search, in
+     *     worldspace.
+     * @param {number} loY Lower Y bound of the interval to search, in
+     *     offset space.
+     * @param {number} hiY Upper Y bound of the interval to search, in
+     *     offset space.
+     * @param {function():*} onHitCallback Function to call for each slice
+     *     intersecting the interval.
+     */
+    pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) {
+      for (var i = 0; i < this.tracks_.length; i++) {
+        var trackClientRect = this.tracks_[i].getBoundingClientRect();
+        var a = Math.max(loY, trackClientRect.top);
+        var b = Math.min(hiY, trackClientRect.bottom);
+        if (a <= b)
+          this.tracks_[i].pickRange(loWX, hiWX, loY, hiY, onHitCallback);
+      }
+    }
+  };
+
+  /**
+   * Visualizes a TimelineThread using a series of of TimelineSliceTracks.
+   * @constructor
+   */
+  var TimelineThreadTrack = cr.ui.define(TimelineContainerTrack);
+  TimelineThreadTrack.prototype = {
+    __proto__: TimelineContainerTrack.prototype,
+
+    decorate: function() {
+      this.classList.add('timeline-thread-track');
+    },
+
+    get thread(thread) {
+      return this.thread_;
+    },
+
+    set thread(thread) {
+      this.thread_ = thread;
+      this.updateChildTracks_();
+    },
+
+    get tooltip() {
+      return this.tooltip_;
+    },
+
+    set tooltip(value) {
+      this.tooltip_ = value;
+      this.updateChildTracks_();
+    },
+
+    get heading() {
+      return this.heading_;
+    },
+
+    set heading(h) {
+      this.heading_ = h;
+      this.updateChildTracks_();
+    },
+
+    get headingWidth() {
+      return this.headingWidth_;
+    },
+
+    set headingWidth(width) {
+      this.headingWidth_ = width;
+      this.updateChildTracks_();
+    },
+
+    addTrack_: function(slices) {
+      var track = new TimelineSliceTrack();
+      track.heading = '';
+      track.slices = slices;
+      track.headingWidth = this.headingWidth_;
+      track.viewport = this.viewport_;
+
+      this.tracks_.push(track);
+      this.appendChild(track);
+      return track;
+    },
+
+    updateChildTracks_: function() {
+      this.detach();
+      this.textContent = '';
+      this.tracks_ = [];
+      if (this.thread_) {
+        if (this.thread_.cpuSlices) {
+          var track = this.addTrack_(this.thread_.cpuSlices);
+          track.height = '4px';
+        }
+
+        for (var srI = 0; srI < this.thread_.nonNestedSubRows.length; ++srI) {
+          this.addTrack_(this.thread_.nonNestedSubRows[srI]);
+        }
+        for (var srI = 0; srI < this.thread_.subRows.length; ++srI) {
+          this.addTrack_(this.thread_.subRows[srI]);
+        }
+        if (this.tracks_.length > 0) {
+          if (this.thread_.cpuSlices) {
+            this.tracks_[1].heading = this.heading_;
+            this.tracks_[1].tooltip = this.tooltip_;
+          } else {
+            this.tracks_[0].heading = this.heading_;
+            this.tracks_[0].tooltip = this.tooltip_;
+          }
+        }
+      }
+    }
+  };
+
+  /**
+   * Visualizes a TimelineCpu using a series of of TimelineSliceTracks.
+   * @constructor
+   */
+  var TimelineCpuTrack = cr.ui.define(TimelineContainerTrack);
+  TimelineCpuTrack.prototype = {
+    __proto__: TimelineContainerTrack.prototype,
+
+    decorate: function() {
+      this.classList.add('timeline-thread-track');
+    },
+
+    get cpu(cpu) {
+      return this.cpu_;
+    },
+
+    set cpu(cpu) {
+      this.cpu_ = cpu;
+      this.updateChildTracks_();
+    },
+
+    get tooltip() {
+      return this.tooltip_;
+    },
+
+    set tooltip(value) {
+      this.tooltip_ = value;
+      this.updateChildTracks_();
+    },
+
+    get heading() {
+      return this.heading_;
+    },
+
+    set heading(h) {
+      this.heading_ = h;
+      this.updateChildTracks_();
+    },
+
+    get headingWidth() {
+      return this.headingWidth_;
+    },
+
+    set headingWidth(width) {
+      this.headingWidth_ = width;
+      this.updateChildTracks_();
+    },
+
+    updateChildTracks_: function() {
+      this.detach();
+      this.textContent = '';
+      this.tracks_ = [];
+      if (this.cpu_) {
+        var track = new TimelineSliceTrack();
+        track.slices = this.cpu_.slices;
+        track.headingWidth = this.headingWidth_;
+        track.viewport = this.viewport_;
+
+        this.tracks_.push(track);
+        this.appendChild(track);
+
+        this.tracks_[0].heading = this.heading_;
+        this.tracks_[0].tooltip = this.tooltip_;
+      }
+    }
+  };
+
+  /**
+   * A canvas-based track constructed. Provides the basic heading and
+   * invalidation-managment infrastructure. Subclasses must implement drawing
+   * and picking code.
+   * @constructor
+   * @extends {HTMLDivElement}
+   */
+  var CanvasBasedTrack = cr.ui.define('div');
+
+  CanvasBasedTrack.prototype = {
+    __proto__: HTMLDivElement.prototype,
+
+    decorate: function() {
+      this.className = 'timeline-canvas-based-track';
+      this.slices_ = null;
+
+      this.headingDiv_ = document.createElement('div');
+      this.headingDiv_.className = 'timeline-canvas-based-track-title';
+      this.appendChild(this.headingDiv_);
+
+      this.canvasContainer_ = document.createElement('div');
+      this.canvasContainer_.className =
+          'timeline-canvas-based-track-canvas-container';
+      this.appendChild(this.canvasContainer_);
+      this.canvas_ = document.createElement('canvas');
+      this.canvas_.className = 'timeline-canvas-based-track-canvas';
+      this.canvasContainer_.appendChild(this.canvas_);
+
+      this.ctx_ = this.canvas_.getContext('2d');
+    },
+
+    detach: function() {
+      if (this.viewport_)
+        this.viewport_.removeEventListener('change',
+                                           this.viewportChangeBoundToThis_);
+    },
+
+    set headingWidth(width) {
+      this.headingDiv_.style.width = width;
+    },
+
+    get heading() {
+      return this.headingDiv_.textContent;
+    },
+
+    set heading(text) {
+      this.headingDiv_.textContent = text;
+    },
+
+    set tooltip(text) {
+      this.headingDiv_.title = text;
+    },
+
+    get viewport() {
+      return this.viewport_;
+    },
+
+    set viewport(v) {
+      this.viewport_ = v;
+      if (this.viewport_)
+        this.viewport_.removeEventListener('change',
+                                           this.viewportChangeBoundToThis_);
+      this.viewport_ = v;
+      if (this.viewport_) {
+        this.viewportChangeBoundToThis_ = this.viewportChange_.bind(this);
+        this.viewport_.addEventListener('change',
+                                        this.viewportChangeBoundToThis_);
+      }
+      this.invalidate();
+    },
+
+    viewportChange_: function() {
+      this.invalidate();
+    },
+
+    invalidate: function() {
+      if (this.rafPending_)
+        return;
+      webkitRequestAnimationFrame(function() {
+        this.rafPending_ = false;
+        if (!this.viewport_)
+          return;
+
+        if (this.canvas_.width != this.canvasContainer_.clientWidth)
+          this.canvas_.width = this.canvasContainer_.clientWidth;
+        if (this.canvas_.height != this.canvasContainer_.clientHeight)
+          this.canvas_.height = this.canvasContainer_.clientHeight;
+
+        this.redraw();
+      }.bind(this), this);
+      this.rafPending_ = true;
+    },
+
+    get firstCanvas() {
+      return this.canvas_;
+    }
+
+  };
+
+  /**
+   * A track that displays an array of TimelineSlice objects.
+   * @constructor
+   * @extends {CanvasBasedTrack}
+   */
+
+  var TimelineSliceTrack = cr.ui.define(CanvasBasedTrack);
+
+  TimelineSliceTrack.prototype = {
+
+    __proto__: CanvasBasedTrack.prototype,
+
+    decorate: function() {
+      this.classList.add('timeline-slice-track');
+    },
+
+    get slices() {
+      return this.slices_;
+    },
+
+    set slices(slices) {
+      this.slices_ = slices;
+      this.invalidate();
+    },
+
+    set height(height) {
+      this.style.height = height;
+    },
+
+    redraw: function() {
+      var ctx = this.ctx_;
+      var canvasW = this.canvas_.width;
+      var canvasH = this.canvas_.height;
+
+      ctx.clearRect(0, 0, canvasW, canvasH);
+
+      // Culling parameters.
+      var vp = this.viewport_;
+      var pixWidth = vp.xViewVectorToWorld(1);
+      var viewLWorld = vp.xViewToWorld(0);
+      var viewRWorld = vp.xViewToWorld(canvasW);
+
+      // Draw grid without a transform because the scale
+      // affects line width.
+      if (vp.gridEnabled) {
+        var x = vp.gridTimebase;
+        ctx.beginPath();
+        while (x < viewRWorld) {
+          if (x >= viewLWorld) {
+            // Do conversion to viewspace here rather than on
+            // x to avoid precision issues.
+            var vx = vp.xWorldToView(x);
+            ctx.moveTo(vx, 0);
+            ctx.lineTo(vx, canvasH);
+          }
+          x += vp.gridStep;
+        }
+        ctx.strokeStyle = 'rgba(255,0,0,0.25)';
+        ctx.stroke();
+      }
+
+      // Begin rendering in world space.
+      ctx.save();
+      vp.applyTransformToCanavs(ctx);
+
+      // Slices.
+      var tr = new tracing.FastRectRenderer(ctx, viewLWorld, 2 * pixWidth,
+                                            2 * pixWidth, viewRWorld, pallette);
+      tr.setYandH(0, canvasH);
+      var slices = this.slices_;
+      for (var i = 0; i < slices.length; ++i) {
+        var slice = slices[i];
+        var x = slice.start;
+        // Less than 0.001 causes short events to disappear when zoomed in.
+        var w = Math.max(slice.duration, 0.001);
+        var colorId = slice.selected ?
+            slice.colorId + highlightIdBoost :
+            slice.colorId;
+
+        if (w < pixWidth)
+          w = pixWidth;
+        if (slice.duration > 0) {
+          tr.fillRect(x, w, colorId);
+        } else {
+          // Instant: draw a triangle.  If zoomed too far, collapse
+          // into the FastRectRenderer.
+          if (pixWidth > 0.001) {
+            tr.fillRect(x, pixWidth, colorId);
+          } else {
+            ctx.fillStyle = pallette[colorId];
+            ctx.beginPath();
+            ctx.moveTo(x - (4 * pixWidth), canvasH);
+            ctx.lineTo(x, 0);
+            ctx.lineTo(x + (4 * pixWidth), canvasH);
+            ctx.closePath();
+            ctx.fill();
+          }
+        }
+      }
+      tr.flush();
+      ctx.restore();
+
+      // Labels.
+      if (canvasH > 8) {
+        ctx.textAlign = 'center';
+        ctx.textBaseline = 'top';
+        ctx.font = '10px sans-serif';
+        ctx.strokeStyle = 'rgb(0,0,0)';
+        ctx.fillStyle = 'rgb(0,0,0)';
+        // Don't render text until until it is 20px wide
+        var quickDiscardThresshold = pixWidth * 20;
+        for (var i = 0; i < slices.length; ++i) {
+          var slice = slices[i];
+          if (slice.duration > quickDiscardThresshold) {
+            var title = slice.title;
+            if (slice.didNotFinish) {
+              title += ' (Did Not Finish)';
+            }
+            var labelWidth = quickMeasureText(ctx, title) + 2;
+            var labelWidthWorld = pixWidth * labelWidth;
+
+            if (labelWidthWorld < slice.duration) {
+              var cX = vp.xWorldToView(slice.start + 0.5 * slice.duration);
+              ctx.fillText(title, cX, 2.5, labelWidth);
+            }
+          }
+        }
+      }
+    },
+
+    /**
+     * Picks a slice, if any, at a given location.
+     * @param {number} wX X location to search at, in worldspace.
+     * @param {number} wY Y location to search at, in offset space.
+     *     offset space.
+     * @param {function():*} onHitCallback Callback to call with the slice,
+     *     if one is found.
+     * @return {boolean} true if a slice was found, otherwise false.
+     */
+    pick: function(wX, wY, onHitCallback) {
+      var clientRect = this.getBoundingClientRect();
+      if (wY < clientRect.top || wY >= clientRect.bottom)
+        return false;
+      var x = tracing.findLowIndexInSortedIntervals(this.slices_,
+          function(x) { return x.start; },
+          function(x) { return x.duration; },
+          wX);
+      if (x >= 0 && x < this.slices_.length) {
+        onHitCallback('slice', this, this.slices_[x]);
+        return true;
+      }
+      return false;
+    },
+
+    /**
+     * Finds slices intersecting the given interval.
+     * @param {number} loWX Lower X bound of the interval to search, in
+     *     worldspace.
+     * @param {number} hiWX Upper X bound of the interval to search, in
+     *     worldspace.
+     * @param {number} loY Lower Y bound of the interval to search, in
+     *     offset space.
+     * @param {number} hiY Upper Y bound of the interval to search, in
+     *     offset space.
+     * @param {function():*} onHitCallback Function to call for each slice
+     *     intersecting the interval.
+     */
+    pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) {
+      var clientRect = this.getBoundingClientRect();
+      var a = Math.max(loY, clientRect.top);
+      var b = Math.min(hiY, clientRect.bottom);
+      if (a > b)
+        return;
+
+      var that = this;
+      function onPickHit(slice) {
+        onHitCallback('slice', that, slice);
+      }
+      tracing.iterateOverIntersectingIntervals(this.slices_,
+          function(x) { return x.start; },
+          function(x) { return x.duration; },
+          loWX, hiWX,
+          onPickHit);
+    },
+
+    /**
+     * Find the index for the given slice.
+     * @return {index} Index of the given slice, or undefined.
+     * @private
+     */
+    indexOfSlice_: function(slice) {
+      var index = tracing.findLowIndexInSortedArray(this.slices_,
+          function(x) { return x.start; },
+          slice.start);
+      while (index < this.slices_.length &&
+          slice.start == this.slices_[index].start &&
+          slice.colorId != this.slices_[index].colorId) {
+        index++;
+      }
+      return index < this.slices_.length ? index : undefined;
+    },
+
+    /**
+     * Return the next slice, if any, after the given slice.
+     * @param {slice} The previous slice.
+     * @return {slice} The next slice, or undefined.
+     * @private
+     */
+    pickNext: function(slice) {
+      var index = this.indexOfSlice_(slice);
+      if (index != undefined) {
+        if (index < this.slices_.length - 1)
+          index++;
+        else
+          index = undefined;
+      }
+      return index != undefined ? this.slices_[index] : undefined;
+    },
+
+    /**
+     * Return the previous slice, if any, before the given slice.
+     * @param {slice} A slice.
+     * @return {slice} The previous slice, or undefined.
+     */
+    pickPrevious: function(slice) {
+      var index = this.indexOfSlice_(slice);
+      if (index == 0)
+        return undefined;
+      else if ((index != undefined) && (index > 0))
+        index--;
+      return index != undefined ? this.slices_[index] : undefined;
+    }
+
+  };
+
+  /**
+   * A track that displays a TimelineCounter object.
+   * @constructor
+   * @extends {CanvasBasedTrack}
+   */
+
+  var TimelineCounterTrack = cr.ui.define(CanvasBasedTrack);
+
+  TimelineCounterTrack.prototype = {
+
+    __proto__: CanvasBasedTrack.prototype,
+
+    decorate: function() {
+      this.classList.add('timeline-counter-track');
+    },
+
+    get counter() {
+      return this.counter_;
+    },
+
+    set counter(counter) {
+      this.counter_ = counter;
+      this.invalidate();
+    },
+
+    redraw: function() {
+      var ctr = this.counter_;
+      var ctx = this.ctx_;
+      var canvasW = this.canvas_.width;
+      var canvasH = this.canvas_.height;
+
+      ctx.clearRect(0, 0, canvasW, canvasH);
+
+      // Culling parametrs.
+      var vp = this.viewport_;
+      var pixWidth = vp.xViewVectorToWorld(1);
+      var viewLWorld = vp.xViewToWorld(0);
+      var viewRWorld = vp.xViewToWorld(canvasW);
+
+      // Drop sampels that are less than skipDistancePix apart.
+      var skipDistancePix = 1;
+      var skipDistanceWorld = vp.xViewVectorToWorld(skipDistancePix);
+
+      // Begin rendering in world space.
+      ctx.save();
+      vp.applyTransformToCanavs(ctx);
+
+      // Figure out where drawing should begin.
+      var numSeries = ctr.numSeries;
+      var numSamples = ctr.numSamples;
+      var startIndex = tracing.findLowIndexInSortedArray(ctr.timestamps,
+                                                         function() {
+                                                         },
+                                                         viewLWorld);
+
+      // Draw indices one by one until we fall off the viewRWorld.
+      var yScale = canvasH / ctr.maxTotal;
+      for (var seriesIndex = ctr.numSeries - 1;
+           seriesIndex >= 0; seriesIndex--) {
+        var colorId = ctr.seriesColors[seriesIndex];
+        ctx.fillStyle = pallette[colorId];
+        ctx.beginPath();
+
+        // Set iLast and xLast such that the first sample we draw is the
+        // startIndex sample.
+        var iLast = startIndex - 1;
+        var xLast = iLast >= 0 ? ctr.timestamps[iLast] - skipDistanceWorld : -1;
+        var yLastView = canvasH;
+
+        // Iterate over samples from iLast onward until we either fall off the
+        // viewRWorld or we run out of samples. To avoid drawing too much, after
+        // drawing a sample at xLast, skip subsequent samples that are less than
+        // skipDistanceWorld from xLast.
+        var hasMoved = false;
+        while (true) {
+          var i = iLast + 1;
+          if (i >= numSamples) {
+            ctx.lineTo(xLast, yLastView);
+            ctx.lineTo(xLast + 8 * pixWidth, yLastView);
+            ctx.lineTo(xLast + 8 * pixWidth, canvasH);
+            break;
+          }
+
+          var x = ctr.timestamps[i];
+
+          var y = ctr.totals[i * numSeries + seriesIndex];
+          var yView = canvasH - (yScale * y);
+
+          if (x > viewRWorld) {
+            ctx.lineTo(x, yLastView);
+            ctx.lineTo(x, canvasH);
+            break;
+          }
+
+          if (x - xLast < skipDistanceWorld) {
+            iLast = i;
+            continue;
+          }
+
+          if (!hasMoved) {
+            ctx.moveTo(viewLWorld, canvasH);
+            hasMoved = true;
+          }
+          ctx.lineTo(x, yLastView);
+          ctx.lineTo(x, yView);
+          iLast = i;
+          xLast = x;
+          yLastView = yView;
+        }
+        ctx.closePath();
+        ctx.fill();
+      }
+      ctx.restore();
+    },
+
+    /**
+     * Picks a slice, if any, at a given location.
+     * @param {number} wX X location to search at, in worldspace.
+     * @param {number} wY Y location to search at, in offset space.
+     *     offset space.
+     * @param {function():*} onHitCallback Callback to call with the slice,
+     *     if one is found.
+     * @return {boolean} true if a slice was found, otherwise false.
+     */
+    pick: function(wX, wY, onHitCallback) {
+    },
+
+    /**
+     * Finds slices intersecting the given interval.
+     * @param {number} loWX Lower X bound of the interval to search, in
+     *     worldspace.
+     * @param {number} hiWX Upper X bound of the interval to search, in
+     *     worldspace.
+     * @param {number} loY Lower Y bound of the interval to search, in
+     *     offset space.
+     * @param {number} hiY Upper Y bound of the interval to search, in
+     *     offset space.
+     * @param {function():*} onHitCallback Function to call for each slice
+     *     intersecting the interval.
+     */
+    pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) {
+    }
+
+  };
+
+  return {
+    TimelineCounterTrack: TimelineCounterTrack,
+    TimelineSliceTrack: TimelineSliceTrack,
+    TimelineThreadTrack: TimelineThreadTrack,
+    TimelineCpuTrack: TimelineCpuTrack
+  };
+});
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+/**
+ * @fileoverview TimelineView visualizes TRACE_EVENT events using the
+ * tracing.Timeline component.
+ */
+cr.define('tracing', function() {
+  function tsRound(ts) {
+    return Math.round(ts * 1000.0) / 1000.0;
+  }
+  function getPadding(text, width) {
+    width = width || 0;
+
+    if (typeof text != 'string')
+      text = String(text);
+
+    if (text.length >= width)
+      return '';
+
+    var pad = '';
+    for (var i = 0; i < width - text.length; i++)
+      pad += ' ';
+    return pad;
+  }
+
+  function leftAlign(text, width) {
+    return text + getPadding(text, width);
+  }
+
+  function rightAlign(text, width) {
+    return getPadding(text, width) + text;
+  }
+
+  /**
+   * TimelineView
+   * @constructor
+   * @extends {HTMLDivElement}
+   */
+  TimelineView = cr.ui.define('div');
+
+  TimelineView.prototype = {
+    __proto__: HTMLDivElement.prototype,
+
+    decorate: function() {
+      this.classList.add('timeline-view');
+
+      this.timelineContainer_ = document.createElement('div');
+      this.timelineContainer_.className = 'timeline-container';
+
+      var summaryContainer_ = document.createElement('div');
+      summaryContainer_.className = 'summary-container';
+
+      this.summaryEl_ = document.createElement('pre');
+      this.summaryEl_.className = 'summary';
+
+      summaryContainer_.appendChild(this.summaryEl_);
+      this.appendChild(this.timelineContainer_);
+      this.appendChild(summaryContainer_);
+
+      this.onSelectionChangedBoundToThis_ = this.onSelectionChanged_.bind(this);
+    },
+
+    set traceData(traceData) {
+      this.model = new tracing.TimelineModel(traceData);
+    },
+
+    get model(model) {
+      return this.timelineModel_;
+    },
+
+    set model(model) {
+      this.timelineModel_ = model;
+
+      // remove old timeline
+      this.timelineContainer_.textContent = '';
+
+      // create new timeline if needed
+      if (this.timelineModel_.minTimestamp !== undefined) {
+        if (this.timeline_)
+          this.timeline_.detach();
+        this.timeline_ = new tracing.Timeline();
+        this.timeline_.model = this.timelineModel_;
+        this.timeline_.focusElement = this.parentElement;
+        this.timelineContainer_.appendChild(this.timeline_);
+        this.timeline_.addEventListener('selectionChange',
+                                        this.onSelectionChangedBoundToThis_);
+        this.onSelectionChanged_();
+      } else {
+        this.timeline_ = null;
+      }
+    },
+
+    get timeline() {
+      return this.timeline_;
+    },
+
+    onSelectionChanged_: function(e) {
+      var timeline = this.timeline_;
+      var selection = timeline.selection;
+      if (!selection.length) {
+        var oldScrollTop = this.timelineContainer_.scrollTop;
+        this.summaryEl_.textContent = timeline.keyHelp;
+        this.timelineContainer_.scrollTop = oldScrollTop;
+        return;
+      }
+
+      var text = '';
+      if (selection.length == 1) {
+        var c0Width = 14;
+        var slice = selection[0].slice;
+        text = 'Selected item:\n';
+        text += leftAlign('Title', c0Width) + ': ' + slice.title + '\n';
+        text += leftAlign('Start', c0Width) + ': ' +
+            tsRound(slice.start) + ' ms\n';
+        text += leftAlign('Duration', c0Width) + ': ' +
+            tsRound(slice.duration) + ' ms\n';
+        if (slice.durationInUserTime)
+          text += leftAlign('Duration (U)', c0Width) + ': ' +
+              tsRound(slice.durationInUserTime) + ' ms\n';
+
+        var n = 0;
+        for (var argName in slice.args) {
+          n += 1;
+        }
+        if (n > 0) {
+          text += leftAlign('Args', c0Width) + ':\n';
+          for (var argName in slice.args) {
+            var argVal = slice.args[argName];
+            text += leftAlign(' ' + argName, c0Width) + ': ' + argVal + '\n';
+          }
+        }
+      } else {
+        var c0Width = 55;
+        var c1Width = 12;
+        var c2Width = 5;
+        text = 'Selection summary:\n';
+        var tsLo = Math.min.apply(Math, selection.map(
+            function(s) {return s.slice.start;}));
+        var tsHi = Math.max.apply(Math, selection.map(
+            function(s) {return s.slice.end;}));
+
+        // compute total selection duration
+        var titles = selection.map(function(i) { return i.slice.title; });
+
+        var slicesByTitle = {};
+        for (var i = 0; i < selection.length; i++) {
+          var slice = selection[i].slice;
+          if (!slicesByTitle[slice.title])
+            slicesByTitle[slice.title] = {
+              slices: []
+            };
+          slicesByTitle[slice.title].slices.push(slice);
+        }
+        var totalDuration = 0;
+        for (var sliceGroupTitle in slicesByTitle) {
+          var sliceGroup = slicesByTitle[sliceGroupTitle];
+          var duration = 0;
+          for (i = 0; i < sliceGroup.slices.length; i++)
+            duration += sliceGroup.slices[i].duration;
+          totalDuration += duration;
+
+          text += ' ' +
+              leftAlign(sliceGroupTitle, c0Width) + ': ' +
+              rightAlign(tsRound(duration) + 'ms', c1Width) + '   ' +
+              rightAlign(String(sliceGroup.slices.length), c2Width) +
+              ' occurrences' + '\n';
+        }
+
+        text += leftAlign('*Totals', c0Width) + ' : ' +
+            rightAlign(tsRound(totalDuration) + 'ms', c1Width) + '   ' +
+            rightAlign(String(selection.length), c2Width) + ' occurrences' +
+            '\n';
+
+        text += '\n';
+
+        text += leftAlign('Selection start', c0Width) + ' : ' +
+            rightAlign(tsRound(tsLo) + 'ms', c1Width) +
+            '\n';
+        text += leftAlign('Selection extent', c0Width) + ' : ' +
+            rightAlign(tsRound(tsHi - tsLo) + 'ms', c1Width) +
+            '\n';
+      }
+
+      // done
+      var oldScrollTop = this.timelineContainer_.scrollTop;
+      this.summaryEl_.textContent = text;
+      this.timelineContainer_.scrollTop = oldScrollTop;
+    }
+  };
+
+  return {
+    TimelineView: TimelineView
+  };
+});
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+/**
+ * @fileoverview Provides a mechanism for drawing massive numbers of
+ * colored rectangles into a canvas in an efficient manner, provided
+ * they are drawn left to right with fixed y and height throughout.
+ *
+ * The basic idea used here is to fuse subpixel rectangles together so that
+ * we never issue a canvas fillRect for them. It turns out Javascript can
+ * do this quite efficiently, compared to asking Canvas2D to do the same.
+ *
+ * A few extra things are done by this class in the name of speed:
+ * - Viewport culling: off-viewport rectangles are discarded.
+ *
+ * - The actual discarding operation is done in world space,
+ *   e.g. pre-transform.
+ *
+ * - Rather than expending compute cycles trying to figure out an average
+ *   color for fused rectangles from css strings, you instead draw using
+ *   palletized colors. The fused rect is the max pallete index encountered.
+ *
+ * Make sure to flush the trackRenderer before finishing drawing in order
+ * to commit any queued drawing operations.
+ */
+cr.define('tracing', function() {
+
+  /**
+   * Creates a fast rect renderer with a specific set of culling rules
+   * and color pallette.
+   * @param {GraphicsContext2D} ctx Canvas2D drawing context.
+   * @param {number} vpLeft The leftmost visible part of the drawing viewport.
+   * @param {number} minRectSize Only rectangles with width < minRectSize are
+   *    considered for merging.
+   * @param {number} maxMergeDist Controls how many successive small rectangles
+   *    can be merged together before issuing a rectangle.
+   * @param {number} vpRight The rightmost visible part of the viewport.
+   * @param {Array} pallette The color pallete for drawing. Pallette slots
+   *    should map to valid Canvas fillStyle strings.
+   *
+   * @constructor
+   */
+  function FastRectRenderer(ctx, vpLeft, minRectSize, maxMergeDist, vpRight,
+                            pallette) {
+    this.ctx_ = ctx;
+    this.vpLeft_ = vpLeft;
+    this.minRectSize_ = minRectSize;
+    this.maxMergeDist_ = maxMergeDist;
+    this.vpRight_ = vpRight;
+    this.pallette_ = pallette;
+  }
+
+  FastRectRenderer.prototype = {
+    y_: 0,
+    h_: 0,
+    merging_: false,
+    mergeStartX_: 0,
+    mergeCurRight_: 0,
+
+    /**
+     * Changes the y position and height for subsequent fillRect
+     * calls. x and width are specifieid on the fillRect calls.
+     */
+    setYandH: function(y, h) {
+      this.flush();
+      this.y_ = y;
+      this.h_ = h;
+    },
+
+    /**
+     * Fills rectangle at the specified location, if visible. If the
+     * rectangle is subpixel, it will be merged with adjacent rectangles.
+     * The drawing operation may not take effect until flush is called.
+     * @param {number} colorId The color of this rectangle, as an index
+     *     in the renderer's color pallete.
+     */
+    fillRect: function(x, w, colorId) {
+      var r = x + w;
+      if (r < this.vpLeft_ || x > this.vpRight_) return;
+      if (w < this.minRectSize_) {
+        if (r - this.mergeStartX_ > this.maxMergeDist_)
+          this.flush();
+        if (!this.merging_) {
+          this.merging_ = true;
+          this.mergeStartX_ = x;
+          this.mergeCurRight_ = r;
+          this.mergedColorId = colorId;
+        } else {
+          this.mergeCurRight_ = r;
+          this.mergedColorId = Math.max(this.mergedColorId, colorId);
+        }
+      } else {
+        if (this.merging_)
+          this.flush();
+        this.ctx_.fillStyle = this.pallette_[colorId];
+        this.ctx_.fillRect(x, this.y_, w, this.h_);
+      }
+    },
+
+    /**
+     * Commits any pending fillRect operations to the underlying graphics
+     * context.
+     */
+    flush: function() {
+      if (this.merging_) {
+        this.ctx_.fillStyle = this.pallette_[this.mergedColorId];
+        this.ctx_.fillRect(this.mergeStartX_, this.y_,
+                           this.mergeCurRight_ - this.mergeStartX_, this.h_);
+        this.merging_ = false;
+      }
+    }
+  };
+
+  return {
+    FastRectRenderer: FastRectRenderer
+  };
+
+});
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Helper functions for use in tracing tests.
+ */
+
+
+/**
+ * goog.testing.assertion's assertEquals tweaked to do equality-to-a-constant.
+ * @param {*} a First value.
+ * @param {*} b Second value.
+ */
+function assertAlmostEquals(a, b) {
+  _validateArguments(2, arguments);
+  var var1 = nonCommentArg(1, 2, arguments);
+  var var2 = nonCommentArg(2, 2, arguments);
+  _assert(commentArg(2, arguments), Math.abs(var1 - var2) < 0.00001,
+          'Expected ' + _displayStringForValue(var1) + ' but was ' +
+          _displayStringForValue(var2));
+}
+
+cr.define('test_utils', function() {
+  function getAsync(url, cb) {
+    var req = new XMLHttpRequest();
+    req.open('GET', url, true);
+    req.onreadystatechange = function(aEvt) {
+      if (req.readyState == 4) {
+        window.setTimeout(function() {
+          if (req.status == 200) {
+            cb(req.responseText);
+          } else {
+            console.log('Failed to load ' + url);
+          }
+        }, 0);
+      }
+    };
+    req.send(null);
+  }
+  return {
+    getAsync: getAsync
+  };
+});
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..4116787
--- /dev/null
+++ b/style.css
@@ -0,0 +1 @@
+.timeline-drag-box{background-color:rgba(0,0,255,0.25);border:1px solid #000060 font-size:75%;position:fixed;}.timeline-thread-track{-webkit-box-orient:vertical;display:-webkit-box;padding:1px 0;}.timeline-thread-track:not(:first-child){border-top:1px solid #D0D0D0;}.timeline-canvas-based-track{-webkit-box-orient:horizontal;-webkit-box-align:stretch;background-color:white;display:-webkit-box;margin:0;padding:0;padding-right:5px;}.timeline-canvas-based-track-title{overflow:hidden;padding-right:5px;text-align:right;text-overflow:ellipsis;white-space:nowrap;}.timeline-canvas-based-track-canvas-container{-webkit-box-flex:1;width:100%;}.timeline-canvas-based-track-canvas{-webkit-box-flex:1;display:block;height:100%;width:100%;}.timeline-slice-track{height:18px;}.timeline-counter-track{height:30px;}.timeline-view{padding:0;-webkit-box-orient:vertical;-webkit-box-flex:1;display:-webkit-box;}.timeline-view>.timeline{display:-webkit-box;-webkit-box-flex:1;overflow:auto;}.timeline-view .timeline-container{-webkit-box-flex:1;display:-webkit-box;overflow:auto;}.timeline-view .timeline-container>*{-webkit-box-flex:1;}.timeline-view .summary-container *{-webkit-user-select:text;}.timeline-view .summary-container{border-top:1px solid black;max-height:250px;min-height:250px;font-family:monospace;overflow:auto;}.timeline-view .selection{margin:2px;}.timeline-view .selection ul{margin:0;}
\ No newline at end of file
diff --git a/systrace.py b/systrace.py
index ddaaa63..76cbb33 100755
--- a/systrace.py
+++ b/systrace.py
@@ -1,20 +1,8 @@
 #!/usr/bin/env python
 
-#
-# Copyright 2012, The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Android system-wide tracing utility.
 
@@ -58,9 +46,15 @@
     else:
       parser.error('the trace buffer size must be a positive number')
 
+  script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
+  css_filename = os.path.join(script_dir, 'style.css')
+  js_filename = os.path.join(script_dir, 'script.js')
+  css = open(css_filename).read()
+  js = open(js_filename).read()
+
   html_filename = options.output_file
   html_file = open(html_filename, 'w')
-  html_file.write(html_prefix)
+  html_file.write(html_prefix % (css, js))
 
   trace_started = False
   leftovers = ''
@@ -107,22 +101,8 @@
 <html>
 <head i18n-values="dir:textdirection;">
 <title>Android System Trace</title>
-<link rel="stylesheet" href="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/timeline.css">
-<link rel="stylesheet" href="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/timeline_view.css">
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/shared/js/cr.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/shared/js/cr/event_target.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/shared/js/cr/ui.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/shared/js/util.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/timeline_model.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/linux_perf_importer.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/trace_event_importer.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/sorted_array_utils.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/measuring_stick.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/timeline.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/timeline_track.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/timeline_view.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/fast_rect_renderer.js"></script>
-<script src="http://www.corp.google.com/~jgennis/android_tracing/0.1/viewer/test_utils.js"></script>
+<style type="text/css">%s</style>
+<script language="javascript">%s</script>
 <style>
   .view {
     overflow: hidden;
diff --git a/update.sh b/update.sh
new file mode 100755
index 0000000..5d0df3d
--- /dev/null
+++ b/update.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+CSS_FILES="
+  www/viewer/timeline.css
+  www/viewer/timeline_view.css
+"
+
+JS_FILES="
+  www/shared/js/cr.js
+  www/shared/js/cr/event_target.js
+  www/shared/js/cr/ui.js
+  www/shared/js/util.js
+  www/viewer/timeline_model.js
+  www/viewer/linux_perf_importer.js
+  www/viewer/trace_event_importer.js
+  www/viewer/sorted_array_utils.js
+  www/viewer/measuring_stick.js
+  www/viewer/timeline.js
+  www/viewer/timeline_track.js
+  www/viewer/timeline_view.js
+  www/viewer/fast_rect_renderer.js
+  www/viewer/test_utils.js
+"
+
+cat $CSS_FILES | yui-compressor --type css -o style.css
+if [ "$?" -ne 0 ]; then
+  echo "failed to update style.css"
+  exit
+else
+  echo "updated style.css"
+fi
+
+#cat $JS_FILES | yui-compressor --type js -o script.js
+cat $JS_FILES > script.js
+if [ "$?" -ne 0 ]; then
+  echo "failed to update script.js"
+  exit
+else
+  echo "updated script.js"
+fi
+