Initial import of the Chrome Frame codebase. Integration in chrome.gyp coming in a separate CL.

BUG=None
TEST=None

Review URL: http://codereview.chromium.org/218019

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@27042 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome_frame/CFInstall.js b/chrome_frame/CFInstall.js
new file mode 100644
index 0000000..915f958
--- /dev/null
+++ b/chrome_frame/CFInstall.js
@@ -0,0 +1,222 @@
+// Copyright (c) 2009 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 CFInstall.js provides a set of utilities for managing
+ * the Chrome Frame detection and installation process.
+ * @author [email protected] (Alex Russell)
+ */
+
+(function(scope) {
+  // bail if we'd be over-writing an existing CFInstall object
+  if (scope['CFInstall']) {
+    return;
+  }
+
+  /** 
+   * returns an item based on DOM ID. Optionally a document may be provided to
+   * specify the scope to search in. If a node is passed, it's returned as-is.
+   * @param {string|Node} id The ID of the node to be located or a node
+   * @param {Node} doc Optional A document to search for id.
+   * @return {Node} 
+   */
+  var byId = function(id, doc) {
+    return (typeof id == 'string') ? (doc || document).getElementById(id) : id;
+  };
+
+  /////////////////////////////////////////////////////////////////////////////
+  // Plugin Detection
+  /////////////////////////////////////////////////////////////////////////////
+  
+  var cachedAvailable;
+
+  /** 
+   * Checks to find out if ChromeFrame is available as a plugin
+   * @return {Boolean} 
+   */
+  var isAvailable = function() {
+    if (typeof cachedAvailable != 'undefined') {
+      return cachedAvailable;
+    }
+
+    cachedAvailable = false;
+
+    // Look for CF in the User Agent before trying more expensive checks
+    var ua = navigator.userAgent.toLowerCase();
+    if (ua.indexOf("chromeframe") >= 0 || ua.indexOf("x-clock") >= 0) {
+      cachedAvailable = true;
+      return cachedAvailable;
+    }
+
+    if (typeof window['ActiveXObject'] != 'undefined') {
+      try {
+        var obj = new ActiveXObject('ChromeTab.ChromeFrame');
+        if (obj) {
+          cachedAvailable = true;
+        }
+      } catch(e) {
+        // squelch
+      }
+    }
+    return cachedAvailable;
+  };
+
+
+  /** @type {boolean} */
+  var cfStyleTagInjected = false;
+
+  /** 
+   * Creates a style sheet in the document which provides default styling for
+   * ChromeFrame instances. Successive calls should have no additive effect.
+   */
+  var injectCFStyleTag = function() {
+    if (cfStyleTagInjected) {
+      // Once and only once
+      return;
+    }
+    try {
+      var rule = '.chromeFrameInstallDefaultStyle {' +
+                    'width: 500px;' +
+                    'height: 400px;' +
+                    'padding: 0;' +
+                    'border: 1px solid #0028c4;' +
+                    'margin: 0;' +
+                  '}';
+      var ss = document.createElement('style');
+      ss.setAttribute('type', 'text/css');
+      if (ss.styleSheet) {
+        ss.styleSheet.cssText = rule;
+      } else {
+        ss.appendChild(document.createTextNode(rule));
+      }
+      var h = document.getElementsByTagName('head')[0];
+      var firstChild = h.firstChild;
+      h.insertBefore(ss, firstChild);
+      cfStyleTagInjected = true;
+    } catch (e) {
+      // squelch
+    }
+  };
+
+
+  /** 
+   * Plucks properties from the passed arguments and sets them on the passed
+   * DOM node
+   * @param {Node} node The node to set properties on
+   * @param {Object} args A map of user-specified properties to set
+   */
+  var setProperties = function(node, args) {
+    injectCFStyleTag();
+
+    var srcNode = byId(args['node']);
+
+    node.id = args['id'] || (srcNode ? srcNode['id'] || getUid(srcNode) : '');
+
+    // TODO(slightlyoff): Opera compat? need to test there
+    var cssText = args['cssText'] || '';
+    node.style.cssText = ' ' + cssText;
+
+    var classText = args['className'] || '';
+    node.className = 'chromeFrameInstallDefaultStyle ' + classText;
+
+    // default if the browser doesn't so we don't show sad-tab
+    var src = args['src'] || 'about:blank';
+
+    node.src = src;
+
+    if (srcNode) {
+      srcNode.parentNode.replaceChild(node, srcNode);
+    }
+  };
+
+  /** 
+   * Creates an iframe.
+   * @param {Object} args A bag of configuration properties, including values
+   *    like 'node', 'cssText', 'className', 'id', 'src', etc.
+   * @return {Node} 
+   */
+  var makeIframe = function(args) {
+    var el = document.createElement('iframe');
+    setProperties(el, args);
+    return el;
+  };
+
+  var CFInstall = {};
+  /** 
+   * Checks to see if Chrome Frame is available, if not, prompts the user to
+   * install. Once installation is begun, a background timer starts,
+   * checkinging for a successful install every 2 seconds. Upon detection of
+   * successful installation, the current page is reloaded, or if a
+   * 'destination' parameter is passed, the page navigates there instead.
+   * @param {Object} args A bag of configuration properties. Respected
+   *    properties are: 'mode', 'url', 'destination', 'node', 'onmissing',
+   *    'preventPrompt', 'oninstall', 'preventInstallDetection', 'cssText', and
+   *    'className'.
+   * @public
+   */
+  CFInstall.check = function(args) {
+    args = args || {};
+
+    // We currently only support CF in IE 
+    // TODO(slightlyoff): Update this should we support other browsers!
+    var ieRe = /MSIE (\S+)/;
+    if (!ieRe.test(navigator.userAgent)) {
+      return;
+    }
+
+
+    // TODO(slightlyoff): Update this URL when a mini-installer page is
+    //   available.
+    var installUrl = '//www.google.com/chromeframe';
+    if (!isAvailable()) {
+      if (args.onmissing) {
+        args.onmissing();
+      }
+
+      args.src = args.url || installUrl;
+      var mode = args.mode || 'inline';
+      var preventPrompt = args.preventPrompt || false;
+
+      if (!preventPrompt) {
+        if (mode == 'inline') {
+          var ifr = makeIframe(args);
+          // TODO(slightlyoff): handle placement more elegantly!
+          if (!ifr.parentNode) {
+            var firstChild = document.body.firstChild;
+            document.body.insertBefore(ifr, firstChild);
+          }
+        } else {
+          window.open(args.src);
+        }
+      }
+
+      if (args.preventInstallDetection) {
+        return;
+      }
+
+      // Begin polling for install success.
+      var installTimer = setInterval(function() {
+          // every 2 seconds, look to see if CF is available, if so, proceed on
+          // to our destination
+          if (isAvailable()) {
+            if (args.oninstall) {
+              args.oninstall();
+            }
+
+            clearInterval(installTimer);
+            // TODO(slightlyoff): add a way to prevent navigation or make it
+            //    contingent on oninstall?
+            window.location = args.destination || window.location;
+          }
+      }, 2000);
+    }
+  };
+
+  CFInstall.isAvailable = isAvailable;
+
+  // expose CFInstall to the external scope. We've already checked to make
+  // sure we're not going to blow existing objects away.
+  scope.CFInstall = CFInstall;
+
+})(this['ChromeFrameInstallScope'] || this);
diff --git a/chrome_frame/CFInstance.js b/chrome_frame/CFInstance.js
new file mode 100644
index 0000000..90a28f5f
--- /dev/null
+++ b/chrome_frame/CFInstance.js
@@ -0,0 +1,1656 @@
+// Copyright (c) 2009 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.
+
+// Parts Copyright 2005-2009, the Dojo Foundation. Used under the terms of the
+// "New" BSD License:
+//
+//  http://download.dojotoolkit.org/release-1.3.2/dojo-release-1.3.2/dojo/LICENSE
+//
+
+/**
+ * @fileoverview CFInstance.js provides a set of utilities for managing
+ * ChromeFrame plugins, including creation, RPC services, and a singleton to
+ * use for communicating from ChromeFrame hosted content to an external
+ * CFInstance wrapper. CFInstance.js is stand-alone, designed to be served from
+ * a CDN, and built to not create side-effects for other hosted content.
+ * @author [email protected] (Alex Russell)
+ */
+
+(function(scope) {
+  // TODO:
+  //   * figure out if there's any way to install w/o a browser restart, and if
+  //     so, where and how
+  //   * slim down Deferred and RPC scripts
+  //   * determine what debugging APIs should be exposed and how they should be
+  //     surfaced. What about content authoring in Chrome instances? Stubbing
+  //     the other side of RPC's?
+
+  // bail if we'd be over-writing an existing CFInstance object
+  if (scope['CFInstance']) {
+    return;
+  }
+
+  /////////////////////////////////////////////////////////////////////////////
+  // Utiliity and Cross-Browser Functions
+  /////////////////////////////////////////////////////////////////////////////
+
+  // a monotonically incrementing counter
+  var _counter = 0;
+
+  var undefStr = 'undefined';
+
+  //
+  // Browser detection: ua.isIE, ua.isSafari, ua.isOpera, etc.
+  //
+
+  /**
+   * An object for User Agent detection
+   * @type {!Object}
+   * @protected
+   */
+  var ua = {};
+  var n = navigator;
+  var dua = String(n.userAgent);
+  var dav = String(n.appVersion);
+  var tv = parseFloat(dav);
+  var duaParse = function(s){
+    var c = 0;
+    try {
+      return parseFloat(
+        dua.split(s)[1].replace(/\./g, function() {
+          c++;
+          return (c > 1) ? '' : '.';
+        } )
+      );
+    } catch(e) {
+      // squelch to intentionally return undefined
+    }
+  };
+  /** @type {number} */
+  ua.isOpera = dua.indexOf('Opera') >= 0 ? tv: undefined;
+  /** @type {number} */
+  ua.isWebKit = duaParse('WebKit/');
+  /** @type {number} */
+  ua.isChrome = duaParse('Chrome/');
+  /** @type {number} */
+  ua.isKhtml = dav.indexOf('KHTML') >= 0 ? tv : undefined;
+
+  var index = Math.max(dav.indexOf('WebKit'), dav.indexOf('Safari'), 0);
+
+  if (index && !ua.isChrome) {
+    /** @type {number} */
+    ua.isSafari = parseFloat(dav.split('Version/')[1]);
+    if(!ua.isSafari || parseFloat(dav.substr(index + 7)) <= 419.3){
+      ua.isSafari = 2;
+    }
+  }
+
+  if (dua.indexOf('Gecko') >= 0 && !ua.isKhtml) {
+    /** @type {number} */
+    ua.isGecko = duaParse(' rv:');
+  }
+
+  if (ua.isGecko) {
+    /** @type {number} */
+    ua.isFF = parseFloat(dua.split('Firefox/')[1]) || undefined;
+  }
+
+  if (document.all && !ua.isOpera) {
+    /** @type {number} */
+    ua.isIE = parseFloat(dav.split('MSIE ')[1]) || undefined;
+  }
+
+ 
+  /**
+   * Log out varargs to a browser-provided console object (if available). Else
+   * a no-op.
+   * @param {*} var_args Optional Things to log.
+   * @protected
+   **/
+  var log = function() {
+    if (window['console']) {
+      try {
+        if (ua.isSafari || ua.isChrome) {
+          throw Error();
+        }
+        console.log.apply(console, arguments);
+      } catch(e) { 
+        try {
+          console.log(toArray(arguments).join(' '));
+        } catch(e2) {
+          // squelch
+        }
+      }
+    }
+  };
+
+  //
+  // Language utility methods
+  //
+ 
+  /**
+   * Determine if the passed item is a String
+   * @param {*} item Item to test.
+   * @protected
+   **/
+  var isString = function(item) {
+    return typeof item == 'string';
+  };
+
+  /** 
+   * Determine if the passed item is a Function object
+   * @param {*} item Item to test.
+   * @protected
+   **/
+  var isFunction = function(item) {
+    return (
+      item && (
+        typeof item == 'function' || item instanceof Function
+      )
+    );
+  };
+
+  /** 
+   * Determine if the passed item is an array.
+   * @param {*} item Item to test.
+   * @protected
+   **/
+  var isArray = function(item){
+    return (
+      item && (
+        item instanceof Array || (
+          typeof item == 'object' && 
+          typeof item.length != undefStr
+        )
+      )
+    );
+  };
+
+  /**
+   * A toArray version which takes advantage of builtins
+   * @param {*} obj The array-like object to convert to a real array.
+   * @param {number} opt_offset An index to being copying from in the source.
+   * @param {Array} opt_startWith An array to extend with elements of obj in
+   *    lieu of creating a new array to return.
+   * @private
+   **/
+  var _efficientToArray = function(obj, opt_offset, opt_startWith){
+    return (opt_startWith || []).concat(
+                              Array.prototype.slice.call(obj, opt_offset || 0 )
+                             );
+  };
+
+  /**
+   * A version of toArray that iterates in lieu of using array generics.
+   * @param {*} obj The array-like object to convert to a real array.
+   * @param {number} opt_offset An index to being copying from in the source.
+   * @param {Array} opt_startWith An array to extend with elements of obj in
+   * @private
+   **/
+  var _slowToArray = function(obj, opt_offset, opt_startWith){
+    var arr = opt_startWith || []; 
+    for(var x = opt_offset || 0; x < obj.length; x++){ 
+      arr.push(obj[x]); 
+    } 
+    return arr;
+  };
+
+  /** 
+   * Converts an array-like object (e.g., an "arguments" object) to a real
+   * Array.
+   * @param {*} obj The array-like object to convert to a real array.
+   * @param {number} opt_offset An index to being copying from in the source.
+   * @param {Array} opt_startWith An array to extend with elements of obj in
+   * @protected
+   */
+  var toArray = ua.isIE ?  
+    function(obj){
+      return (
+        obj.item ? _slowToArray : _efficientToArray
+      ).apply(this, arguments);
+    } :
+    _efficientToArray;
+
+  var _getParts = function(arr, obj, cb){
+    return [ 
+      isString(arr) ? arr.split('') : arr, 
+      obj || window,
+      isString(cb) ? new Function('item', 'index', 'array', cb) : cb
+    ];
+  };
+
+  /** 
+   * like JS1.6 Array.forEach()
+   * @param {Array} arr the array to iterate
+   * @param {function(Object, number, Array)} callback the method to invoke for
+   *     each item in the array
+   * @param {function?} thisObject Optional a scope to use with callback
+   * @return {array} the original arr
+   * @protected
+   */
+  var forEach = function(arr, callback, thisObject) {
+    if(!arr || !arr.length){ 
+      return arr;
+    }
+    var parts = _getParts(arr, thisObject, callback); 
+    // parts has a structure of:
+    //  [
+    //    array,
+    //    scope,
+    //    function
+    //  ]
+    arr = parts[0];
+    for (var i = 0, l = arr.length; i < l; ++i) { 
+      parts[2].call( parts[1], arr[i], i, arr );
+    }
+    return arr;
+  };
+
+  /** 
+   * returns a new function bound to scope with a variable number of positional
+   * params pre-filled
+   * @private
+   */
+  var _hitchArgs = function(scope, method /*,...*/) {
+    var pre = toArray(arguments, 2);
+    var named = isString(method);
+    return function() {
+      var args = toArray(arguments);
+      var f = named ? (scope || window)[method] : method;
+      return f && f.apply(scope || this, pre.concat(args));
+    }
+  };
+
+  /** 
+   * Like goog.bind(). Hitches the method (named or provided as a function
+   * object) to scope, optionally partially applying positional arguments. 
+   * @param {Object} scope the object to hitch the method to
+   * @param {string|function} method the method to be bound
+   * @return {function} The bound method
+   * @protected
+   */
+  var hitch = function(scope, method){
+    if (arguments.length > 2) {
+      return _hitchArgs.apply(window, arguments);  // Function
+    }
+
+    if (!method) {
+      method = scope;
+      scope = null;
+    }
+
+    if (isString(method)) {
+      scope = scope || window;
+      if (!scope[method]) {
+        throw(
+          ['scope["', method, '"] is null (scope="', scope, '")'].join('')
+        );
+      }
+      return function() {
+        return scope[method].apply(scope, arguments || []);
+      };
+    }
+
+    return !scope ?
+        method :
+        function() {
+          return method.apply(scope, arguments || []);
+        };
+  };
+
+  /** 
+   * A version of addEventListener that works on IE too. *sigh*.
+   * @param {!Object} obj The object to attach to
+   * @param {!String} type Name of the event to attach to
+   * @param {!Function} handler The function to connect
+   * @protected
+   */
+  var listen = function(obj, type, handler) {
+    if (obj['attachEvent']) {
+      obj.attachEvent('on' + type, handler);
+    } else {
+      obj.addEventListener(type, handler, false);
+    }
+  };
+
+  /** 
+   * Adds "listen" and "_dispatch" methods to the passed object, taking
+   * advantage of native event hanlding if it's available.
+   * @param {Object} instance The object to install the event system on
+   * @protected
+   */
+  var installEvtSys = function(instance) {
+    var eventsMap = {};
+
+    var isNative = (
+      (typeof instance.addEventListener != undefStr) && 
+      ((instance['tagName'] || '').toLowerCase()  != 'iframe')
+    );
+
+    instance.listen = function(type, func) {
+      var t = eventsMap[type];
+      if (!t) {
+        t = eventsMap[type] = [];
+        if (isNative) {
+          listen(instance, type, hitch(instance, "_dispatch", type));
+        }
+      }
+      t.push(func);
+      return t;
+    };
+
+    instance._dispatch = function(type, evt) {
+      var stopped = false;
+      var stopper = function() {
+        stopped = true;
+      };
+
+      forEach(eventsMap[type], function(f) {
+        if (!stopped) {
+          f(evt, stopper);
+        }
+      });
+    };
+
+    return instance;
+  };
+
+  /** 
+   * Deserialize the passed JSON string
+   * @param {!String} json A string to be deserialized
+   * @return {Object}
+   * @protected
+   */
+  var fromJson = window['JSON'] ? function(json) {
+    return JSON.parse(json);
+  } :
+  function(json) {
+    return eval('(' + (json || undefStr) + ')');
+  };
+
+  /** 
+   * String escaping for use in JSON serialization
+   * @param {string} str The string to escape
+   * @return {string} 
+   * @private
+   */
+  var _escapeString = function(str) {
+    return ('"' + str.replace(/(["\\])/g, '\\$1') + '"').
+             replace(/[\f]/g, '\\f').
+             replace(/[\b]/g, '\\b').
+             replace(/[\n]/g, '\\n').
+             replace(/[\t]/g, '\\t').
+             replace(/[\r]/g, '\\r').
+             replace(/[\x0B]/g, '\\u000b'); // '\v' is not supported in JScript;
+  };
+
+  /** 
+   * JSON serialization for arbitrary objects. Circular references or strong
+   * typing information are not handled.
+   * @param {Object} it Any valid JavaScript object or type
+   * @return {string} the serialized representation of the passed object
+   * @protected
+   */
+  var toJson = window['JSON'] ? function(it) {
+    return JSON.stringify(it);
+  } :
+  function(it) {
+
+    if (it === undefined) {
+      return undefStr;
+    }
+
+    var objtype = typeof it;
+    if (objtype == 'number' || objtype == 'boolean') {
+      return it + '';
+    }
+
+    if (it === null) {
+      return 'null';
+    }
+
+    if (isString(it)) { 
+      return _escapeString(it); 
+    }
+
+    // recurse
+    var recurse = arguments.callee;
+
+    if(it.nodeType && it.cloneNode){ // isNode
+      // we can't seriailize DOM nodes as regular objects because they have
+      // cycles DOM nodes could be serialized with something like outerHTML,
+      // but that can be provided by users in the form of .json or .__json__
+      // function.
+      throw new Error('Cannot serialize DOM nodes');
+    }
+
+    // array
+    if (isArray(it)) {
+      var res = [];
+      forEach(it, function(obj) {
+        var val = recurse(obj);
+        if (typeof val != 'string') {
+          val = undefStr;
+        }
+        res.push(val);
+      });
+      return '[' + res.join(',') + ']';
+    }
+
+    if (objtype == 'function') {
+      return null;
+    }
+
+    // generic object code path
+    var output = [];
+    for (var key in it) {
+      var keyStr, val;
+      if (typeof key == 'number') {
+        keyStr = '"' + key + '"';
+      } else if(typeof key == 'string') {
+        keyStr = _escapeString(key);
+      } else {
+        // skip non-string or number keys
+        continue;
+      }
+      val = recurse(it[key]);
+      if (typeof val != 'string') {
+        // skip non-serializable values
+        continue;
+      }
+      // TODO(slightlyoff): use += on Moz since it's faster there
+      output.push(keyStr + ':' + val);
+    }
+    return '{' + output.join(',') + '}'; // String
+  };
+
+  // code to register with the earliest safe onload-style handler
+
+  var _loadedListenerList = [];
+  var _loadedFired = false;
+
+  /** 
+   * a default handler for document onload. When called (the first time),
+   * iterates over the list of registered listeners, calling them in turn.
+   * @private
+   */
+  var documentLoaded = function() {
+    if (!_loadedFired) {
+      _loadedFired = true;
+      forEach(_loadedListenerList, 'item();');
+    }
+  };
+
+  if (document.addEventListener) {
+    // NOTE: 
+    //    due to a threading issue in Firefox 2.0, we can't enable
+    //    DOMContentLoaded on that platform. For more information, see:
+    //    http://trac.dojotoolkit.org/ticket/1704
+    if (ua.isWebKit > 525 || ua.isOpera || ua.isFF >= 3) {
+      listen(document, 'DOMContentLoaded', documentLoaded);
+    }
+    //  mainly for Opera 8.5, won't be fired if DOMContentLoaded fired already.
+    //  also used for FF < 3.0 due to nasty DOM race condition
+    listen(window, 'load', documentLoaded);
+  } else {
+      // crazy hack for IE that relies on the "deferred" behavior of script
+      // tags
+      document.write(
+        '<scr' + 'ipt defer src="//:" '
+        + 'onreadystatechange="if(this.readyState==\'complete\')'
+        +    '{ CFInstance._documentLoaded();}">'
+        + '</scr' + 'ipt>'
+      );
+  }
+
+  // TODO(slightlyoff): known KHTML init issues are ignored for now
+
+  //
+  // DOM utility methods
+  //
+
+  /** 
+   * returns an item based on DOM ID. Optionally a doucment may be provided to
+   * specify the scope to search in. If a node is passed, it's returned as-is.
+   * @param {string|Node} id The ID of the node to be located or a node
+   * @param {Node} doc Optional A document to search for id.
+   * @return {Node} 
+   * @protected
+   */
+  var byId = (ua.isIE || ua.isOpera) ?
+    function(id, doc) {
+      if (isString(id)) {
+        doc = doc || document;
+        var te = doc.getElementById(id);
+        // attributes.id.value is better than just id in case the 
+        // user has a name=id inside a form
+        if (te && te.attributes.id.value == id) {
+          return te;
+        } else {
+          var elements = doc.all[id];
+          if (!elements || !elements.length) {
+            return elements;
+          }
+          // if more than 1, choose first with the correct id
+          var i=0;
+          while (te = elements[i++]) {
+            if (te.attributes.id.value == id) {
+              return te;
+            }
+          }
+        }
+      } else {
+        return id; // DomNode
+      }
+    } : 
+    function(id, doc) {
+      return isString(id) ? (doc || document).getElementById(id) : id;
+    };
+
+
+  /**
+   * returns a unique DOM id which can be used to locate the node via byId().
+   * If the node already has an ID, it's used. If not, one is generated. Like
+   * IE's uniqueID property.
+   * @param {Node} node The element to create or fetch a unique ID for
+   * @return {String}
+   * @protected
+   */
+  var getUid = function(node) {
+    var u = 'cfUnique' + (_counter++);
+    return (!node) ? u : ( node.id || node.uniqueID || (node.id = u) );
+  };
+
+  //
+  // the Deferred class, borrowed from Twisted Python and Dojo
+  //
+
+  /** 
+   * A class that models a single response (past or future) to a question.
+   * Multiple callbacks and error handlers may be added. If the response was
+   * added in the past, adding callbacks has the effect of calling them
+   * immediately. In this way, Deferreds simplify thinking about synchronous
+   * vs. asynchronous programming in languages which don't have continuations
+   * or generators which might otherwise provide syntax for deferring
+   * operations. 
+   * @param {function} canceller Optional A function to be called when the
+   *     Deferred is canceled.
+   * @param {number} timeout Optional How long to wait (in ms) before errback
+   *     is called with a timeout error. If no timeout is passed, the default
+   *     is 1hr. Passing -1 will disable any timeout.
+   * @constructor
+   * @public
+   */
+  Deferred = function(/*Function?*/ canceller, timeout){
+    //  example:
+    //    var deferred = new Deferred();
+    //    setTimeout(function(){ deferred.callback({success: true}); }, 1000);
+    //    return deferred;
+    this.chain = [];
+    this.id = _counter++;
+    this.fired = -1;
+    this.paused = 0;
+    this.results = [ null, null ];
+    this.canceller = canceller;
+    // FIXME(slightlyoff): is it really smart to be creating this many timers?
+    if (typeof timeout == 'number') {
+      if (timeout <= 0) {
+        timeout = 216000;  // give it an hour
+      }
+    }
+    this._timer = setTimeout(
+                    hitch(this, 'errback', new Error('timeout')), 
+                    (timeout || 1000)
+                  );
+    this.silentlyCancelled = false;
+  };
+
+  /**
+   * Cancels a Deferred that has not yet received a value, or is waiting on
+   * another Deferred as its value. If a canceller is defined, the canceller
+   * is called. If the canceller did not return an error, or there was no
+   * canceller, then the errback chain is started.
+   * @public
+   */
+  Deferred.prototype.cancel = function() {
+    var err;
+    if (this.fired == -1) {
+      if (this.canceller) {
+        err = this.canceller(this);
+      } else {
+        this.silentlyCancelled = true;
+      }
+      if (this.fired == -1) {
+        if ( !(err instanceof Error) ) {
+          var res = err;
+          var msg = 'Deferred Cancelled';
+          if (err && err.toString) {
+            msg += ': ' + err.toString();
+          }
+          err = new Error(msg);
+          err.dType = 'cancel';
+          err.cancelResult = res;
+        }
+        this.errback(err);
+      }
+    } else if (
+      (this.fired == 0) &&
+      (this.results[0] instanceof Deferred)
+    ) {
+      this.results[0].cancel();
+    }
+  };
+
+
+  /**
+   * internal function for providing a result. If res is an instance of Error,
+   * we treat it like such and start the error chain.
+   * @param {Object|Error} res the result
+   * @private
+   */
+  Deferred.prototype._resback = function(res) {
+    if (this._timer) {
+      clearTimeout(this._timer);
+    }
+    this.fired = res instanceof Error ? 1 : 0;
+    this.results[this.fired] = res;
+    this._fire();
+  };
+
+  /**
+   * determine if the deferred has already been resolved
+   * @return {boolean}
+   * @private
+   */
+  Deferred.prototype._check = function() {
+    if (this.fired != -1) {
+      if (!this.silentlyCancelled) {
+        return 0;
+      }
+      this.silentlyCancelled = 0;
+      return 1;
+    }
+    return 0;
+  };
+
+  /**
+   * Begin the callback sequence with a non-error value.
+   * @param {Object|Error} res the result
+   * @public
+   */
+  Deferred.prototype.callback = function(res) {
+    this._check();
+    this._resback(res);
+  };
+
+  /**
+   * Begin the callback sequence with an error result.
+   * @param {Error|string} res the result. If not an Error, it's treated as the
+   *     message for a new Error.
+   * @public
+   */
+  Deferred.prototype.errback = function(res) {
+    this._check();
+    if ( !(res instanceof Error) ) {
+      res = new Error(res);
+    }
+    this._resback(res);
+  };
+
+  /** 
+   * Add a single function as the handler for both callback and errback,
+   * allowing you to specify a scope (unlike addCallbacks).
+   * @param {function|Object} cb A function. If cbfn is passed, the value of cb
+   *     is treated as a scope
+   * @param {function|string} cbfn Optional A function or name of a function in
+   *     the scope cb.
+   * @return {Deferred} this
+   * @public
+   */
+  Deferred.prototype.addBoth = function(cb, cbfn) {
+    var enclosed = hitch.apply(window, arguments);
+    return this.addCallbacks(enclosed, enclosed);
+  };
+
+  /** 
+   * Add a single callback to the end of the callback sequence. Add a function
+   * as the handler for successful resolution of the Deferred. May be called
+   * multiple times to register many handlers. Note that return values are
+   * chained if provided, so it's best for callback handlers not to return
+   * anything.
+   * @param {function|Object} cb A function. If cbfn is passed, the value of cb
+   *     is treated as a scope
+   * @param {function|string} cbfn Optional A function or name of a function in
+   *     the scope cb.
+   * @return {Deferred} this
+   * @public
+   */
+  Deferred.prototype.addCallback = function(cb, cbfn /*...*/) {
+    return this.addCallbacks(hitch.apply(window, arguments));
+  };
+
+
+  /** 
+   * Add a function as the handler for errors in the Deferred. May be called
+   * multiple times to add multiple error handlers.
+   * @param {function|Object} cb A function. If cbfn is passed, the value of cb
+   *     is treated as a scope
+   * @param {function|string} cbfn Optional A function or name of a function in
+   *     the scope cb.
+   * @return {Deferred} this
+   * @public
+   */
+  Deferred.prototype.addErrback = function(cb, cbfn) {
+    return this.addCallbacks(null, hitch.apply(window, arguments));
+  };
+
+  /** 
+   * Add a functions as handlers for callback and errback in a single shot.
+   * @param {function} callback A function
+   * @param {function} errback A function 
+   * @return {Deferred} this
+   * @public
+   */
+  Deferred.prototype.addCallbacks = function(callback, errback) {
+    this.chain.push([callback, errback]);
+    if (this.fired >= 0) {
+      this._fire();
+    }
+    return this;
+  };
+
+  /** 
+   * when this Deferred is satisfied, pass it on to def, allowing it to run.
+   * @param {Deferred} def A deferred to add to the end of this Deferred in a chain
+   * @return {Deferred} this
+   * @public
+   */
+  Deferred.prototype.chain = function(def) {
+    this.addCallbacks(def.callback, def.errback);
+    return this;
+  };
+
+  /** 
+   * Used internally to exhaust the callback sequence when a result is
+   * available.
+   * @private
+   */
+  Deferred.prototype._fire = function() {
+    var chain = this.chain;
+    var fired = this.fired;
+    var res = this.results[fired];
+    var cb = null;
+    while ((chain.length > 0) && (this.paused == 0)) {
+      var f = chain.shift()[fired];
+      if (!f) {
+        continue;
+      }
+      var func = hitch(this, function() {
+        var ret = f(res);
+        //If no response, then use previous response.
+        if (typeof ret != undefStr) {
+          res = ret;
+        }
+        fired = res instanceof Error ? 1 : 0;
+        if (res instanceof Deferred) {
+          cb = function(res) {
+            this._resback(res);
+            // inlined from _pause()
+            this.paused--;
+            if ( (this.paused == 0) && (this.fired >= 0)) {
+              this._fire();
+            }
+          }
+          // inlined from _unpause
+          this.paused++;
+        }
+      });
+
+      try {
+        func.call(this);
+      } catch(err) {
+        fired = 1;
+        res = err;
+      }
+    }
+
+    this.fired = fired;
+    this.results[fired] = res;
+    if (cb && this.paused ) {
+      // this is for "tail recursion" in case the dependent
+      // deferred is already fired
+      res.addBoth(cb);
+    }
+  };
+
+  /////////////////////////////////////////////////////////////////////////////
+  // Plugin Initialization Class and Helper Functions
+  /////////////////////////////////////////////////////////////////////////////
+  
+  var returnFalse = function() {
+    return false;
+  };
+
+  var cachedHasVideo;
+  var cachedHasAudio;
+
+  var contentTests = {
+    canvas: function() {
+      return !!(
+        ua.isChrome || ua.isSafari >= 3 || ua.isFF >= 3 || ua.isOpera >= 9.2
+      );
+    },
+
+    svg: function() {
+      return !!(ua.isChrome || ua.isSafari || ua.isFF || ua.isOpera);
+    },
+
+    postMessage: function() {
+      return (
+        !!window['postMessage'] ||
+        ua.isChrome ||
+        ua.isIE >= 8 || 
+        ua.isSafari >= 3 || 
+        ua.isFF >= 3 || 
+        ua.isOpera >= 9.2
+      );
+    },
+
+    // the spec isn't settled and nothing currently supports it
+    websocket: returnFalse,
+
+    'css-anim': function() {
+      // pretty much limited to WebKit's special transition and animation
+      // properties. Need to figure out a better way to triangulate this as
+      // FF3.x adds more of these properties in parallel.
+      return ua.isWebKit > 500;
+    },
+
+    // "working" video/audio tag? 
+    video: function() {
+      if (typeof cachedHasVideo != undefStr) {
+        return cachedHasVideo;
+      }
+
+      // We haven't figured it out yet, so probe the <video> tag and cache the
+      // result.
+      var video = document.createElement('video');
+      return cachedHasVideo = (typeof video['play'] != undefStr);
+    },
+
+    audio: function() {
+      if (typeof cachedHasAudio != undefStr) {
+        return cachedHasAudio;
+      }
+
+      var audio = document.createElement('audio');
+      return cachedHasAudio = (typeof audio['play'] != undefStr);
+    },
+
+    'video-theora': function() {
+      return contentTests.video() && (ua.isChrome || ua.isFF > 3); 
+    },
+
+    'video-h264': function() {
+      return contentTests.video() && (ua.isChrome || ua.isSafari >= 4); 
+    },
+
+    'audio-vorbis': function() {
+      return contentTests.audio() && (ua.isChrome || ua.isFF > 3); 
+    },
+
+    'audio-mp3': function() {
+      return contentTests.audio() && (ua.isChrome || ua.isSafari >= 4); 
+    },
+
+    // can we implement RPC over available primitives?
+    rpc: function() {
+      // on IE we need the src to be on the same domain or we need postMessage
+      // to work. Since we can't count on the src being same-domain, we look
+      // for things that have postMessage. We may re-visit this later and add
+      // same-domain checking and cross-window-call-as-postMessage-replacement
+      // code.
+
+      // use "!!" to avoid null-is-an-object weirdness
+      return !!window['postMessage'];
+    },
+
+    sql: function() {
+      // HTML 5 databases
+      return !!window['openDatabase'];
+    },
+
+    storage: function(){
+      // DOM storage
+
+      // IE8, Safari, etc. support "localStorage", FF supported "globalStorage"
+      return !!window['globalStorage'] || !!window['localStorage'];
+    }
+  };
+
+  // isIE, isFF, isWebKit, etc.
+  forEach([
+      'isOpera', 'isWebKit', 'isChrome', 'isKhtml', 'isSafari', 
+      'isGecko', 'isFF', 'isIE'
+    ], 
+    function(name) {
+      contentTests[name] = function() {
+        return !!ua[name];
+      };
+    }
+  );
+
+  /** 
+   * Checks the list of requirements to determine if the current host browser
+   * meets them natively. Primarialy relies on the contentTests array.
+   * @param {Array} reqs A list of tests, either names of test functions in
+   *     contentTests or functions to execute.
+   * @return {boolean} 
+   * @private
+   */
+  var testRequirements = function(reqs) {
+    // never use CF on Chrome or Safari
+    if (ua.isChrome || ua.isSafari) {
+      return true;
+    }
+
+    var allMatch = true;
+    if (!reqs) {
+      return false;
+    }
+    forEach(reqs, function(i) {
+      var matches = false;
+      if (isFunction(i)) {
+        // support custom test functions
+        matches = i();
+      } else {
+        // else it's a lookup by name
+        matches = (!!contentTests[i] && contentTests[i]());
+      }
+      allMatch = allMatch && matches;
+    });
+    return allMatch;
+  };
+
+  var cachedAvailable;
+
+  /** 
+   * Checks to find out if ChromeFrame is available as a plugin
+   * @return {Boolean} 
+   * @private
+   */
+  var isCfAvailable = function() {
+    if (typeof cachedAvailable != undefStr) {
+      return cachedAvailable;
+    }
+
+    cachedAvailable = false;
+    var p = n.plugins;
+    if (typeof window['ActiveXObject'] != undefStr) {
+      try {
+        var i = new ActiveXObject('ChromeTab.ChromeFrame');
+        if (i) {
+          cachedAvailable = true;
+        }
+      } catch(e) {
+        log('ChromeFrame not available, error:', e.message);
+        // squelch
+      }
+    } else {
+      for (var x = 0; x < p.length; x++) {
+        if (p[x].name.indexOf('Google Chrome Frame') == 0) {
+          cachedAvailable = true;
+          break;
+        }
+      }
+    }
+    return cachedAvailable;
+  };
+
+  /** 
+   * Creates a <param> element with the specified name and value. If a parent
+   * is provided, the <param> element is appended to it.
+   * @param {string} name The name of the param
+   * @param {string} value The value
+   * @param {Node} parent Optional parent element
+   * @return {Boolean} 
+   * @private
+   */
+  var param = function(name, value, parent) {
+    var p = document.createElement('param');
+    p.setAttribute('name', name);
+    p.setAttribute('value', value);
+    if (parent) {
+      parent.appendChild(p);
+    }
+    return p;
+  };
+
+  /** @type {boolean} */
+  var cfStyleTagInjected = false;
+
+  /** 
+   * Creates a style sheet in the document which provides default styling for
+   * ChromeFrame instances. Successive calls should have no additive effect.
+   * @private
+   */
+  var injectCFStyleTag = function() {
+    if (cfStyleTagInjected) {
+      // once and only once
+      return;
+    }
+    try {
+      var rule = ['.chromeFrameDefaultStyle {',
+                    'width: 400px;',
+                    'height: 300px;',
+                    'padding: 0;',
+                    'margin: 0;',
+                  '}'].join('');
+      var ss = document.createElement('style');
+      ss.setAttribute('type', 'text/css');
+      if (ss.styleSheet) {
+        ss.styleSheet.cssText = rule;
+      } else {
+        ss.appendChild(document.createTextNode(rule));
+      }
+      var h = document.getElementsByTagName('head')[0];
+      if (h.firstChild) {
+        h.insertBefore(ss, h.firstChild);
+      } else {
+        h.appendChild(ss);
+      }
+      cfStyleTagInjected = true;
+    } catch (e) {
+      // squelch
+
+      // FIXME(slightlyoff): log? retry?
+    }
+  };
+
+  /** 
+   * Plucks properties from the passed arguments and sets them on the passed
+   * DOM node
+   * @param {Node} node The node to set properties on
+   * @param {Object} args A map of user-specified properties to set
+   * @private
+   */
+  var setProperties = function(node, args) {
+    injectCFStyleTag();
+
+    var srcNode = byId(args['node']);
+
+    node.id = args['id'] || (srcNode ? srcNode['id'] || getUid(srcNode) : '');
+
+    // TODO(slightlyoff): Opera compat? need to test there
+    var cssText = args['cssText'] || '';
+    node.style.cssText = ' ' + cssText;
+
+    var classText = args['className'] || '';
+    node.className = 'chromeFrameDefaultStyle ' + classText;
+
+    // default if the browser doesn't so we don't show sad-tab
+    var src = args['src'] || 'about:blank';
+
+    if (ua.isIE || ua.isOpera) {
+      node.src = src;
+    } else {
+      // crazyness regarding when things are set in NPAPI
+      node.setAttribute('src', src);
+    }
+
+    if (srcNode) {
+      srcNode.parentNode.replaceChild(node, srcNode);
+    }
+  };
+
+  /** 
+   * Creates a plugin instance, taking named parameters from the passed args.
+   * @param {Object} args A bag of configuration properties, including values
+   *    like 'node', 'cssText', 'className', 'id', 'src', etc.
+   * @return {Node} 
+   * @private
+   */
+  var makeCFPlugin = function(args) {
+    var el; // the element
+    if (!ua.isIE) {
+      el = document.createElement('object');
+      el.setAttribute("type", "application/chromeframe");
+    } else {
+      var dummy = document.createElement('span');
+      dummy.innerHTML = [
+        '<object codeBase="//www.google.com"',
+          "type='application/chromeframe'",
+          'classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"></object>'
+      ].join(' ');
+      el = dummy.firstChild;
+    }
+    setProperties(el, args);
+    return el;
+  };
+
+  /** 
+   * Creates an iframe in lieu of a ChromeFrame plugin, taking named parameters
+   * from the passed args.
+   * @param {Object} args A bag of configuration properties, including values
+   *    like 'node', 'cssText', 'className', 'id', 'src', etc.
+   * @return {Node} 
+   * @private
+   */
+  var makeCFIframe = function(args) {
+    var el = document.createElement('iframe');
+    setProperties(el, args);
+    // FIXME(slightlyoff):
+    //    This is where we'll need to slot in "don't fire load events for
+    //    fallback URL" logic.
+    listen(el, 'load', hitch(el, '_dispatch', 'load'));
+    return el;
+  };
+
+
+  var msgPrefix = 'CFInstance.rpc:';
+
+  /** 
+   * A class that provides the ability for widget-mode hosted content to more
+   * easily call hosting-page exposed APIs (and vice versa). It builds upon the
+   * message-passing nature of ChromeFrame to route messages to the other
+   * side's RPC host and coordinate events such as 'RPC readyness', buffering
+   * calls until both sides indicate they are ready to participate.
+   * @constructor
+   * @public
+   */
+  var RPC = function(instance) {
+    this.initDeferred = new Deferred();
+
+    this.instance = instance;
+
+    instance.listen('message', hitch(this, '_handleMessage'));
+
+    this._localExposed = {};
+    this._doWithAckCallbacks = {};
+
+    this._open = false;
+    this._msgBacklog = [];
+
+    this._initialized = false;
+    this._exposeMsgBacklog = [];
+
+    this._exposed = false;
+    this._callRemoteMsgBacklog = [];
+
+    this._inFlight = {};
+
+    var sendLoadMsg = hitch(this, function(evt) {
+      this.doWithAck('load').addCallback(this, function() {
+        this._open = true;
+        this._postMessageBacklog();
+      });
+    });
+
+    if (instance['tagName']) {
+      instance.listen('load', sendLoadMsg);
+    } else {
+      sendLoadMsg();
+    }
+  };
+
+  RPC.prototype._postMessageBacklog = function() {
+    if (this._open) {
+      forEach(this._msgBacklog, this._postMessage, this);
+      this._msgBacklog = [];
+    }
+  };
+
+  RPC.prototype._postMessage = function(msg, force) {
+    if (!force && !this._open) {
+      this._msgBacklog.push(msg);
+    } else {
+      // FIXME(slightlyoff): need to check domains list here!
+      this.instance.postMessage(msgPrefix + msg, '*');
+    }
+  };
+
+  // currently no-ops. We may need them in the future
+  // RPC.prototype._doWithAck_load = function() { };
+  // RPC.prototype._doWithAck_init = function() { };
+
+  RPC.prototype._doWithAck = function(what) {
+    var f = this['_doWithAck_' + what];
+    if (f) {
+      f.call(this);
+    }
+
+    this._postMessage('doWithAckCallback:' + what, what == 'load');
+  };
+
+  RPC.prototype.doWithAck = function(what) {
+    var d = new Deferred();
+    this._doWithAckCallbacks[what] = d;
+    this._postMessage('doWithAck:' + what, what == 'load');
+    return d;
+  };
+
+  RPC.prototype._handleMessage = function(evt, stopper) {
+    var d = String(evt.data);
+
+    if (d.indexOf(msgPrefix) != 0) {
+      // not for us, allow the event dispatch to continue...
+      return;
+    }
+
+    // ...else we're the end of the line for this event
+    stopper();
+
+    // see if we know what type of message it is
+    d = d.substr(msgPrefix.length);
+
+    var cIndex = d.indexOf(':');
+
+    var type = d.substr(0, cIndex);
+
+    if (type == 'doWithAck') {
+      this._doWithAck(d.substr(cIndex + 1));
+      return;
+    }
+
+    var msgBody = d.substr(cIndex + 1);
+
+    if (type == 'doWithAckCallback') {
+      this._doWithAckCallbacks[msgBody].callback(1);
+      return;
+    }
+
+    if (type == 'init') {
+      return;
+    }
+
+    // All the other stuff we can do uses a JSON payload.
+    var obj = fromJson(msgBody);
+
+    if (type == 'callRemote') {
+
+      if (obj.method && obj.params && obj.id) {
+
+        var ret = {
+          success: 0,
+          returnId: obj.id
+        };
+
+        try {
+          // Undefined isn't valid JSON, so use null as default value.
+          ret.value = this._localExposed[ obj.method ](evt, obj) || null;
+          ret.success = 1;
+        } catch(e) {
+          ret.error = e.message;
+        }
+
+        this._postMessage('callReturn:' + toJson(ret));
+      }
+    }
+
+    if (type == 'callReturn') {
+      // see if we're waiting on an outstanding RPC call, which
+      // would be identified by returnId.
+      var rid = obj['returnId'];
+      if (!rid) {
+        // throw an error?
+        return;
+      }
+      var callWrap = this._inFlight[rid];
+      if (!callWrap) {
+        return;
+      }
+
+      if (obj.success) {
+        callWrap.d.callback(obj['value'] || 1);
+      } else {
+        callWrap.d.errback(new Error(obj['error'] || 'unspecified RPC error'));
+      }
+      delete this._inFlight[rid];
+    }
+
+  };
+  
+  /** 
+   * Makes a method visible to be called 
+   * @param {string} name The name to expose the method at. 
+   * @param {Function|string} method The function (or name of the function) to
+   *     expose. If a name is provided, it's looked up from the passed scope.
+   *     If no scope is provided, the global scope is queried for a function
+   *     with that name.
+   * @param {Object} scope Optional A scope to bind the passed method to. If
+   *     the method parameter is specified by a string, the method is both
+   *     located on the passed scope and bound to it.
+   * @param {Array} domains Optional A list of domains in
+   *     'http://example.com:8080' format which may call the given method.
+   *     Currently un-implemented.
+   * @public
+   */
+  RPC.prototype.expose = function(name, method, scope, domains) {
+    scope = scope || window;
+    method = isString(method) ? scope[method] : method;
+
+    // local call proxy that the other side will hit when calling our method
+    this._localExposed[name] = function(evt, obj) {
+      return method.apply(scope, obj.params);
+    };
+
+    if (!this._initialized) {
+      this._exposeMsgBacklog.push(arguments);
+      return;
+    }
+
+    var a = [name, method, scope, domains];
+    this._sendExpose.apply(this, a);
+  };
+
+  RPC.prototype._sendExpose = function(name) {
+    // now tell the other side that we're advertising this method
+    this._postMessage('expose:' + toJson({ name: name }));
+  };
+
+
+  /** 
+   * Calls a remote method asynchronously and returns a Deferred object
+   * representing the response.
+   * @param {string} method Name of the method to call. Should be the same name
+   *     which the other side has expose()'d.
+   * @param {Array} params Optional A list of arguments to pass to the called
+   *     method.  All elements in the list must be cleanly serializable to
+   *     JSON.
+   * @param {CFInstance.Deferred} deferred Optional A Deferred to use for
+   *     reporting the response of the call. If no deferred is passed, a new
+   *     Deferred is created and returned.
+   * @return {CFInstance.Deferred} 
+   * @public
+   */
+  RPC.prototype.callRemote = function(method, params, timeout, deferred) {
+    var d = deferred || new Deferred(null, timeout || -1);
+
+    if (!this._exposed) {
+      var args = toArray(arguments);
+      args.length = 3;
+      args.push(d);
+      this._callRemoteMsgBacklog.push(args);
+      return d;
+    }
+
+
+    if (!method) {
+      d.errback('no method provided!');
+      return d;
+    }
+
+    var id = msgPrefix + (_counter++);
+
+    // JSON-ify the whole bundle
+    var callWrapper = {
+      method: String(method), 
+      params: params || [],
+      id: id
+    };
+    var callJson = toJson(callWrapper);
+    callWrapper.d = d;
+    this._inFlight[id] = callWrapper;
+    this._postMessage('callRemote:' + callJson);
+    return d;
+  };
+
+
+  /** 
+   * Tells the other side of the connection that we're ready to start receiving
+   * calls. Returns a Deferred that is called back when both sides have
+   * initialized and any backlogged messages have been sent. RPC users should
+   * generally work to make sure that they call expose() on all of the methods
+   * they'd like to make available to the other side *before* calling init()
+   * @return {CFInstance.Deferred} 
+   * @public
+   */
+  RPC.prototype.init = function() {
+    var d = this.initDeferred;
+    this.doWithAck('init').addCallback(this, function() {
+      // once the init floodgates are open, send our backlogs one at a time,
+      // with a little lag in the middle to prevent ordering problems
+    
+      this._initialized = true;
+      while (this._exposeMsgBacklog.length) {
+        this.expose.apply(this, this._exposeMsgBacklog.shift());
+      }
+
+      setTimeout(hitch(this, function(){
+
+        this._exposed = true;
+        while (this._callRemoteMsgBacklog.length) {
+          this.callRemote.apply(this, this._callRemoteMsgBacklog.shift());
+        }
+
+        d.callback(1);
+
+      }), 30);
+      
+    });
+    return d;
+  };
+
+  // CFInstance design notes:
+  //  
+  //    The CFInstance constructor is only ever used in host environments. In
+  //    content pages (things hosted by a ChromeFrame instance), CFInstance
+  //    acts as a singleton which provides services like RPC for communicating
+  //    with it's mirror-image object in the hosting environment.  We want the
+  //    same methods and properties to be available on *instances* of
+  //    CFInstance objects in the host env as on the singleton in the hosted
+  //    content, despite divergent implementation. 
+  //
+  //    Further complicating things, CFInstance may specialize behavior
+  //    internally based on whether or not it is communicationg with a fallback
+  //    iframe or a 'real' ChromeFrame instance.
+
+  var CFInstance; // forward declaration
+  var h = window['externalHost'];
+  var inIframe = (window.parent != window);
+
+  if (inIframe) {
+    h = window.parent;
+  }
+
+  var normalizeTarget = function(targetOrigin) {
+    var l = window.location;
+    if (!targetOrigin) {
+      if (l.protocol != 'file:') {
+        targetOrigin = l.protocol + '//' + l.host + "/";
+      } else {
+        // TODO(slightlyoff):
+        //    is this secure enough? Is there another way to get messages
+        //    flowing reliably across file-hosted documents?
+        targetOrigin = '*';
+      }
+    }
+    return targetOrigin;
+  };
+
+  var postMessageToDest = function(dest, msg, targetOrigin) {
+    return dest.postMessage(msg, normalizeTarget(targetOrigin));
+  };
+
+  if (h) {
+    //
+    // We're loaded inside a ChromeFrame widget (or something that should look
+    // like we were).
+    //
+
+    CFInstance = {};
+
+    installEvtSys(CFInstance);
+
+    // FIXME(slightlyoff):
+    //    passing a target origin to externalHost's postMessage seems b0rked
+    //    right now, so pass null instead. Will re-enable hitch()'d variant
+    //    once that's fixed.
+
+    // CFInstance.postMessage = hitch(null, postMessageToDest, h);
+
+    CFInstance.postMessage = function(msg, targetOrigin) {
+      return h.postMessage(msg, 
+                           (inIframe ? normalizeTarget(targetOrigin) : null) );
+    };
+
+    // Attach to the externalHost's onmessage to proxy it in to CFInstance's
+    // onmessage.
+    var dispatchMsg = function(evt) {
+      try {
+        CFInstance._dispatch('message', evt);
+      } catch(e) {
+        log(e);
+        // squelch
+      }
+    };
+    if (inIframe) {
+      listen(window, 'message', dispatchMsg);
+    } else {
+      h.onmessage = dispatchMsg;
+    }
+
+    CFInstance.rpc = new RPC(CFInstance);
+
+    _loadedListenerList.push(function(evt) {
+      CFInstance._dispatch('load', evt);
+    });
+
+  } else {
+    //
+    // We're the host document.
+    //
+
+    var installProperties = function(instance, args) {
+      var s = instance.supportedEvents = ['load', 'message'];
+      instance._msgPrefix = 'CFMessage:';
+
+      installEvtSys(instance);
+
+      instance.log = log;
+
+      // set up an RPC instance
+      instance.rpc = new RPC(instance);
+
+      forEach(s, function(evt) {
+        var l = args['on' + evt];
+        if (l) {
+          instance.listen(evt, l);
+        }
+      });
+
+      var contentWindow = instance.contentWindow;
+
+      // if it doesn't have a postMessage, route to/from the iframe's built-in
+      if (typeof instance['postMessage'] == undefStr && !!contentWindow) {
+
+        instance.postMessage = hitch(null, postMessageToDest, contentWindow);
+
+        listen(window, 'message', function(evt) {
+          if (evt.source == contentWindow) {
+            instance._dispatch('message', evt);
+          }
+        });
+      }
+
+      return instance;
+    };
+
+    /** 
+     * A class whose instances correspond to ChromeFrame instances. Passing an
+     * arguments object to CFInstance helps parameterize the instance. 
+     * @constructor
+     * @public
+     */
+    CFInstance = function(args) {
+      args = args || {};
+      var instance;
+      var success = false;
+
+      // If we've been passed a CFInstance object as our source node, just
+      // re-use it.
+      if (args['node']) {
+        var n = byId(args['node']);
+        // Look for CF-specific properties.
+        if (n && n.tagName == 'OBJECT' && n.success && n.rpc) {
+          // Navigate, set styles, etc.
+          setProperties(n, args);
+          return n;
+        }
+      }
+
+      var force = !!args['forcePlugin'];
+
+      if (!force && testRequirements(args['requirements'])) {
+        instance = makeCFIframe(args);
+        success = true;
+      } else if (isCfAvailable()) {
+        instance = makeCFPlugin(args);
+        success = true;
+      } else {
+        // else create an iframe but load the failure content and
+        // not the 'normal' content
+
+        // grab the fallback URL, and if none, use the 'click here
+        // to install ChromeFrame' URL. Note that we only support
+        // polling for install success if we're using the default
+        // URL
+
+        var fallback = '//www.google.com/chromeframe';
+
+        args.src = args['fallback'] || fallback;
+        instance = makeCFIframe(args);
+
+        if (args.src == fallback) {
+          // begin polling for install success.
+
+          // TODO(slightlyoff): need to prevent firing of onload hooks!
+          // TODO(slightlyoff): implement polling
+          // TODO(slightlyoff): replacement callback?
+          // TODO(slightlyoff): add flag to disable this behavior
+        }
+      }
+      instance.success = success;
+
+      installProperties(instance, args);
+
+      return instance;
+    };
+
+    // compatibility shims for development-time. These mirror the methods that
+    // are created on the CFInstance singleton if we detect that we're running
+    // inside of CF.
+    if (!CFInstance['postMessage']) {
+      CFInstance.postMessage = function() {
+        var args = toArray(arguments);
+        args.unshift('CFInstance.postMessage:');
+        log.apply(null, args);
+      };
+      CFInstance.listen = function() {
+        // this space intentionally left blank
+      };
+    }
+  }
+
+  // expose some properties
+  CFInstance.ua = ua;
+  CFInstance._documentLoaded = documentLoaded;
+  CFInstance.contentTests = contentTests;
+  CFInstance.isAvailable = function(requirements) {
+    var hasCf = isCfAvailable();
+    return requirements ? (hasCf || testRequirements(requirements)) : hasCf;
+
+  };
+  CFInstance.Deferred = Deferred;
+  CFInstance.toJson = toJson;
+  CFInstance.fromJson = fromJson;
+  CFInstance.log = log;
+
+  // expose CFInstance to the external scope. We've already checked to make
+  // sure we're not going to blow existing objects away.
+  scope.CFInstance = CFInstance;
+
+})( this['ChromeFrameScope'] || this );
+
+// vim: shiftwidth=2:et:ai:tabstop=2
diff --git a/chrome_frame/DEPS b/chrome_frame/DEPS
new file mode 100644
index 0000000..545ddc6
--- /dev/null
+++ b/chrome_frame/DEPS
@@ -0,0 +1,6 @@
+deps = {

+  # TODO(slightlyoff): need to add to Chromium third_party/ !!

+  # Chrome Frame needs these gecko SDKs and internals.

+  "src/third_party/xulrunner-sdk":

+      "svn://chrome-svn/chrome/trunk/deps/third_party/xulrunner-sdk",

+}

diff --git a/chrome_frame/bho.cc b/chrome_frame/bho.cc
new file mode 100644
index 0000000..e8c0374
--- /dev/null
+++ b/chrome_frame/bho.cc
@@ -0,0 +1,247 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/bho.h"
+
+#include <shlguid.h>
+#include <shobjidl.h>
+
+#include "base/logging.h"
+#include "base/registry.h"
+#include "base/scoped_bstr_win.h"
+#include "base/scoped_comptr_win.h"
+#include "base/scoped_variant_win.h"
+#include "base/string_util.h"
+#include "chrome_tab.h" // NOLINT
+#include "chrome_frame/protocol_sink_wrap.h"
+#include "chrome_frame/utils.h"
+#include "chrome_frame/vtable_patch_manager.h"
+
+const wchar_t kUrlMonDllName[] = L"urlmon.dll";
+const wchar_t kPatchProtocols[] = L"PatchProtocols";
+static const int kIBrowserServiceOnHttpEquivIndex = 30;
+
+PatchHelper g_patch_helper;
+
+BEGIN_VTABLE_PATCHES(IBrowserService)
+  VTABLE_PATCH_ENTRY(kIBrowserServiceOnHttpEquivIndex, Bho::OnHttpEquiv)
+END_VTABLE_PATCHES()
+
+_ATL_FUNC_INFO Bho::kBeforeNavigate2Info = {
+  CC_STDCALL, VT_EMPTY, 7, {
+    VT_DISPATCH,
+    VT_VARIANT | VT_BYREF,
+    VT_VARIANT | VT_BYREF,
+    VT_VARIANT | VT_BYREF,
+    VT_VARIANT | VT_BYREF,
+    VT_VARIANT | VT_BYREF,
+    VT_BOOL | VT_BYREF
+  }
+};
+
+Bho::Bho() {
+}
+
+STDMETHODIMP Bho::SetSite(IUnknown* site) {
+  HRESULT hr = S_OK;
+  if (site) {
+    ScopedComPtr<IWebBrowser2> web_browser2;
+    web_browser2.QueryFrom(site);
+    if (web_browser2) {
+      hr = DispEventAdvise(web_browser2, &DIID_DWebBrowserEvents2);
+      DCHECK(SUCCEEDED(hr)) << "DispEventAdvise failed. Error: " << hr;
+    }
+
+    if (g_patch_helper.state() == PatchHelper::PATCH_IBROWSER) {
+      ScopedComPtr<IBrowserService> browser_service;
+      hr = DoQueryService(SID_SShellBrowser, site, browser_service.Receive());
+      DCHECK(browser_service) << "DoQueryService - SID_SShellBrowser failed."
+        << " Site: " << site << " Error: " << hr;
+      if (browser_service) {
+        g_patch_helper.PatchBrowserService(browser_service);
+        DCHECK(SUCCEEDED(hr)) << "vtable_patch::PatchInterfaceMethods failed."
+            << " Site: " << site << " Error: " << hr;
+      }
+    }
+  }
+
+  return IObjectWithSiteImpl<Bho>::SetSite(site);
+}
+
+STDMETHODIMP Bho::BeforeNavigate2(IDispatch* dispatch, VARIANT* url,
+    VARIANT* flags, VARIANT* target_frame_name, VARIANT* post_data,
+    VARIANT* headers, VARIANT_BOOL* cancel) {
+  ScopedComPtr<IWebBrowser2> web_browser2;
+  if (dispatch)
+    web_browser2.QueryFrom(dispatch);
+
+  if (!web_browser2) {
+    NOTREACHED() << "Can't find WebBrowser2 with given dispatch";
+    return S_OK;  // Return success, we operate on best effort basis.
+  }
+
+  DLOG(INFO) << "BeforeNavigate2: " << url->bstrVal;
+
+  if (g_patch_helper.state() == PatchHelper::PATCH_IBROWSER) {
+    VARIANT_BOOL is_top_level = VARIANT_FALSE;
+    web_browser2->get_TopLevelContainer(&is_top_level);
+
+    std::wstring current_url;
+    bool is_chrome_protocol = false;
+    if (is_top_level && IsValidUrlScheme(url->bstrVal)) {
+      current_url.assign(url->bstrVal, SysStringLen(url->bstrVal));
+      is_chrome_protocol = StartsWith(current_url, kChromeProtocolPrefix,
+                                      false);
+
+      if (!is_chrome_protocol && IsOptInUrl(current_url.c_str())) {
+        DLOG(INFO) << "Canceling navigation and switching to cf";
+        // Cancel original navigation
+        *cancel = VARIANT_TRUE;
+
+        // Issue new request with 'cf:'
+        current_url.insert(0, kChromeProtocolPrefix);
+        ScopedVariant new_url(current_url.c_str());
+        HRESULT hr = web_browser2->Navigate2(new_url.AsInput(), flags,
+                                             target_frame_name, post_data,
+                                             headers);
+        DCHECK(SUCCEEDED(hr)) << "web_browser2->Navigate2 failed. Error: " << hr
+            << std::endl << "Url: " << current_url
+            << std::endl << "flags: " << flags
+            << std::endl << "post data: " << post_data
+            << std::endl << "headers: " << headers;
+      }
+    }
+  }
+  return S_OK;
+}
+
+HRESULT Bho::FinalConstruct() {
+  return S_OK;
+}
+
+void Bho::FinalRelease() {
+}
+
+HRESULT STDMETHODCALLTYPE Bho::OnHttpEquiv(
+    IBrowserService_OnHttpEquiv_Fn original_httpequiv,
+    IBrowserService* browser, IShellView* shell_view, BOOL done,
+    VARIANT* in_arg, VARIANT* out_arg) {
+  if (!done && in_arg && (VT_BSTR == V_VT(in_arg))) {
+    if (StrStrI(V_BSTR(in_arg), kChromeContentPrefix)) {
+      // OnHttpEquiv is invoked for meta tags within sub frames as well.
+      // We want to switch renderers only for the top level frame. Since
+      // the same |browser| and |shell_view| are passed in to OnHttpEquiv
+      // even for sub iframes, we determine if this is the top one by
+      // checking if there are any sub frames created or not.
+      ScopedComPtr<IWebBrowser2> web_browser2;
+      DoQueryService(SID_SWebBrowserApp, browser, web_browser2.Receive());
+      if (web_browser2 && !HasSubFrames(web_browser2))
+        SwitchRenderer(web_browser2, browser, shell_view, V_BSTR(in_arg));
+    }
+  }
+
+  return original_httpequiv(browser, shell_view, done, in_arg, out_arg);
+}
+
+bool Bho::HasSubFrames(IWebBrowser2* web_browser2) {
+  bool has_sub_frames = false;
+  ScopedComPtr<IDispatch> doc_dispatch;
+  if (web_browser2) {
+    HRESULT hr = web_browser2->get_Document(doc_dispatch.Receive());
+    DCHECK(SUCCEEDED(hr) && doc_dispatch) <<
+        "web_browser2->get_Document failed. Error: " << hr;
+    ScopedComPtr<IOleContainer> container;
+    if (SUCCEEDED(hr) && doc_dispatch) {
+      container.QueryFrom(doc_dispatch);
+      ScopedComPtr<IEnumUnknown> enumerator;
+      if (container) {
+        container->EnumObjects(OLECONTF_EMBEDDINGS, enumerator.Receive());
+        ScopedComPtr<IUnknown> unk;
+        ULONG items_retrieved = 0;
+        if (enumerator)
+          enumerator->Next(1, unk.Receive(), &items_retrieved);
+        has_sub_frames = (items_retrieved != 0);
+      }
+    }
+  }
+
+  return has_sub_frames;
+}
+
+HRESULT Bho::SwitchRenderer(IWebBrowser2* web_browser2,
+    IBrowserService* browser, IShellView* shell_view,
+    const wchar_t* meta_tag) {
+  DCHECK(web_browser2 && browser && shell_view && meta_tag);
+
+  // Get access to the mshtml instance and the moniker
+  ScopedComPtr<IOleObject> mshtml_ole_object;
+  HRESULT hr = shell_view->GetItemObject(SVGIO_BACKGROUND, IID_IOleObject,
+      reinterpret_cast<void**>(mshtml_ole_object.Receive()));
+  if (!mshtml_ole_object) {
+    NOTREACHED() << "shell_view->GetItemObject failed. Error: " << hr;
+    return hr;
+  }
+
+  std::wstring url;
+  ScopedComPtr<IMoniker> moniker;
+  hr = mshtml_ole_object->GetMoniker(OLEGETMONIKER_ONLYIFTHERE,
+                                     OLEWHICHMK_OBJFULL, moniker.Receive());
+  DCHECK(moniker) << "mshtml_ole_object->GetMoniker failed. Error: " << hr;
+
+  if (moniker)
+    hr = GetUrlFromMoniker(moniker, NULL, &url);
+
+  DCHECK(!url.empty()) << "GetUrlFromMoniker failed. Error: " << hr;
+  DCHECK(!StartsWith(url, kChromeProtocolPrefix, false));
+
+  if (!url.empty()) {
+    url.insert(0, kChromeProtocolPrefix);
+    // Navigate to new url
+    VARIANT empty = ScopedVariant::kEmptyVariant;
+    VARIANT flags = { VT_I4 };
+    V_I4(&flags) = 0;
+    ScopedVariant url_var(url.c_str());
+    hr = web_browser2->Navigate2(url_var.AsInput(), &flags, &empty, &empty,
+                                 &empty);
+    DCHECK(SUCCEEDED(hr)) << "web_browser2->Navigate2 failed. Error: " << hr
+        << std::endl << "Url: " << url;
+  }
+
+  return S_OK;
+}
+
+void PatchHelper::InitializeAndPatchProtocolsIfNeeded() {
+  if (state_ != UNKNOWN)
+    return;
+
+  bool patch_protocol = GetConfigBool(true, kPatchProtocols);
+  if (patch_protocol) {
+    ProtocolSinkWrap::PatchProtocolHandler(kUrlMonDllName, CLSID_HttpProtocol);
+    ProtocolSinkWrap::PatchProtocolHandler(kUrlMonDllName, CLSID_HttpSProtocol);
+    state_ = PATCH_PROTOCOL;
+  } else {
+    state_ = PATCH_IBROWSER;
+  }
+}
+
+void PatchHelper::PatchBrowserService(IBrowserService* browser_service) {
+  DCHECK(state_ == PATCH_IBROWSER);
+  state_ = PATCH_IBROWSER_OK;
+  vtable_patch::PatchInterfaceMethods(browser_service,
+                                      IBrowserService_PatchInfo);
+}
+
+extern vtable_patch::MethodPatchInfo IInternetProtocol_PatchInfo[];
+extern vtable_patch::MethodPatchInfo IInternetProtocolEx_PatchInfo[];
+void PatchHelper::UnpatchIfNeeded() {
+  if (state_ == PATCH_PROTOCOL) {
+    vtable_patch::UnpatchInterfaceMethods(IInternetProtocol_PatchInfo);
+    vtable_patch::UnpatchInterfaceMethods(IInternetProtocolEx_PatchInfo);
+  } else if (state_ == PATCH_IBROWSER_OK) {
+    vtable_patch::UnpatchInterfaceMethods(IBrowserService_PatchInfo);
+  }
+
+  state_ = UNKNOWN;
+}
+
diff --git a/chrome_frame/bho.h b/chrome_frame/bho.h
new file mode 100644
index 0000000..ea9fe430
--- /dev/null
+++ b/chrome_frame/bho.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_BHO_H_
+#define CHROME_FRAME_BHO_H_
+
+#include <string>
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <exdisp.h>
+#include <exdispid.h>
+#include <mshtml.h>
+#include <shdeprecated.h>
+
+#include "chrome_tab.h"  // NOLINT
+#include "chrome_frame/resource.h"
+#include "grit/chrome_frame_resources.h"
+
+class PatchHelper {
+ public:
+  enum State { UNKNOWN, PATCH_IBROWSER, PATCH_IBROWSER_OK, PATCH_PROTOCOL };
+  PatchHelper() : state_(UNKNOWN) {
+  }
+
+  State state() const {
+    return state_;
+  }
+
+  void InitializeAndPatchProtocolsIfNeeded();
+  void PatchBrowserService(IBrowserService* p);
+  void UnpatchIfNeeded();
+ protected:
+  State state_;
+};
+
+// Single global variable
+extern PatchHelper g_patch_helper;
+
+class ATL_NO_VTABLE Bho
+  : public CComObjectRootEx<CComSingleThreadModel>,
+    public CComCoClass<Bho, &CLSID_ChromeFrameBHO>,
+    public IObjectWithSiteImpl<Bho>,
+    public IDispEventSimpleImpl<0, Bho, &DIID_DWebBrowserEvents2> {
+ public:
+  typedef HRESULT (STDMETHODCALLTYPE* IBrowserService_OnHttpEquiv_Fn)(
+      IBrowserService* browser, IShellView* shell_view, BOOL done,
+      VARIANT* in_arg, VARIANT* out_arg);
+
+DECLARE_REGISTRY_RESOURCEID(IDR_BHO)
+DECLARE_NOT_AGGREGATABLE(Bho)
+DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+BEGIN_COM_MAP(Bho)
+  COM_INTERFACE_ENTRY(IObjectWithSite)
+END_COM_MAP()
+
+BEGIN_SINK_MAP(Bho)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2,
+                 BeforeNavigate2, &kBeforeNavigate2Info)
+END_SINK_MAP()
+
+  // Lifetime management methods
+  Bho();
+
+  HRESULT FinalConstruct();
+  void FinalRelease();
+
+  // IObjectWithSite
+  STDMETHODIMP SetSite(IUnknown* site);
+  STDMETHOD(BeforeNavigate2)(IDispatch* dispatch, VARIANT* url, VARIANT* flags,
+      VARIANT* target_frame_name, VARIANT* post_data, VARIANT* headers,
+      VARIANT_BOOL* cancel);
+
+  // mshtml sends an IOleCommandTarget::Exec of OLECMDID_HTTPEQUIV
+  // (and OLECMDID_HTTPEQUIV_DONE) as soon as it parses a meta tag.
+  // It also sends contents of the meta tag as an argument. IEFrame
+  // handles this in IBrowserService::OnHttpEquiv.  So this allows
+  // us to sniff the META tag by simply patching it. The renderer
+  // switching can be achieved by cancelling original navigation
+  // and issuing a new one using IWebBrowser2->Navigate2.
+  static HRESULT STDMETHODCALLTYPE OnHttpEquiv(
+      IBrowserService_OnHttpEquiv_Fn original_httpequiv,
+      IBrowserService* browser, IShellView* shell_view, BOOL done,
+      VARIANT* in_arg, VARIANT* out_arg);
+
+ protected:
+  bool PatchProtocolHandler(const CLSID& handler_clsid);
+  static bool HasSubFrames(IWebBrowser2* web_browser2);
+  static HRESULT SwitchRenderer(IWebBrowser2* web_browser2,
+      IBrowserService* browser, IShellView* shell_view,
+      const wchar_t* meta_tag);
+
+  static _ATL_FUNC_INFO kBeforeNavigate2Info;
+};
+
+// Add Bho to class library table so IE can CoCreate it.
+OBJECT_ENTRY_AUTO(CLSID_ChromeFrameBHO, Bho);
+
+#endif  // CHROME_FRAME_BHO_H_
+
diff --git a/chrome_frame/bho.rgs b/chrome_frame/bho.rgs
new file mode 100644
index 0000000..824fd7d
--- /dev/null
+++ b/chrome_frame/bho.rgs
@@ -0,0 +1,23 @@
+HKLM {

+  NoRemove SOFTWARE {

+    NoRemove Classes {

+      ChromeFrame.Bho.1 = s 'Bho Class' {

+        CLSID = s '{ECB3C477-1A0A-44bd-BB57-78F9EFE34FA7}'

+      }

+      ChromeFrame.Bho = s 'ChromeFrame BHO' {

+        CLSID = s '{ECB3C477-1A0A-44bd-BB57-78F9EFE34FA7}'

+        CurVer = s 'ChromeFrame.Bho.1'

+      }

+      NoRemove CLSID {

+        ForceRemove {ECB3C477-1A0A-44bd-BB57-78F9EFE34FA7} = s 'ChromeFrame BHO' {

+          ProgID = s 'ChromeFrame.Bho.1'

+          VersionIndependentProgID = s 'ChromeFrame.Bho'

+          InprocServer32 = s '%MODULE%' {

+            val ThreadingModel = s 'Apartment'

+          }

+          'TypeLib' = s '{6F2664E1-FF6E-488A-BCD1-F4CA6001DFCC}'

+        }

+      }

+    }

+  }

+}

diff --git a/chrome_frame/chrome_active_document.bmp b/chrome_frame/chrome_active_document.bmp
new file mode 100644
index 0000000..1229764
--- /dev/null
+++ b/chrome_frame/chrome_active_document.bmp
Binary files differ
diff --git a/chrome_frame/chrome_active_document.cc b/chrome_frame/chrome_active_document.cc
new file mode 100644
index 0000000..bbe38b7
--- /dev/null
+++ b/chrome_frame/chrome_active_document.cc
@@ -0,0 +1,648 @@
+// Copyright (c) 2009 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.
+
+// Implementation of ChromeActiveDocument
+#include "chrome_frame/chrome_active_document.h"
+
+#include <hlink.h>
+#include <htiface.h>
+#include <mshtmcid.h>
+#include <shdeprecated.h>
+#include <shlguid.h>
+#include <shobjidl.h>
+#include <tlogstg.h>
+#include <urlmon.h>
+#include <wininet.h>
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/registry.h"
+#include "base/scoped_variant_win.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "base/thread.h"
+#include "base/thread_local.h"
+
+#include "grit/generated_resources.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/navigation_types.h"
+#include "chrome/test/automation/browser_proxy.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "chrome_frame/utils.h"
+
+const wchar_t kChromeAttachExternalTabPrefix[] = L"attach_external_tab";
+
+static const wchar_t kUseChromeNetworking[] = L"UseChromeNetworking";
+static const wchar_t kHandleTopLevelRequests[] =
+    L"HandleTopLevelRequests";
+
+base::ThreadLocalPointer<ChromeActiveDocument> g_active_doc_cache;
+
+bool g_first_launch_by_process_ = true;
+
+ChromeActiveDocument::ChromeActiveDocument()
+    : first_navigation_(true),
+      is_automation_client_reused_(false) {
+}
+
+HRESULT ChromeActiveDocument::FinalConstruct() {
+  // If we have a cached ChromeActiveDocument instance in TLS, then grab
+  // ownership of the cached document's automation client. This is an
+  // optimization to get Chrome active documents to load faster.
+  ChromeActiveDocument* cached_document = g_active_doc_cache.Get();
+  if (cached_document) {
+    DCHECK(automation_client_.get() == NULL);
+    automation_client_.reset(cached_document->automation_client_.release());
+    DLOG(INFO) << "Reusing automation client instance from "
+               << cached_document;
+    DCHECK(automation_client_.get() != NULL);
+    automation_client_->Reinitialize(this);
+    is_automation_client_reused_ = true;
+  } else {
+    // The FinalConstruct implementation in the ChromeFrameActivexBase class
+    // i.e. Base creates an instance of the ChromeFrameAutomationClient class
+    // and initializes it, which would spawn a new Chrome process, etc.
+    // We don't want to be doing this if we have a cached document, whose
+    // automation client instance can be reused.
+    HRESULT hr = Base::FinalConstruct();
+    if (FAILED(hr))
+      return hr;
+  }
+
+  bool chrome_network = GetConfigBool(false, kUseChromeNetworking);
+  bool top_level_requests = GetConfigBool(true, kHandleTopLevelRequests);
+  automation_client_->set_use_chrome_network(chrome_network);
+  automation_client_->set_handle_top_level_requests(top_level_requests);
+
+  find_dialog_.Init(automation_client_.get());
+
+  enabled_commands_map_[OLECMDID_PRINT] = true;
+  enabled_commands_map_[OLECMDID_FIND] = true;
+  enabled_commands_map_[OLECMDID_CUT] = true;
+  enabled_commands_map_[OLECMDID_COPY] = true;
+  enabled_commands_map_[OLECMDID_PASTE] = true;
+  enabled_commands_map_[OLECMDID_SELECTALL] = true;
+  return S_OK;
+}
+
+ChromeActiveDocument::~ChromeActiveDocument() {
+  DLOG(INFO) << __FUNCTION__;
+  if (find_dialog_.IsWindow()) {
+    find_dialog_.DestroyWindow();
+  }
+}
+
+// Override DoVerb
+STDMETHODIMP ChromeActiveDocument::DoVerb(LONG verb,
+                                          LPMSG msg,
+                                          IOleClientSite* active_site,
+                                          LONG index,
+                                          HWND parent_window,
+                                          LPCRECT pos) {
+  // IE will try and in-place activate us in some cases. This happens when
+  // the user opens a new IE window with a URL that has us as the DocObject.
+  // Here we refuse to be activated in-place and we will force IE to UIActivate
+  // us.
+  if (OLEIVERB_INPLACEACTIVATE == verb) {
+    return E_NOTIMPL;
+  }
+  // Check if we should activate as a docobject or not
+  // (client supports IOleDocumentSite)
+  if (doc_site_) {
+    switch (verb) {
+    case OLEIVERB_SHOW:
+    case OLEIVERB_OPEN:
+    case OLEIVERB_UIACTIVATE:
+      if (!m_bUIActive) {
+        return doc_site_->ActivateMe(NULL);
+      }
+      break;
+    }
+  }
+  return IOleObjectImpl<ChromeActiveDocument>::DoVerb(verb,
+                                                      msg,
+                                                      active_site,
+                                                      index,
+                                                      parent_window,
+                                                      pos);
+}
+
+STDMETHODIMP ChromeActiveDocument::InPlaceDeactivate(void) {
+  // Release the pointers we have no need for now.
+  doc_site_.Release();
+  in_place_frame_.Release();
+  return IOleInPlaceObjectWindowlessImpl<ChromeActiveDocument>::
+             InPlaceDeactivate();
+}
+
+// Override IOleInPlaceActiveObjectImpl::OnDocWindowActivate
+STDMETHODIMP ChromeActiveDocument::OnDocWindowActivate(BOOL activate) {
+  DLOG(INFO) << __FUNCTION__;
+  return S_OK;
+}
+
+STDMETHODIMP ChromeActiveDocument::TranslateAccelerator(MSG* msg) {
+  DLOG(INFO) << __FUNCTION__;
+  if (msg == NULL)
+    return E_POINTER;
+
+  if (msg->message == WM_KEYDOWN && msg->wParam == VK_TAB) {
+    HWND focus = ::GetFocus();
+    if (focus != m_hWnd && !::IsChild(m_hWnd, focus)) {
+      // The call to SetFocus triggers a WM_SETFOCUS that makes the base class
+      // set focus to the correct element in Chrome.
+      ::SetFocus(m_hWnd);
+      return S_OK;
+    }
+  }
+
+  return S_FALSE;
+}
+// Override IPersistStorageImpl::IsDirty
+STDMETHODIMP ChromeActiveDocument::IsDirty() {
+  DLOG(INFO) << __FUNCTION__;
+  return S_FALSE;
+}
+
+bool ChromeActiveDocument::is_frame_busting_enabled() {
+  return false;
+}
+
+STDMETHODIMP ChromeActiveDocument::Load(BOOL fully_avalable,
+                                        IMoniker* moniker_name,
+                                        LPBC bind_context,
+                                        DWORD mode) {
+  if (NULL == moniker_name) {
+    return E_INVALIDARG;
+  }
+  CComHeapPtr<WCHAR> display_name;
+  moniker_name->GetDisplayName(bind_context, NULL, &display_name);
+  std::wstring url = display_name;
+
+  bool is_chrome_protocol = StartsWith(url, kChromeProtocolPrefix, false);
+  bool is_new_navigation = true;
+
+  if (is_chrome_protocol) {
+    url.erase(0, lstrlen(kChromeProtocolPrefix));
+    is_new_navigation = 
+        !StartsWith(url, kChromeAttachExternalTabPrefix, false);
+  }
+
+  if (!IsValidUrlScheme(url)) {
+    DLOG(WARNING) << __FUNCTION__ << " Disallowing navigation to url: "
+                  << url;
+    return E_INVALIDARG;
+  }
+
+  if (!is_new_navigation) {
+    WStringTokenizer tokenizer(url, L"&");
+    // Skip over kChromeAttachExternalTabPrefix
+    tokenizer.GetNext();
+
+    intptr_t external_tab_cookie = 0;
+
+    if (tokenizer.GetNext())
+      StringToInt(tokenizer.token(),
+                  reinterpret_cast<int*>(&external_tab_cookie));
+
+    if (external_tab_cookie == 0) {
+      NOTREACHED() << "invalid url for attach tab: " << url;
+      return E_FAIL;
+    }
+
+    automation_client_->AttachExternalTab(external_tab_cookie);
+  } 
+
+  // Initiate navigation before launching chrome so that the url will be
+  // cached and sent with launch settings.
+  if (is_new_navigation) {
+    url_.Reset(::SysAllocString(url.c_str()));
+    if (url_.Length()) {
+      std::string utf8_url;
+      WideToUTF8(url_, url_.Length(), &utf8_url);
+      if (!automation_client_->InitiateNavigation(utf8_url)) {
+        DLOG(ERROR) << "Invalid URL: " << url;
+        Error(L"Invalid URL");
+        url_.Reset();
+        return E_INVALIDARG;
+      }
+
+      DLOG(INFO) << "Url is " << url_;
+    }
+  }
+
+  if (!is_automation_client_reused_ &&
+      !InitializeAutomation(GetHostProcessName(false), L"", IsIEInPrivate())) {
+    return E_FAIL;
+  }
+
+  if (!is_chrome_protocol) {
+    CComObject<UrlmonUrlRequest>* new_request = NULL;
+    CComObject<UrlmonUrlRequest>::CreateInstance(&new_request);
+    new_request->AddRef();
+
+    if (SUCCEEDED(new_request->ConnectToExistingMoniker(moniker_name,
+                                                        bind_context,
+                                                        url))) {
+      base_url_request_.swap(&new_request);
+      DCHECK(new_request == NULL);
+    } else {
+      new_request->Release();
+    }
+  }
+
+  UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.FullTabLaunchType",
+                              is_chrome_protocol, 0, 1, 2);
+  return S_OK;
+}
+
+STDMETHODIMP ChromeActiveDocument::Save(IMoniker* moniker_name,
+                                        LPBC bind_context,
+                                        BOOL remember) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP ChromeActiveDocument::SaveCompleted(IMoniker* moniker_name,
+                                                 LPBC bind_context) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP ChromeActiveDocument::GetCurMoniker(IMoniker** moniker_name) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP ChromeActiveDocument::GetClassID(CLSID* class_id) {
+  if (NULL == class_id) {
+    return E_POINTER;
+  }
+  *class_id = GetObjectCLSID();
+  return S_OK;
+}
+
+STDMETHODIMP ChromeActiveDocument::QueryStatus(const GUID* cmd_group_guid,
+                                               ULONG number_of_commands,
+                                               OLECMD commands[],
+                                               OLECMDTEXT* command_text) {
+  DLOG(INFO) << __FUNCTION__;
+  for (ULONG command_index = 0; command_index < number_of_commands;
+       command_index++) {
+    DLOG(INFO) << "Command id = " << commands[command_index].cmdID;
+    if (enabled_commands_map_.find(commands[command_index].cmdID) !=
+        enabled_commands_map_.end()) {
+      commands[command_index].cmdf = OLECMDF_ENABLED;
+    }
+  }
+  return S_OK;
+}
+
+STDMETHODIMP ChromeActiveDocument::Exec(const GUID* cmd_group_guid,
+                                        DWORD command_id,
+                                        DWORD cmd_exec_opt,
+                                        VARIANT* in_args,
+                                        VARIANT* out_args) {
+  DLOG(INFO) << __FUNCTION__ << " Cmd id =" << command_id;
+  // Bail out if we have been uninitialized.
+  if (automation_client_.get() && automation_client_->tab()) {
+    return ProcessExecCommand(cmd_group_guid, command_id, cmd_exec_opt,
+                              in_args, out_args);
+  }
+  return S_FALSE;
+}
+
+STDMETHODIMP ChromeActiveDocument::GetUrlForEvents(BSTR* url) {
+  if (NULL == url) {
+    return E_POINTER;
+  }
+  *url = ::SysAllocString(url_);
+  return S_OK;
+}
+
+HRESULT ChromeActiveDocument::IOleObject_SetClientSite(
+    IOleClientSite* client_site) {
+  if (client_site == NULL) {
+    ChromeActiveDocument* cached_document = g_active_doc_cache.Get();
+    if (cached_document) {
+      DCHECK(this == cached_document);
+      g_active_doc_cache.Set(NULL);
+      cached_document->Release();
+    }
+  }
+  return Base::IOleObject_SetClientSite(client_site);
+}
+
+
+HRESULT ChromeActiveDocument::ActiveXDocActivate(LONG verb) {
+  HRESULT hr = S_OK;
+  m_bNegotiatedWnd = TRUE;
+  if (!m_bInPlaceActive) {
+    hr = m_spInPlaceSite->CanInPlaceActivate();
+    if (FAILED(hr)) {
+      return hr;
+    }
+    m_spInPlaceSite->OnInPlaceActivate();
+  }
+  m_bInPlaceActive = TRUE;
+  // get location in the parent window,
+  // as well as some information about the parent
+  ScopedComPtr<IOleInPlaceUIWindow> in_place_ui_window;
+  frame_info_.cb = sizeof(OLEINPLACEFRAMEINFO);
+  HWND parent_window = NULL;
+  if (m_spInPlaceSite->GetWindow(&parent_window) == S_OK) {
+    in_place_frame_.Release();
+    RECT position_rect = {0};
+    RECT clip_rect = {0};
+    m_spInPlaceSite->GetWindowContext(in_place_frame_.Receive(),
+                                      in_place_ui_window.Receive(),
+                                      &position_rect,
+                                      &clip_rect,
+                                      &frame_info_);
+    if (!m_bWndLess) {
+      if (IsWindow()) {
+        ::ShowWindow(m_hWnd, SW_SHOW);
+        SetFocus();
+      } else {
+        m_hWnd = Create(parent_window, position_rect);
+      }
+    }
+    SetObjectRects(&position_rect, &clip_rect);
+  }
+
+  ScopedComPtr<IOleInPlaceActiveObject> in_place_active_object(this);
+
+  // Gone active by now, take care of UIACTIVATE
+  if (DoesVerbUIActivate(verb)) {
+    if (!m_bUIActive) {
+      m_bUIActive = TRUE;
+      hr = m_spInPlaceSite->OnUIActivate();
+      if (FAILED(hr)) {
+        return hr;
+      }
+      // set ourselves up in the host
+      if (in_place_active_object) {
+        if (in_place_frame_) {
+          in_place_frame_->SetActiveObject(in_place_active_object, NULL);
+        }
+        if (in_place_ui_window) {
+          in_place_ui_window->SetActiveObject(in_place_active_object, NULL);
+        }
+      }
+    }
+  }
+  m_spClientSite->ShowObject();
+  return S_OK;
+}
+
+void ChromeActiveDocument::OnNavigationStateChanged(int tab_handle, int flags,
+    const IPC::NavigationInfo& nav_info) {
+  // TODO(joshia): handle INVALIDATE_TAB,INVALIDATE_LOAD etc.
+  DLOG(INFO) << __FUNCTION__ << std::endl << " Flags: " << flags
+      << "Url: " << nav_info.url <<
+      ", Title: " << nav_info.title <<
+      ", Type: " << nav_info.navigation_type << ", Relative Offset: " <<
+      nav_info.relative_offset << ", Index: " << nav_info.navigation_index;;
+
+  UpdateNavigationState(nav_info);
+}
+
+void ChromeActiveDocument::OnUpdateTargetUrl(int tab_handle,
+    const std::wstring& new_target_url) {
+  if (in_place_frame_) {
+    in_place_frame_->SetStatusText(new_target_url.c_str());
+  }
+}
+
+bool IsFindAccelerator(const MSG& msg) {
+  // TODO(robertshield): This may not stand up to localization. Fix if this
+  // is the case.
+  return msg.message == WM_KEYDOWN && msg.wParam == 'F' &&
+         win_util::IsCtrlPressed() &&
+         !(win_util::IsAltPressed() || win_util::IsShiftPressed());
+}
+
+void ChromeActiveDocument::OnAcceleratorPressed(int tab_handle,
+                                                const MSG& accel_message) {
+  bool handled_accel = false;
+  if (in_place_frame_ != NULL) {
+    handled_accel = (S_OK == in_place_frame_->TranslateAcceleratorW(
+        const_cast<MSG*>(&accel_message), 0));
+  }
+
+  if (!handled_accel) {
+    if (IsFindAccelerator(accel_message)) {
+      // Handle the showing of the find dialog explicitly.
+      OnFindInPage();
+    } else if (AllowFrameToTranslateAccelerator(accel_message) != S_OK) {
+      DLOG(INFO) << "IE DID NOT handle accel key " << accel_message.wParam;
+      TabProxy* tab = GetTabProxy();
+      if (tab) {
+        tab->ProcessUnhandledAccelerator(accel_message);
+      }
+    }
+  } else {
+    DLOG(INFO) << "IE handled accel key " << accel_message.wParam;
+  }
+}
+
+void ChromeActiveDocument::OnTabbedOut(int tab_handle, bool reverse) {
+  DLOG(INFO) << __FUNCTION__;
+  if (in_place_frame_) {
+    MSG msg = { NULL, WM_KEYDOWN, VK_TAB };
+    in_place_frame_->TranslateAcceleratorW(&msg, 0);
+  }
+}
+
+void ChromeActiveDocument::OnDidNavigate(int tab_handle,
+                                         const IPC::NavigationInfo& nav_info) {
+  DLOG(INFO) << __FUNCTION__ << std::endl << "Url: " << nav_info.url <<
+      ", Title: " << nav_info.title <<
+      ", Type: " << nav_info.navigation_type << ", Relative Offset: " <<
+      nav_info.relative_offset << ", Index: " << nav_info.navigation_index;
+
+  // This could be NULL if the active document instance is being destroyed.
+  if (!m_spInPlaceSite) {
+    DLOG(INFO) << __FUNCTION__ << "m_spInPlaceSite is NULL. Returning";
+    return;
+  }
+
+  UpdateNavigationState(nav_info);
+}
+
+void ChromeActiveDocument::UpdateNavigationState(
+    const IPC::NavigationInfo& new_navigation_info) {
+  bool is_title_changed = (navigation_info_.title != new_navigation_info.title);
+  bool is_url_changed = (navigation_info_.url.is_valid() &&
+      (navigation_info_.url != new_navigation_info.url));
+  bool is_ssl_state_changed =
+      (navigation_info_.security_style != new_navigation_info.security_style) ||
+      (navigation_info_.has_mixed_content !=
+          new_navigation_info.has_mixed_content);
+
+  navigation_info_ = new_navigation_info;
+
+  if (is_title_changed) {
+    ScopedVariant title(navigation_info_.title.c_str());
+    IEExec(NULL, OLECMDID_SETTITLE, OLECMDEXECOPT_DONTPROMPTUSER,
+           title.AsInput(), NULL);
+  }
+
+  if (is_ssl_state_changed) {
+    int lock_status = SECURELOCK_SET_UNSECURE;
+    switch (navigation_info_.security_style) {
+      case SECURITY_STYLE_AUTHENTICATION_BROKEN:
+        lock_status = SECURELOCK_SET_SECUREUNKNOWNBIT;
+        break;
+      case SECURITY_STYLE_AUTHENTICATED:
+        lock_status = navigation_info_.has_mixed_content ?
+            SECURELOCK_SET_MIXED : SECURELOCK_SET_SECUREUNKNOWNBIT;
+        break;
+      default:
+        break;
+    }
+
+    ScopedVariant secure_lock_status(lock_status);
+    IEExec(&CGID_ShellDocView, INTERNAL_CMDID_SET_SSL_LOCK,
+           OLECMDEXECOPT_DODEFAULT, secure_lock_status.AsInput(), NULL);
+  }
+
+  if (navigation_info_.url.is_valid() &&
+      (is_url_changed || url_.Length() == 0)) {
+    url_.Allocate(UTF8ToWide(navigation_info_.url.spec()).c_str());
+    // Now call the FireNavigateCompleteEvent which makes IE update the text
+    // in the address-bar. We call the FireBeforeNavigateComplete2Event and
+    // FireDocumentComplete event just for completeness sake. If some BHO
+    // chooses to cancel the navigation in the OnBeforeNavigate2 handler
+    // we will ignore the cancellation request.
+
+    // Todo(joshia): investigate if there's a better way to set URL in the
+    // address bar
+    ScopedComPtr<IWebBrowserEventsService> web_browser_events_svc;
+    DoQueryService(__uuidof(web_browser_events_svc), m_spClientSite,
+                   web_browser_events_svc.Receive());
+    if (web_browser_events_svc) {
+      // TODO(joshia): maybe we should call FireBeforeNavigate2Event in
+      // ChromeActiveDocument::Load and abort if cancelled.
+      VARIANT_BOOL should_cancel = VARIANT_FALSE;
+      web_browser_events_svc->FireBeforeNavigate2Event(&should_cancel);
+      web_browser_events_svc->FireNavigateComplete2Event();
+      if (VARIANT_TRUE != should_cancel) {
+        web_browser_events_svc->FireDocumentCompleteEvent();
+      }
+    }
+  }
+}
+
+void ChromeActiveDocument::OnFindInPage() {
+  TabProxy* tab = GetTabProxy();
+  if (tab) {
+    if (!find_dialog_.IsWindow()) {
+      find_dialog_.Create(m_hWnd);
+    }
+
+    find_dialog_.ShowWindow(SW_SHOW);
+  }
+}
+
+void ChromeActiveDocument::OnViewSource() {
+  DCHECK(navigation_info_.url.is_valid());
+  std::string url_to_open = "view-source:";
+  url_to_open += navigation_info_.url.spec();
+  OnOpenURL(0, GURL(url_to_open), NEW_WINDOW);
+}
+
+void ChromeActiveDocument::OnOpenURL(int tab_handle, const GURL& url_to_open,
+                                     int open_disposition) {
+  // If the disposition indicates that we should be opening the URL in the
+  // current tab, then we can reuse the ChromeFrameAutomationClient instance
+  // maintained by the current ChromeActiveDocument instance. We cache this
+  // instance so that it can be used by the new ChromeActiveDocument instance
+  // which may be instantiated for handling the new URL.
+  if (open_disposition == CURRENT_TAB) {
+    // Grab a reference to ensure that the document remains valid.
+    AddRef();
+    g_active_doc_cache.Set(this);
+  }
+
+  Base::OnOpenURL(tab_handle, url_to_open, open_disposition);
+}
+
+void ChromeActiveDocument::OnLoad(int tab_handle, const GURL& url) {
+  if (ready_state_ < READYSTATE_COMPLETE) {
+    ready_state_ = READYSTATE_COMPLETE;
+    FireOnChanged(DISPID_READYSTATE);
+  }
+}
+
+bool ChromeActiveDocument::PreProcessContextMenu(HMENU menu) {
+  ScopedComPtr<IBrowserService> browser_service;
+  ScopedComPtr<ITravelLog> travel_log;
+
+  DoQueryService(SID_SShellBrowser, m_spClientSite, browser_service.Receive());
+  if (!browser_service)
+    return true;
+
+  browser_service->GetTravelLog(travel_log.Receive());
+  if (!travel_log)
+    return true;
+
+  if (SUCCEEDED(travel_log->GetTravelEntry(browser_service, TLOG_BACK, NULL))) {
+    EnableMenuItem(menu, IDS_CONTENT_CONTEXT_BACK, MF_BYCOMMAND | MF_ENABLED);
+  } else {
+    EnableMenuItem(menu, IDS_CONTENT_CONTEXT_BACK, MF_BYCOMMAND | MFS_DISABLED);
+  }
+
+
+  if (SUCCEEDED(travel_log->GetTravelEntry(browser_service, TLOG_FORE, NULL))) {
+    EnableMenuItem(menu, IDS_CONTENT_CONTEXT_FORWARD,
+                   MF_BYCOMMAND | MF_ENABLED);
+  } else {
+    EnableMenuItem(menu, IDS_CONTENT_CONTEXT_FORWARD,
+                   MF_BYCOMMAND | MFS_DISABLED);
+  }
+
+  // Call base class (adds 'About' item)
+  return Base::PreProcessContextMenu(menu);
+}
+
+bool ChromeActiveDocument::HandleContextMenuCommand(UINT cmd) {
+  ScopedComPtr<IWebBrowser2> web_browser2;
+  DoQueryService(SID_SWebBrowserApp, m_spClientSite, web_browser2.Receive());
+
+  switch (cmd) {
+    case IDS_CONTENT_CONTEXT_BACK:
+      web_browser2->GoBack();
+      break;
+
+    case IDS_CONTENT_CONTEXT_FORWARD:
+      web_browser2->GoForward();
+      break;
+
+    case IDS_CONTENT_CONTEXT_RELOAD:
+      web_browser2->Refresh();
+      break;
+
+    default:
+      return Base::HandleContextMenuCommand(cmd);
+  }
+
+  return true;
+}
+
+HRESULT ChromeActiveDocument::IEExec(const GUID* cmd_group_guid,
+                                     DWORD command_id, DWORD cmd_exec_opt,
+                                     VARIANT* in_args, VARIANT* out_args) {
+  HRESULT hr = E_FAIL;
+  ScopedComPtr<IOleCommandTarget> frame_cmd_target;
+  if (m_spInPlaceSite)
+    hr = frame_cmd_target.QueryFrom(m_spInPlaceSite);
+
+  if (frame_cmd_target)
+    hr = frame_cmd_target->Exec(cmd_group_guid, command_id, cmd_exec_opt,
+                                in_args, out_args);
+
+  return hr;
+}
diff --git a/chrome_frame/chrome_active_document.h b/chrome_frame/chrome_active_document.h
new file mode 100644
index 0000000..b488bc5
--- /dev/null
+++ b/chrome_frame/chrome_active_document.h
@@ -0,0 +1,258 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_CHROME_ACTIVE_DOCUMENT_H_
+#define CHROME_FRAME_CHROME_ACTIVE_DOCUMENT_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <atlctl.h>
+#include <map>
+
+#include "base/scoped_ptr.h"
+#include "base/scoped_comptr_win.h"
+#include "base/thread.h"
+
+#include "chrome_frame/chrome_frame_activex_base.h"
+#include "chrome_frame/com_type_info_holder.h"
+#include "chrome_frame/find_dialog.h"
+#include "chrome_frame/in_place_menu.h"
+#include "chrome_frame/ole_document_impl.h"
+#include "chrome_frame/resource.h"
+#include "chrome_frame/extra_system_apis.h"
+
+class Thread;
+class TabProxy;
+class ChromeActiveDocument;
+
+// A call to IOleCommandTarget::Exec on the webbrowser with this command id
+// and a command group of CGID_EXPLORER causes IE to finalize the current
+// travel log entry and move to a new location (pruning any forward entries
+// if needed)
+#define INTERNAL_CMDID_FINALIZE_TRAVEL_LOG (38)
+
+// To set the lock icon status call IOleCommandTarget::Exec on site with
+// this command id and a command group of CGID_EXPLORER  The in arg is one of
+// the values: SECURELOCK_SET_UNSECURE, SECURELOCK_SET_MIXED,
+// SECURELOCK_SET_SECURE128BIT etc declared in shdeprecated.h
+#define INTERNAL_CMDID_SET_SSL_LOCK (37)
+
+// A call to IOleCommandTarget::Exec on the webbrowser with this command id
+// and a command group of CGID_EXPLORER causes IE to replace the URL in the
+// current travel log entry
+#define UNDOC_CMDID_REPLACE_CURRENT_TRAVEL_LOG_ENTRY_URL (40)
+
+#define UNDOC_IE_CONTEXTMENU_ADDFAV        (2261)
+#define UNDOC_IE_CONTEXTMENU_VIEWSOURCE    (2139)
+#define UNDOC_IE_CONTEXTMENU_REFRESH       (6042)
+
+// This macro should be defined in the public section of the class.
+#define BEGIN_EXEC_COMMAND_MAP(theClass) \
+  public: \
+   HRESULT ProcessExecCommand(const GUID* cmd_group_guid, DWORD command_id, \
+                              DWORD cmd_exec_opt, VARIANT* in_args, \
+                              VARIANT* out_args) { \
+   HRESULT hr = OLECMDERR_E_NOTSUPPORTED; \
+   switch (command_id) {
+
+
+#define EXEC_COMMAND_HANDLER(id, handler) \
+  case id: { \
+    hr = S_OK;  \
+    handler(cmd_group_guid, command_id, cmd_exec_opt, in_args, out_args)  \
+    break;  \
+  }
+
+#define EXEC_COMMAND_HANDLER_NO_ARGS(id, handler) \
+  case id: { \
+    hr = S_OK;  \
+    handler();  \
+    break;  \
+  }
+
+#define EXEC_COMMAND_HANDLER_GENERIC(id, code) \
+  case id: { \
+    hr = S_OK;  \
+    code;  \
+    break;  \
+  }
+
+#define END_EXEC_COMMAND_MAP()  \
+    default: \
+      break; \
+  } \
+  return hr; \
+}
+
+// ChromeActiveDocument: Implementation of the Active Document object that is
+// responsible for rendering pages in Chrome. This object delegates to
+// Chrome.exe (via the Chrome IPC-based automation mechanism) for the actual
+// rendering
+class ATL_NO_VTABLE ChromeActiveDocument
+     : public ChromeFrameActivexBase<ChromeActiveDocument,
+                                     CLSID_ChromeActiveDocument>,
+       public IOleDocumentImpl<ChromeActiveDocument>,
+       public IOleDocumentViewImpl<ChromeActiveDocument>,
+       public IPersistMoniker,
+       public IOleCommandTarget,
+       public InPlaceMenu<ChromeActiveDocument>,
+       public IWebBrowserEventsUrlService {
+ public:
+  typedef ChromeFrameActivexBase<ChromeActiveDocument,
+      CLSID_ChromeActiveDocument> Base;
+  ChromeActiveDocument();
+  ~ChromeActiveDocument();
+
+  DECLARE_REGISTRY_RESOURCEID(IDR_CHROMEACTIVEDOCUMENT)
+
+BEGIN_COM_MAP(ChromeActiveDocument)
+  COM_INTERFACE_ENTRY(IOleDocument)
+  COM_INTERFACE_ENTRY(IOleDocumentView)
+  COM_INTERFACE_ENTRY(IPersistMoniker)
+  COM_INTERFACE_ENTRY(IOleCommandTarget)
+  COM_INTERFACE_ENTRY(IWebBrowserEventsUrlService)
+  COM_INTERFACE_ENTRY_CHAIN(Base)
+END_COM_MAP()
+
+BEGIN_MSG_MAP(ChromeActiveDocument)
+  CHAIN_MSG_MAP(Base)
+END_MSG_MAP()
+
+  HRESULT FinalConstruct();
+
+#define FORWARD_TAB_COMMAND(id, command) \
+  EXEC_COMMAND_HANDLER_GENERIC(id, GetTabProxy() ? GetTabProxy()->command() : 1)
+
+BEGIN_EXEC_COMMAND_MAP(ChromeActiveDocument)
+  EXEC_COMMAND_HANDLER_GENERIC(OLECMDID_PRINT, automation_client_->PrintTab())
+  EXEC_COMMAND_HANDLER_NO_ARGS(OLECMDID_FIND, OnFindInPage)
+  EXEC_COMMAND_HANDLER_NO_ARGS(UNDOC_IE_CONTEXTMENU_VIEWSOURCE, OnViewSource)
+  FORWARD_TAB_COMMAND(OLECMDID_SELECTALL, SelectAll)
+  FORWARD_TAB_COMMAND(OLECMDID_CUT, Cut)
+  FORWARD_TAB_COMMAND(OLECMDID_COPY, Copy)
+  FORWARD_TAB_COMMAND(OLECMDID_PASTE, Paste)
+  FORWARD_TAB_COMMAND(OLECMDID_REFRESH, ReloadAsync)
+  FORWARD_TAB_COMMAND(OLECMDID_STOP, StopAsync)
+END_EXEC_COMMAND_MAP()
+
+  // IPCs from automation server.
+  virtual void OnNavigationStateChanged(int tab_handle, int flags,
+                                        const IPC::NavigationInfo& nav_info);
+  virtual void OnUpdateTargetUrl(int tab_handle,
+                                 const std::wstring& new_target_url);
+  virtual void OnAcceleratorPressed(int tab_handle, const MSG& accel_message);
+  virtual void OnTabbedOut(int tab_handle, bool reverse);
+  virtual void OnDidNavigate(int tab_handle,
+                             const IPC::NavigationInfo& nav_info);
+
+  void OnFindInPage();
+
+  // Override DoVerb
+  STDMETHOD(DoVerb)(LONG verb,
+                    LPMSG msg,
+                    IOleClientSite* active_site,
+                    LONG index,
+                    HWND parent_window,
+                    LPCRECT pos);
+  STDMETHOD(InPlaceDeactivate)(void);
+
+  // Override IOleInPlaceActiveObjectImpl::OnDocWindowActivate
+  STDMETHOD(OnDocWindowActivate)(BOOL activate);
+  STDMETHOD(TranslateAccelerator)(MSG* msg);
+
+  // IPersistMoniker methods
+  STDMETHOD(GetClassID)(CLSID* class_id);
+  STDMETHOD(IsDirty)();
+  STDMETHOD(GetCurMoniker)(IMoniker** moniker_name);
+  STDMETHOD(Load)(BOOL fully_avalable,
+                  IMoniker* moniker_name,
+                  LPBC bind_context,
+                  DWORD mode);
+  STDMETHOD(Save)(IMoniker* moniker_name,
+                  LPBC bind_context,
+                  BOOL remember);
+  STDMETHOD(SaveCompleted)(IMoniker* moniker_name,
+                  LPBC bind_context);
+
+  // IOleCommandTarget methods
+  STDMETHOD(QueryStatus)(const GUID* cmd_group_guid,
+                         ULONG number_of_commands,
+                         OLECMD commands[],
+                         OLECMDTEXT* command_text);
+  STDMETHOD(Exec)(const GUID* cmd_group_guid, DWORD command_id,
+                  DWORD cmd_exec_opt,
+                  VARIANT* in_args,
+                  VARIANT* out_args);
+
+  // IWebBrowserEventsUrlService methods
+  STDMETHOD(GetUrlForEvents)(BSTR* url);
+
+  // ChromeFrameActivexBase overrides
+  HRESULT IOleObject_SetClientSite(IOleClientSite* client_site);
+
+  HRESULT ActiveXDocActivate(LONG verb);
+
+  // Callbacks from ChromeFramePlugin<T>
+  bool PreProcessContextMenu(HMENU menu);
+  bool HandleContextMenuCommand(UINT cmd);
+
+  // Should connections initiated by this class try to block 
+  // responses served with the X-Frame-Options header?
+  bool is_frame_busting_enabled();
+
+ protected:
+  // ChromeFrameActivexBase overrides
+  virtual void OnOpenURL(int tab_handle, const GURL& url_to_open,
+                         int open_disposition);
+
+  virtual void OnLoad(int tab_handle, const GURL& url);
+
+  // A helper method that updates our internal navigation state
+  // as well as IE's navigation state (viz Title and current URL).
+  // The navigation_flags is a TabContents::InvalidateTypes enum
+  void UpdateNavigationState(const IPC::NavigationInfo& nav_info);
+
+  TabProxy* GetTabProxy() const {
+    if (automation_client_.get())
+      return automation_client_->tab();
+    return NULL;
+  }
+
+  // Exec command handlers
+  void OnViewSource();
+
+  // Call exec on our site's command target
+  HRESULT IEExec(const GUID* cmd_group_guid, DWORD command_id,
+                 DWORD cmd_exec_opt, VARIANT* in_args, VARIANT* out_args);
+
+ protected:
+  typedef std::map<int, bool> EnabledCommandsMap;
+
+  IPC::NavigationInfo navigation_info_;
+  bool is_doc_object_;
+
+  // This indicates whether this is the first navigation in this
+  // active document. It is initalize to true and it is set to false
+  // after we get a navigation notification from Chrome
+  bool first_navigation_;
+
+  // Our find dialog
+  CFFindDialog find_dialog_;
+
+  // Contains the list of enabled commands ids.
+  EnabledCommandsMap enabled_commands_map_;
+
+  // Set to true if the automation_client_ member is initialized from
+  // an existing ChromeActiveDocument instance which is going away and
+  // a new ChromeActiveDocument instance is taking its place.
+  bool is_automation_client_reused_;
+
+ public:
+  ScopedComPtr<IOleInPlaceFrame> in_place_frame_;
+  OLEINPLACEFRAMEINFO frame_info_;
+};
+
+OBJECT_ENTRY_AUTO(__uuidof(ChromeActiveDocument), ChromeActiveDocument)
+
+#endif  // CHROME_FRAME_CHROME_ACTIVE_DOCUMENT_H_
diff --git a/chrome_frame/chrome_active_document.rgs b/chrome_frame/chrome_active_document.rgs
new file mode 100644
index 0000000..2fae04b
--- /dev/null
+++ b/chrome_frame/chrome_active_document.rgs
@@ -0,0 +1,67 @@
+HKLM {

+  NoRemove Software {

+    NoRemove Classes {

+      ChromeTab.ChromeActiveDocument.1 = s 'ChromeActiveDocument Class' {

+        CLSID = s '{3E1D0E7F-F5E3-44CC-AA6A-C0A637619AB8}'

+        'DocObject' = s '0'

+        val EditFlags = d '65536'

+      }

+      ChromeTab.ChromeActiveDocument = s 'ChromeActiveDocument Class' {

+        CLSID = s '{3E1D0E7F-F5E3-44CC-AA6A-C0A637619AB8}'

+        CurVer = s 'ChromeTab.ChromeActiveDocument.1'

+      }

+      NoRemove CLSID {

+        ForceRemove {3E1D0E7F-F5E3-44CC-AA6A-C0A637619AB8} = s 'ChromeActiveDocument Class' {

+          ProgID = s 'ChromeTab.ChromeActiveDocument.1'

+          VersionIndependentProgID = s 'ChromeTab.ChromeActiveDocument'

+          ForceRemove 'Programmable'

+          InprocServer32 = s '%MODULE%' {

+            val ThreadingModel = s 'Apartment'

+          }

+          val AppID = s '%APPID%'

+          ForceRemove 'Control'

+          ForceRemove 'ToolboxBitmap32' = s '%MODULE%, 104'

+          'DocObject' = s '0'

+          'MiscStatus' = s '0' {

+              '1' = s '%OLEMISC%'

+          }

+          'TypeLib' = s '{6F2664E1-FF6E-488A-BCD1-F4CA6001DFCC}'

+          'Version' = s '1.0'

+        }

+      }

+    }

+  }

+}

+

+HKLM {

+  NoRemove Software {

+    NoRemove Classes {

+      NoRemove MIME {

+        NoRemove Database {

+          NoRemove s 'Content Type' {

+            'application/chromepage' {

+              val CLSID = s '{3E1D0E7F-F5E3-44CC-AA6A-C0A637619AB8}'

+            }

+          }

+        }

+      }

+    }

+  }

+}

+

+HKCU {

+  NoRemove Software {

+    NoRemove Microsoft {

+      NoRemove Windows {

+        NoRemove CurrentVersion {

+          NoRemove 'Internet Settings' {

+            NoRemove 'Secure Mime Handlers' {

+              val ChromeTab.ChromeActiveDocument.1 = d '1'

+              val ChromeTab.ChromeActiveDocument = d '1'

+            }

+          }

+        }

+      }

+    }

+  }

+}

diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp
new file mode 100644
index 0000000..8782076
--- /dev/null
+++ b/chrome_frame/chrome_frame.gyp
@@ -0,0 +1,725 @@
+# Copyright (c) 2009 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.
+
+{
+  'variables': {
+    'chromium_code': 1,
+    'xul_sdk_dir': '../third_party/xulrunner-sdk/<(OS)',
+
+    # Keep the archive builder happy.
+    'chrome_personalization%': 1,
+    'use_syncapi_stub%': 0,
+
+    # Deps info.
+    'xul_include_directories': [
+      # TODO(slightlyoff): pare these down. This makes it too easy to
+      #     regress to using unfrozen FF interfaces.
+      '<(xul_sdk_dir)/include',
+      '<(xul_sdk_dir)/include/accessibility',
+      '<(xul_sdk_dir)/include/alerts',
+      '<(xul_sdk_dir)/include/appcomps',
+      '<(xul_sdk_dir)/include/appshell',
+      '<(xul_sdk_dir)/include/autocomplete',
+      '<(xul_sdk_dir)/include/autoconfig',
+      '<(xul_sdk_dir)/include/ax_common',
+      '<(xul_sdk_dir)/include/browser',
+      '<(xul_sdk_dir)/include/cairo',
+      '<(xul_sdk_dir)/include/caps',
+      '<(xul_sdk_dir)/include/chardet',
+      '<(xul_sdk_dir)/include/chrome',
+      '<(xul_sdk_dir)/include/commandhandler',
+      '<(xul_sdk_dir)/include/composer',
+      '<(xul_sdk_dir)/include/content',
+      '<(xul_sdk_dir)/include/contentprefs',
+      '<(xul_sdk_dir)/include/cookie',
+      '<(xul_sdk_dir)/include/crashreporter',
+      '<(xul_sdk_dir)/include/docshell',
+      '<(xul_sdk_dir)/include/dom',
+      '<(xul_sdk_dir)/include/downloads',
+      '<(xul_sdk_dir)/include/editor',
+      '<(xul_sdk_dir)/include/embed_base',
+      '<(xul_sdk_dir)/include/embedcomponents',
+      '<(xul_sdk_dir)/include/expat',
+      '<(xul_sdk_dir)/include/extensions',
+      '<(xul_sdk_dir)/include/exthandler',
+      '<(xul_sdk_dir)/include/exthelper',
+      '<(xul_sdk_dir)/include/fastfind',
+      '<(xul_sdk_dir)/include/feeds',
+      '<(xul_sdk_dir)/include/find',
+      '<(xul_sdk_dir)/include/gfx',
+      '<(xul_sdk_dir)/include/htmlparser',
+      '<(xul_sdk_dir)/include/imgicon',
+      '<(xul_sdk_dir)/include/imglib2',
+      '<(xul_sdk_dir)/include/inspector',
+      '<(xul_sdk_dir)/include/intl',
+      '<(xul_sdk_dir)/include/jar',
+      '<(xul_sdk_dir)/include/java',
+      '<(xul_sdk_dir)/include/jpeg',
+      '<(xul_sdk_dir)/include/js',
+      '<(xul_sdk_dir)/include/jsdebug',
+      '<(xul_sdk_dir)/include/jsurl',
+      '<(xul_sdk_dir)/include/layout',
+      '<(xul_sdk_dir)/include/lcms',
+      '<(xul_sdk_dir)/include/libbz2',
+      '<(xul_sdk_dir)/include/libmar',
+      '<(xul_sdk_dir)/include/libpixman',
+      '<(xul_sdk_dir)/include/libreg',
+      '<(xul_sdk_dir)/include/liveconnect',
+      '<(xul_sdk_dir)/include/locale',
+      '<(xul_sdk_dir)/include/loginmgr',
+      '<(xul_sdk_dir)/include/lwbrk',
+      '<(xul_sdk_dir)/include/mimetype',
+      '<(xul_sdk_dir)/include/morkreader',
+      '<(xul_sdk_dir)/include/necko',
+      '<(xul_sdk_dir)/include/nkcache',
+      '<(xul_sdk_dir)/include/nspr',
+      '<(xul_sdk_dir)/include/nss',
+      '<(xul_sdk_dir)/include/oji',
+      '<(xul_sdk_dir)/include/parentalcontrols',
+      '<(xul_sdk_dir)/include/pipboot',
+      '<(xul_sdk_dir)/include/pipnss',
+      '<(xul_sdk_dir)/include/pippki',
+      '<(xul_sdk_dir)/include/places',
+      '<(xul_sdk_dir)/include/plugin',
+      '<(xul_sdk_dir)/include/png',
+      '<(xul_sdk_dir)/include/pref',
+      '<(xul_sdk_dir)/include/prefetch',
+      '<(xul_sdk_dir)/include/profdirserviceprovider',
+      '<(xul_sdk_dir)/include/profile',
+      '<(xul_sdk_dir)/include/rdf',
+      '<(xul_sdk_dir)/include/rdfutil',
+      '<(xul_sdk_dir)/include/satchel',
+      '<(xul_sdk_dir)/include/shistory',
+      '<(xul_sdk_dir)/include/simple',
+      '<(xul_sdk_dir)/include/spellchecker',
+      '<(xul_sdk_dir)/include/sqlite3',
+      '<(xul_sdk_dir)/include/storage',
+      '<(xul_sdk_dir)/include/string',
+      '<(xul_sdk_dir)/include/thebes',
+      '<(xul_sdk_dir)/include/toolkitcomps',
+      '<(xul_sdk_dir)/include/txmgr',
+      '<(xul_sdk_dir)/include/txtsvc',
+      '<(xul_sdk_dir)/include/uconv',
+      '<(xul_sdk_dir)/include/ucvcn',
+      '<(xul_sdk_dir)/include/ucvibm',
+      '<(xul_sdk_dir)/include/ucvja',
+      '<(xul_sdk_dir)/include/ucvko',
+      '<(xul_sdk_dir)/include/ucvlatin',
+      '<(xul_sdk_dir)/include/ucvmath',
+      '<(xul_sdk_dir)/include/ucvtw',
+      '<(xul_sdk_dir)/include/ucvtw2',
+      '<(xul_sdk_dir)/include/unicharutil',
+      '<(xul_sdk_dir)/include/update',
+      '<(xul_sdk_dir)/include/uriloader',
+      '<(xul_sdk_dir)/include/urlformatter',
+      '<(xul_sdk_dir)/include/util',
+      '<(xul_sdk_dir)/include/view',
+      '<(xul_sdk_dir)/include/webbrowserpersist',
+      '<(xul_sdk_dir)/include/webbrwsr',
+      '<(xul_sdk_dir)/include/webshell',
+      '<(xul_sdk_dir)/include/widget',
+      '<(xul_sdk_dir)/include/windowwatcher',
+      '<(xul_sdk_dir)/include/xml',
+      '<(xul_sdk_dir)/include/xml-rpc',
+      '<(xul_sdk_dir)/include/xpcom',
+      '<(xul_sdk_dir)/include/xpconnect',
+      '<(xul_sdk_dir)/include/xpinstall',
+      '<(xul_sdk_dir)/include/xulapp',
+      '<(xul_sdk_dir)/include/xuldoc',
+      '<(xul_sdk_dir)/include/xulrunner',
+      '<(xul_sdk_dir)/include/xultmpl',
+      '<(xul_sdk_dir)/include/zipwriter',
+      '<(xul_sdk_dir)/include/zlib',
+      '<(xul_sdk_dir)/sdk/include',
+    ],
+  },
+  'includes': [
+    '../build/common.gypi',
+  ],
+  'target_defaults': {
+    'dependencies': [
+      '../chrome/chrome.gyp:chrome_resources',
+      '../chrome/chrome.gyp:chrome_strings',
+      '../chrome/chrome.gyp:theme_resources',
+      '../skia/skia.gyp:skia',
+      '../third_party/npapi/npapi.gyp:npapi',
+    ],
+    'include_dirs': [
+      # all our own includes are relative to src/
+      '..',
+    ],
+  },
+  'targets': [
+    {
+      # TODO(slightlyoff): de-win23-ify
+      'target_name': 'xulrunner_sdk',
+      'type': 'none',
+      'conditions': [
+        ['OS=="win"', {
+          'direct_dependent_settings': {
+            'include_dirs': [
+              '<@(xul_include_directories)',
+            ],
+            'libraries': [
+              '../third_party/xulrunner-sdk/win/lib/xpcomglue_s.lib',
+              '../third_party/xulrunner-sdk/win/lib/xpcom.lib',
+              '../third_party/xulrunner-sdk/win/lib/nspr4.lib',
+            ],
+          },
+        },],
+      ],
+    },
+    {
+      # build the ICU stubs
+      'target_name': 'icu_stubs',
+      'type': 'static_library',
+      'dependencies': [
+        '../base/base.gyp:base',
+      ],
+      'sources': [
+        'icu_stubs.cc'
+      ],
+    },
+    {
+      # TODO(slightlyoff): de-win32-ify
+      #
+      # build the base_noicu.lib.
+      'target_name': 'base_noicu',
+      'type': 'none',
+      'dependencies': [
+        '../base/base.gyp:base',
+        'icu_stubs',
+      ],
+      'actions': [
+        {
+          'action_name': 'combine_libs',
+          'msvs_cygwin_shell': 0,
+          'inputs': [
+            '<(PRODUCT_DIR)/lib/base.lib',
+            '<(PRODUCT_DIR)/lib/icu_stubs.lib',
+          ],
+          'outputs': [
+            '<(PRODUCT_DIR)/lib/base_noicu.lib',
+          ],
+          'action': [
+            'python',
+            'combine_libs.py',
+            '-o <(_outputs)',
+            '-r (icu_|_icu.obj)',
+            '<@(_inputs)'],
+        },
+      ],
+      'direct_dependent_settings': {
+        # linker_settings
+        'libraries': [
+          '<(PRODUCT_DIR)/lib/base_noicu.lib',
+        ],
+      },
+    },
+    {
+      'target_name': 'chrome_frame_unittests',
+      'type': 'executable',
+      'dependencies': [
+        '../build/temp_gyp/googleurl.gyp:googleurl',
+        '../chrome/chrome.gyp:common',
+        '../chrome/chrome.gyp:utility',
+        '../testing/gmock.gyp:gmock',
+        '../testing/gtest.gyp:gtest',
+        'base_noicu',
+        'icu_stubs',
+        'chrome_frame_npapi',
+        'chrome_frame_strings',
+        'xulrunner_sdk',
+      ],
+      'sources': [
+        'chrome_frame_npapi_unittest.cc',
+        'chrome_frame_unittest_main.cc',
+        'chrome_launcher_unittest.cc',
+        'unittest_precompile.h',
+        'unittest_precompile.cc',
+        'urlmon_upload_data_stream.cc',
+        'urlmon_upload_data_stream_unittest.cc',
+        'chrome_frame_histograms.h',
+        'chrome_frame_histograms.cc',
+      ],
+      'include_dirs': [
+        # To allow including "chrome_tab.h"
+        '<(INTERMEDIATE_DIR)',
+      ],
+      'resource_include_dirs': [
+        '<(INTERMEDIATE_DIR)',
+      ],
+      'conditions': [
+        ['OS=="win"', {
+          'msvs_settings': {
+            'VCLinkerTool': {
+              'DelayLoadDLLs': ['xpcom.dll', 'nspr4.dll'],
+            },
+          },
+          'sources': [
+            '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_resources.rc',
+            '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_strings.rc',
+          ],
+          'dependencies': [
+            # TODO(slightlyoff): Get automation targets working on OS X
+            '../chrome/chrome.gyp:automation',
+            '../chrome/installer/installer.gyp:installer_util',
+            '../google_update/google_update.gyp:google_update',
+          ]
+        }],
+      ],
+    },
+    {
+      'target_name': 'chrome_frame_tests',
+      'type': 'executable',
+      'dependencies': [
+        # 'base_noicu',
+        '../build/temp_gyp/googleurl.gyp:googleurl',
+        '../chrome/chrome.gyp:common',
+        '../chrome/chrome.gyp:utility',
+        '../testing/gmock.gyp:gmock',
+        '../testing/gtest.gyp:gtest',
+        '../third_party/libxml/libxml.gyp:libxml',
+        '../third_party/libxslt/libxslt.gyp:libxslt',
+        'chrome_frame_strings',
+        'chrome_frame_npapi',
+        # 'npchrome_tab',
+        'xulrunner_sdk',
+      ],
+      'sources': [
+        '../base/test_suite.h',
+        'test/chrome_frame_test_utils.cc',
+        'test/chrome_frame_test_utils.h',
+        'test/chrome_frame_automation_mock.cc',
+        'test/chrome_frame_automation_mock.h',
+        'test/chrome_frame_unittests.cc',
+        'test/chrome_frame_unittests.h',
+        'test/com_message_event_unittest.cc',
+        'test/function_stub_unittest.cc',
+        'test/html_util_unittests.cc',
+        'test/http_server.cc',
+        'test/http_server.h',
+        'test/icu_stubs_unittests.cc',
+        'test/run_all_unittests.cc',
+        'test/test_server.cc',
+        'test/test_server.h',
+        'test/test_server_test.cc',
+        'test/util_unittests.cc',
+        'chrome_frame_automation.cc',
+        'chrome_tab.h',
+        'chrome_tab.idl',
+        'com_message_event.cc',
+        'html_utils.cc',
+        'sync_msg_reply_dispatcher.cc',
+        'sync_msg_reply_dispatcher.h',
+        'test_utils.cc',
+        'test_utils.h',
+        'utils.cc',
+        'utils.h',
+        'chrome_frame_histograms.h',
+        'chrome_frame_histograms.cc',
+      ],
+      'include_dirs': [
+        '<@(xul_include_directories)',
+        '../chrome/third_party/wtl/include',
+        # To allow including "chrome_tab.h"
+        '<(INTERMEDIATE_DIR)',
+      ],
+      'resource_include_dirs': [
+        '<(INTERMEDIATE_DIR)',
+      ],
+      'conditions': [
+        ['OS=="win"', {
+          'msvs_settings': {
+            'VCLinkerTool': {
+              'DelayLoadDLLs': ['xpcom.dll', 'nspr4.dll'],
+            },
+          },
+          'sources': [
+            '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_resources.rc',
+            '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_strings.rc',
+          ],
+          'dependencies': [
+            '../chrome/chrome.gyp:automation',
+            '../chrome/installer/installer.gyp:installer_util',
+            '../google_update/google_update.gyp:google_update',
+          ]
+        }],
+      ],
+    },
+    {
+      'target_name': 'chrome_frame_perftests',
+      'type': 'executable',
+      'dependencies': [
+        '../base/base.gyp:base_gfx',
+        '../base/base.gyp:test_support_base',
+        '../build/temp_gyp/googleurl.gyp:googleurl',
+        '../chrome/chrome.gyp:common',
+        '../chrome/chrome.gyp:utility',
+        '../testing/gmock.gyp:gmock',
+        '../testing/gtest.gyp:gtest',
+        '../third_party/libxml/libxml.gyp:libxml',
+        '../third_party/libxslt/libxslt.gyp:libxslt',
+        'chrome_frame_strings',
+        'xulrunner_sdk',
+      ],
+      'sources': [
+        '../base/perf_test_suite.h',
+        '../base/perftimer.cc',
+        '../base/test_file_util.h',
+        '../chrome/test/chrome_process_util.cc',
+        '../chrome/test/chrome_process_util.h',
+        '../chrome/test/ui/ui_test.cc',
+        'chrome_tab.h',
+        'chrome_tab.idl',
+        'html_utils.cc',
+        'test/perf/chrome_frame_perftest.cc',
+        'test/perf/chrome_frame_perftest.h',
+        'test/perf/run_all.cc',
+        'test/perf/silverlight.cc',
+        'test_utils.cc',
+        'test_utils.h',
+        'utils.cc',
+        'utils.h',
+      ],
+      'include_dirs': [
+        '<@(xul_include_directories)',
+        '../chrome/third_party/wtl/include',
+        # To allow including "chrome_tab.h"
+        '<(INTERMEDIATE_DIR)',
+      ],
+      'conditions': [
+        ['OS=="win"', {
+          'dependencies': [
+            '../chrome/chrome.gyp:automation',
+            '../breakpad/breakpad.gyp:breakpad_handler',
+            '../chrome/installer/installer.gyp:installer_util',
+            '../google_update/google_update.gyp:google_update',
+            '../chrome/installer/installer.gyp:installer_util',
+          ],
+          'sources': [
+            '../chrome/test/perf/mem_usage_win.cc',
+            '../chrome/test/chrome_process_util_win.cc',
+            '../base/test_file_util_win.cc',
+          ]
+        }],
+      ],
+    },
+
+    {
+      'target_name': 'chrome_frame_net_tests',
+      'type': 'executable',
+      'dependencies': [
+        '../base/base.gyp:test_support_base',
+        '../chrome/chrome.gyp:browser',
+        '../chrome/chrome.gyp:chrome_dll_version',
+        '../chrome/chrome.gyp:chrome_resources',
+        '../chrome/chrome.gyp:debugger',
+        '../chrome/chrome.gyp:renderer',
+        '../chrome/chrome.gyp:syncapi',
+        '../skia/skia.gyp:skia',
+        '../testing/gtest.gyp:gtest',
+        '../third_party/icu/icu.gyp:icui18n',
+        '../third_party/icu/icu.gyp:icuuc',
+        'chrome_frame_npapi',
+      ],
+      'sources': [
+        '../net/url_request/url_request_unittest.cc',
+        '../net/url_request/url_request_unittest.h',
+        'test/chrome_frame_test_utils.cc',
+        'test/chrome_frame_test_utils.h',
+        'test/test_server.cc',
+        'test/test_server.h',
+        'test/net/dialog_watchdog.cc',
+        'test/net/dialog_watchdog.h',
+        'test/net/fake_external_tab.cc',
+        'test/net/fake_external_tab.h',
+        'test/net/process_singleton_subclass.cc',
+        'test/net/process_singleton_subclass.h',
+        'test/net/test_automation_provider.cc',
+        'test/net/test_automation_provider.h',
+        'test/net/test_automation_resource_message_filter.cc',
+        'test/net/test_automation_resource_message_filter.h',
+      ],
+      'include_dirs': [
+        # To allow including "chrome_tab.h"
+        '<(INTERMEDIATE_DIR)',
+      ],
+      'conditions': [
+        ['OS=="win"', {
+          'dependencies': [
+            '../chrome/chrome.gyp:automation',
+            '../breakpad/breakpad.gyp:breakpad_handler',
+            '../chrome/installer/installer.gyp:installer_util',
+            '../google_update/google_update.gyp:google_update',
+            '../chrome/installer/installer.gyp:installer_util',
+          ]
+        }],
+      ],
+    },
+
+    {
+      'target_name': 'chrome_frame_npapi',
+      'type': 'static_library',
+      'dependencies': [
+        'chrome_frame_strings',
+        '../chrome/chrome.gyp:common',
+        'xulrunner_sdk',
+      ],
+      'sources': [
+        'chrome_frame_automation.cc',
+        'chrome_frame_automation.h',
+        'chrome_frame_delegate.cc',
+        'chrome_frame_delegate.h',
+        'chrome_frame_plugin.h',
+        'chrome_frame_npapi.cc',
+        'chrome_frame_npapi.h',
+        'chrome_launcher.cc',
+        'chrome_launcher.h',
+        'html_utils.cc',
+        'html_utils.h',
+        'np_browser_functions.cc',
+        'np_browser_functions.h',
+        'np_event_listener.cc',
+        'np_event_listener.h',
+        'np_proxy_service.cc',
+        'np_proxy_service.h',
+        'npapi_url_request.cc',
+        'npapi_url_request.h',
+        'ns_associate_iid_win.h',
+        'ns_isupports_impl.h',
+        'plugin_url_request.cc',
+        'plugin_url_request.h',
+        'scoped_ns_ptr_win.h',
+        'sync_msg_reply_dispatcher.cc',
+        'sync_msg_reply_dispatcher.h',
+        'utils.cc',
+        'utils.h',
+      ],
+    },
+    {
+      'target_name': 'chrome_launcher',
+      'type': 'executable',
+      'msvs_guid': 'B7E540C1-49D9-4350-ACBC-FB8306316D16',
+      'dependencies': [],
+      'sources': [
+        'chrome_launcher_main.cc',
+      ],
+      'msvs_settings': {
+        'VCLinkerTool': {
+          'OutputFile':
+              '..\\chrome\\$(ConfigurationName)\\servers\\$(ProjectName).exe',
+          # Set /SUBSYSTEM:WINDOWS since this is not a command-line program.
+          'SubSystem': '2',
+          # We're going for minimal size, so no standard library (in release
+          # builds).
+          'IgnoreAllDefaultLibraries': "true",
+        },
+        'VCCLCompilerTool': {
+          # Requires standard library, so disable it.
+          'BufferSecurityCheck': "false",
+        },
+      },
+      'configurations': {
+        # Bring back the standard library in debug buidls.
+        'Debug': {
+          'msvs_settings': {
+            'VCLinkerTool': {
+              'IgnoreAllDefaultLibraries': "false",
+            },
+          },
+        },
+      },
+    },
+    {
+      'target_name': 'chrome_frame_strings',
+      'type': 'none',
+      'rules': [
+        {
+          'rule_name': 'grit',
+          'extension': 'grd',
+          'inputs': [
+            '../tools/grit/grit.py',
+          ],
+          'variables': {
+            'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab',
+          },
+          'outputs': [
+            '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/grit/<(RULE_INPUT_ROOT).h',
+            '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/<(RULE_INPUT_ROOT).pak',
+          ],
+          'action': ['python', '<@(_inputs)', '-i', 
+            '<(RULE_INPUT_PATH)',
+            'build', '-o', '<(grit_out_dir)'
+          ],
+          'message': 'Generating resources from <(RULE_INPUT_PATH)',
+        },
+      ],
+      'sources': [
+        # Localizable resources.
+        'resources/chrome_frame_strings.grd',
+        'resources/chrome_frame_resources.grd',
+      ],
+      'direct_dependent_settings': {
+        'include_dirs': [
+          '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab',
+        ],
+      },
+      'conditions': [
+        ['OS=="win"', {
+          'dependencies': ['../build/win/system.gyp:cygwin'],
+        }],
+      ],
+    },
+    {
+      'target_name': 'npchrome_tab',
+      'type': 'shared_library',
+      'msvs_guid': 'E3DE7E63-D3B6-4A9F-BCC4-5C8169E9C9F2',
+      'dependencies': [
+        'base_noicu',
+        'chrome_frame_npapi',
+        'chrome_frame_strings',
+        'chrome_launcher',
+        'xulrunner_sdk',
+        '../chrome/chrome.gyp:common',
+        '../chrome/chrome.gyp:utility',
+        '../build/temp_gyp/googleurl.gyp:googleurl',
+        # FIXME(slightlyoff):
+        #   gigantic hack to get these to build from main Chrome sln.
+        'chrome_frame_perftests',
+        'chrome_frame_tests',
+        'chrome_frame_unittests',
+        'chrome_frame_net_tests',
+      ],
+      'sources': [
+        'bho.cc',
+        'bho.h',
+        'bho.rgs',
+        'chrome_active_document.bmp',
+        'chrome_active_document.cc',
+        'chrome_active_document.h',
+        'chrome_active_document.rgs',
+        'chrome_frame_activex.cc',
+        'chrome_frame_activex.h',
+        'chrome_frame_activex_base.h',
+        'chrome_frame_activex.rgs',
+        'chrome_frame_npapi.rgs',
+        'chrome_frame_npapi_entrypoints.cc',
+        'chrome_protocol.cc',
+        'chrome_protocol.h',
+        'chrome_protocol.rgs',
+        'chrome_tab.cc',
+        'chrome_tab.def',
+        'chrome_tab.h',
+        'chrome_tab.idl',
+        # FIXME(slightlyoff): For chrome_tab.tlb. Giant hack until we can
+        #   figure out something more gyp-ish.
+        'resources/tlb_resource.rc',
+        'chrome_tab.rgs',
+        'chrome_tab_version.rc.version',
+        'com_message_event.cc',
+        'com_message_event.h',
+        'com_type_info_holder.cc',
+        'com_type_info_holder.h',
+        'crash_report.cc',
+        'crash_report.h',
+        'ff_30_privilege_check.cc',
+        'ff_privilege_check.h',
+        'find_dialog.cc',
+        'find_dialog.h',
+        'function_stub.h',
+        'icu_stubs.cc',
+        'iids.cc',
+        'in_place_menu.h',
+        'ole_document_impl.h',
+        'protocol_sink_wrap.cc',
+        'protocol_sink_wrap.h',
+        'resource.h',
+        'script_security_manager.h',
+        'sync_msg_reply_dispatcher.cc',
+        'sync_msg_reply_dispatcher.h',
+        'extra_system_apis.h',
+        'urlmon_url_request.cc',
+        'urlmon_url_request.h',
+        'urlmon_upload_data_stream.cc',
+        'urlmon_upload_data_stream.h',
+        'vectored_handler-impl.h',
+        'vectored_handler.h',
+        'vtable_patch_manager.cc',
+        'vtable_patch_manager.h',
+        'chrome_frame_histograms.h',
+        'chrome_frame_histograms.cc',
+      ],
+      'include_dirs': [
+        # To allow including "chrome_tab.h"
+        '<(INTERMEDIATE_DIR)',
+        '<(INTERMEDIATE_DIR)/../npchrome_tab',
+      ],
+      'conditions': [
+        ['OS=="win"', {
+          # NOTE(slightlyoff):
+          #   this is a fix for the include dirs length limit on the resource
+          #   compiler, tickled by the xul_include_dirs variable
+          'resource_include_dirs': [
+            '<(INTERMEDIATE_DIR)'
+          ],
+          'sources': [
+            '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_resources.rc',
+            '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_strings.rc',
+          ],
+          'dependencies': [
+            '../breakpad/breakpad.gyp:breakpad_handler',
+            '../chrome/chrome.gyp:automation',
+            # Make the archive build happy.
+            '../chrome/chrome.gyp:syncapi',
+            # Installer
+            '../chrome/installer/installer.gyp:installer_util',
+            '../google_update/google_update.gyp:google_update',
+          ],
+          'msvs_settings': {
+            'VCLinkerTool': {
+              'OutputFile':
+                  '..\\chrome\\$(ConfigurationName)\\servers\\$(ProjectName).dll',
+              'DelayLoadDLLs': ['xpcom.dll', 'nspr4.dll'],
+              'BaseAddress': '0x33000000',
+              # Set /SUBSYSTEM:WINDOWS (for consistency).
+              'SubSystem': '2',
+            },
+          },
+        }],
+      ],
+      'rules': [
+        # Borrowed from chrome.gyp:chrome_dll_version, branding references
+        # removed
+        {
+          'rule_name': 'version',
+          'extension': 'version',
+          'variables': {
+            'version_py': '../chrome/tools/build/version.py',
+            'version_path': '../chrome/VERSION',
+            'template_input_path': 'chrome_tab_version.rc.version',
+          },
+          'inputs': [
+            '<(template_input_path)',
+            '<(version_path)',
+          ],
+          'outputs': [
+            'chrome_tab_version.rc',
+          ],
+          'action': [
+            'python',
+            '<(version_py)',
+            '-f', '<(version_path)',
+            '<(template_input_path)',
+            '<@(_outputs)',
+          ],
+          'process_outputs_as_sources': 1,
+          'message': 'Generating version information in <(_outputs)'
+        },
+      ],
+    },
+  ],
+}
+
+# vim: shiftwidth=2:et:ai:tabstop=2
diff --git a/chrome_frame/chrome_frame_activex.cc b/chrome_frame/chrome_frame_activex.cc
new file mode 100644
index 0000000..e58a9619
--- /dev/null
+++ b/chrome_frame/chrome_frame_activex.cc
@@ -0,0 +1,505 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/chrome_frame_activex.h"
+
+#include <shdeprecated.h>  // for IBrowserService2
+#include <wininet.h>
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/scoped_bstr_win.h"
+#include "base/string_util.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "googleurl/src/gurl.h"
+#include "chrome_frame/com_message_event.h"
+#include "chrome_frame/utils.h"
+
+ChromeFrameActivex::ChromeFrameActivex() {
+}
+
+HRESULT ChromeFrameActivex::FinalConstruct() {
+  HRESULT hr = Base::FinalConstruct();
+  if (FAILED(hr))
+    return hr;
+
+  // No need to call FireOnChanged at this point since nobody will be listening.
+  ready_state_ = READYSTATE_LOADING;
+  return S_OK;
+}
+
+ChromeFrameActivex::~ChromeFrameActivex() {
+  // We expect these to be released during a call to SetClientSite(NULL).
+  DCHECK(onmessage_.size() == 0);
+  DCHECK(onloaderror_.size() == 0);
+  DCHECK(onload_.size() == 0);
+  DCHECK(onreadystatechanged_.size() == 0);
+}
+
+LRESULT ChromeFrameActivex::OnCreate(UINT message, WPARAM wparam, LPARAM lparam,
+                                     BOOL& handled) {
+  Base::OnCreate(message, wparam, lparam, handled);
+  return 0;
+}
+
+void ChromeFrameActivex::OnAcceleratorPressed(int tab_handle,
+                                              const MSG& accel_message) {
+  DCHECK(m_spInPlaceSite != NULL);
+  // Allow our host a chance to handle the accelerator.
+  // This catches things like Ctrl+F, Ctrl+O etc, but not browser
+  // accelerators such as F11, Ctrl+T etc.
+  // (see AllowFrameToTranslateAccelerator for those).
+  HRESULT hr = TranslateAccelerator(const_cast<MSG*>(&accel_message));
+  if (hr != S_OK)
+    hr = AllowFrameToTranslateAccelerator(accel_message);
+
+  DLOG(INFO) << __FUNCTION__ << " browser response: "
+             << StringPrintf("0x%08x", hr);
+
+  // Last chance to handle the keystroke is to pass it to chromium.
+  // We do this last partially because there's no way for us to tell if
+  // chromium actually handled the keystroke, but also since the browser
+  // should have first dibs anyway.
+  if (hr != S_OK && automation_client_.get()) {
+    TabProxy* tab = automation_client_->tab();
+    if (tab) {
+      tab->ProcessUnhandledAccelerator(accel_message);
+    }
+  }
+}
+
+HRESULT ChromeFrameActivex::GetContainingDocument(IHTMLDocument2** doc) {
+  ScopedComPtr<IOleContainer> container;
+  HRESULT hr = m_spClientSite->GetContainer(container.Receive());
+  if (container)
+    hr = container.QueryInterface(doc);
+  return hr;
+}
+
+HRESULT ChromeFrameActivex::GetDocumentWindow(IHTMLWindow2** window) {
+  ScopedComPtr<IHTMLDocument2> document;
+  HRESULT hr = GetContainingDocument(document.Receive());
+  if (document)
+    hr = document->get_parentWindow(window);
+  return hr;
+}
+
+void ChromeFrameActivex::OnLoad(int tab_handle, const GURL& gurl) {
+  ScopedComPtr<IDispatch> event;
+  std::string url = gurl.spec();
+  if (SUCCEEDED(CreateDomEvent("event", url, "", event.Receive())))
+    Fire_onload(event);
+
+  FireEvent(onload_, url);
+
+  HRESULT hr = InvokeScriptFunction(onload_handler_, url);
+
+  if (ready_state_ < READYSTATE_COMPLETE) {
+    ready_state_ = READYSTATE_COMPLETE;
+    FireOnChanged(DISPID_READYSTATE);
+  }
+}
+
+void ChromeFrameActivex::OnLoadFailed(int error_code, const std::string& url) {
+  ScopedComPtr<IDispatch> event;
+  if (SUCCEEDED(CreateDomEvent("event", url, "", event.Receive())))
+    Fire_onloaderror(event);
+
+  FireEvent(onloaderror_, url);
+
+  HRESULT hr = InvokeScriptFunction(onerror_handler_, url);
+}
+
+void ChromeFrameActivex::OnMessageFromChromeFrame(int tab_handle,
+                                                  const std::string& message,
+                                                  const std::string& origin,
+                                                  const std::string& target) {
+  DLOG(INFO) << __FUNCTION__;
+
+  if (target.compare("*") != 0) {
+    bool drop = true;
+
+    if (is_privileged_) {
+      // Forward messages if the control is in privileged mode.
+      ScopedComPtr<IDispatch> message_event;
+      if (SUCCEEDED(CreateDomEvent("message", message, origin,
+                                   message_event.Receive()))) {
+        ScopedBstr target_bstr(UTF8ToWide(target).c_str());
+        Fire_onprivatemessage(message_event, target_bstr);
+
+        FireEvent(onprivatemessage_, message_event, target_bstr);
+      }
+    } else {
+      if (HaveSameOrigin(target, document_url_)) {
+        drop = false;
+      } else {
+        DLOG(WARNING) << "Dropping posted message since target doesn't match "
+            "the current document's origin. target=" << target;
+      }
+    }
+
+    if (drop)
+      return;
+  }
+
+  ScopedComPtr<IDispatch> message_event;
+  if (SUCCEEDED(CreateDomEvent("message", message, origin,
+                               message_event.Receive()))) {
+    Fire_onmessage(message_event);
+
+    FireEvent(onmessage_, message_event);
+
+    ScopedVariant event_var;
+    event_var.Set(static_cast<IDispatch*>(message_event));
+    InvokeScriptFunction(onmessage_handler_, event_var.AsInput());
+  }
+}
+
+void ChromeFrameActivex::OnAutomationServerLaunchFailed(
+    AutomationLaunchResult reason, const std::string& server_version) {
+  Base::OnAutomationServerLaunchFailed(reason, server_version);
+
+  if (reason == AUTOMATION_VERSION_MISMATCH) {
+    DisplayVersionMismatchWarning(m_hWnd, server_version);
+  }
+}
+
+HRESULT ChromeFrameActivex::InvokeScriptFunction(const VARIANT& script_object,
+                                                 const std::string& param) {
+  ScopedVariant script_arg(UTF8ToWide(param.c_str()).c_str());
+  return InvokeScriptFunction(script_object, script_arg.AsInput());
+}
+
+HRESULT ChromeFrameActivex::InvokeScriptFunction(const VARIANT& script_object,
+                                                 VARIANT* param) {
+  if (V_VT(&script_object) != VT_DISPATCH) {
+    return S_FALSE;
+  }
+
+  CComPtr<IDispatch> script(script_object.pdispVal);
+  HRESULT hr = script.Invoke1(static_cast<DISPID>(DISPID_VALUE), param);
+  // 0x80020101 == SCRIPT_E_REPORTED.
+  // When the script we're invoking has an error, we get this error back.
+  DLOG_IF(ERROR, FAILED(hr) && hr != 0x80020101) << "Failed to invoke script";
+
+  return hr;
+}
+
+HRESULT ChromeFrameActivex::OnDraw(ATL_DRAWINFO& draw_info) {  // NO_LINT
+  HRESULT hr = S_OK;
+  int dc_type = ::GetObjectType(draw_info.hicTargetDev);
+  if (dc_type == OBJ_ENHMETADC) {
+    RECT print_bounds = {0};
+    print_bounds.left = draw_info.prcBounds->left;
+    print_bounds.right = draw_info.prcBounds->right;
+    print_bounds.top = draw_info.prcBounds->top;
+    print_bounds.bottom = draw_info.prcBounds->bottom;
+
+    automation_client_->Print(draw_info.hdcDraw, print_bounds);
+  } else {
+    hr = Base::OnDraw(draw_info);
+  }
+
+  return hr;
+}
+
+STDMETHODIMP ChromeFrameActivex::Load(IPropertyBag* bag, IErrorLog* error_log) {
+  DCHECK(bag);
+
+  const wchar_t* event_props[] = {
+    (L"onload"),
+    (L"onloaderror"),
+    (L"onmessage"),
+    (L"onreadystatechanged"),
+  };
+
+  ScopedComPtr<IHTMLObjectElement> obj_element;
+  GetObjectElement(obj_element.Receive());
+
+  ScopedBstr object_id;
+  GetObjectScriptId(obj_element, object_id.Receive());
+
+  ScopedComPtr<IHTMLElement2> element;
+  element.QueryFrom(obj_element);
+  HRESULT hr = S_OK;
+
+  for (int i = 0; SUCCEEDED(hr) && i < arraysize(event_props); ++i) {
+    ScopedBstr prop(event_props[i]);
+    ScopedVariant value;
+    if (SUCCEEDED(bag->Read(prop, value.Receive(), error_log))) {
+      if (value.type() != VT_BSTR ||
+          FAILED(hr = CreateScriptBlockForEvent(element, object_id,
+                                                V_BSTR(&value), prop))) {
+        DLOG(ERROR) << "Failed to create script block for " << prop
+                    << StringPrintf(L"hr=0x%08X, vt=%i", hr, value.type());
+      } else {
+        DLOG(INFO) << "script block created for event " << prop <<
+            StringPrintf(" (0x%08X)", hr) << " connections: " <<
+            ProxyDIChromeFrameEvents<ChromeFrameActivex>::m_vec.GetSize();
+      }
+    } else {
+      DLOG(INFO) << "event property " << prop << " not in property bag";
+    }
+  }
+
+  ScopedVariant src;
+  if (SUCCEEDED(bag->Read(StackBstr(L"src"), src.Receive(), error_log))) {
+    if (src.type() == VT_BSTR) {
+      hr = put_src(V_BSTR(&src));
+      DCHECK(hr != E_UNEXPECTED);
+    }
+  }
+
+  ScopedVariant use_chrome_network;
+  if (SUCCEEDED(bag->Read(StackBstr(L"useChromeNetwork"),
+                          use_chrome_network.Receive(), error_log))) {
+    VariantChangeType(use_chrome_network.AsInput(),
+                      use_chrome_network.AsInput(),
+                      0, VT_BOOL);
+    if (use_chrome_network.type() == VT_BOOL) {
+      hr = put_useChromeNetwork(V_BOOL(&use_chrome_network));
+      DCHECK(hr != E_UNEXPECTED);
+    }
+  }
+
+  DLOG_IF(ERROR, FAILED(hr))
+      << StringPrintf("Failed to load property bag: 0x%08X", hr);
+
+  return hr;
+}
+
+const wchar_t g_activex_mixed_content_error[] = {
+    L"data:text/html,<html><body><b>ChromeFrame Security Error<br><br>"
+    L"Cannot navigate to HTTP url when document URL is HTTPS</body></html>"};
+    
+STDMETHODIMP ChromeFrameActivex::put_src(BSTR src) {
+  GURL document_url(GetDocumentUrl());
+  if (document_url.SchemeIsSecure()) {
+    GURL source_url(src);
+    if (!source_url.SchemeIsSecure()) {
+      Base::put_src(ScopedBstr(g_activex_mixed_content_error));
+      return E_ACCESSDENIED;
+    }
+  }
+  return Base::put_src(src);
+}
+
+HRESULT ChromeFrameActivex::IOleObject_SetClientSite(
+    IOleClientSite* client_site) {
+  HRESULT hr = Base::IOleObject_SetClientSite(client_site);
+  if (FAILED(hr) || !client_site) {
+    EventHandlers* handlers[] = {
+      &onmessage_,
+      &onloaderror_,
+      &onload_,
+      &onreadystatechanged_,
+    };
+
+    for (int i = 0; i < arraysize(handlers); ++i)
+      handlers[i]->clear();
+
+    // Drop privileged mode on uninitialization.
+    is_privileged_ = false;
+  } else {
+    ScopedComPtr<IHTMLDocument2> document;
+    GetContainingDocument(document.Receive());
+    if (document) {
+      ScopedBstr url;
+      if (SUCCEEDED(document->get_URL(url.Receive())))
+        WideToUTF8(url, url.Length(), &document_url_);
+    }
+
+    // Probe to see whether the host implements the privileged service.
+    ScopedComPtr<IChromeFramePrivileged> service;
+    HRESULT service_hr = DoQueryService(SID_ChromeFramePrivileged, client_site,
+                                service.Receive());
+    if (SUCCEEDED(service_hr) && service) {
+      // Does the host want privileged mode?
+      boolean wants_privileged = false;
+      service_hr = service->GetWantsPrivileged(&wants_privileged);
+
+      if (SUCCEEDED(service_hr) && wants_privileged)
+        is_privileged_ = true;
+    }
+
+    std::wstring chrome_extra_arguments;
+    std::wstring profile_name(GetHostProcessName(false));
+    if (is_privileged_) {
+      // Does the host want to provide extra arguments?
+      ScopedBstr extra_arguments_arg;
+      service_hr = service->GetChromeExtraArguments(
+          extra_arguments_arg.Receive());
+      if (S_OK == service_hr && extra_arguments_arg)
+        chrome_extra_arguments.assign(extra_arguments_arg,
+                                      extra_arguments_arg.Length());
+
+      ScopedBstr profile_name_arg;
+      service_hr = service->GetChromeProfileName(profile_name_arg.Receive());
+      if (S_OK == service_hr && profile_name_arg)
+        profile_name.assign(profile_name_arg, profile_name_arg.Length());
+    }
+
+    if (!InitializeAutomation(profile_name, chrome_extra_arguments,
+                              IsIEInPrivate())) {
+      return E_FAIL;
+    }
+  }
+
+  return hr;
+}
+
+HRESULT ChromeFrameActivex::GetObjectScriptId(IHTMLObjectElement* object_elem,
+                                              BSTR* id) {
+  DCHECK(object_elem != NULL);
+  DCHECK(id != NULL);
+
+  HRESULT hr = E_FAIL;
+  if (object_elem) {
+    ScopedComPtr<IHTMLElement> elem;
+    hr = elem.QueryFrom(object_elem);
+    if (elem) {
+      hr = elem->get_id(id);
+    }
+  }
+
+  return hr;
+}
+
+HRESULT ChromeFrameActivex::GetObjectElement(IHTMLObjectElement** element) {
+  DCHECK(m_spClientSite);
+  if (!m_spClientSite)
+    return E_UNEXPECTED;
+
+  ScopedComPtr<IOleControlSite> site;
+  HRESULT hr = site.QueryFrom(m_spClientSite);
+  if (site) {
+    ScopedComPtr<IDispatch> disp;
+    hr = site->GetExtendedControl(disp.Receive());
+    if (disp) {
+      hr = disp.QueryInterface(element);
+    } else {
+      DCHECK(FAILED(hr));
+    }
+  }
+
+  return hr;
+}
+
+HRESULT ChromeFrameActivex::CreateScriptBlockForEvent(
+    IHTMLElement2* insert_after, BSTR instance_id, BSTR script,
+    BSTR event_name) {
+  DCHECK(insert_after);
+  DCHECK(::SysStringLen(event_name) > 0);  // should always have this
+
+  // This might be 0 if not specified in the HTML document.
+  if (!::SysStringLen(instance_id)) {
+    // TODO(tommi): Should we give ourselves an ID if this happens?
+    NOTREACHED() << "Need to handle this";
+    return E_INVALIDARG;
+  }
+
+  ScopedComPtr<IHTMLDocument2> document;
+  HRESULT hr = GetContainingDocument(document.Receive());
+  if (SUCCEEDED(hr)) {
+    ScopedComPtr<IHTMLElement> element, new_element;
+    document->createElement(StackBstr(L"script"), element.Receive());
+    if (element) {
+      ScopedComPtr<IHTMLScriptElement> script_element;
+      if (SUCCEEDED(hr = script_element.QueryFrom(element))) {
+        script_element->put_htmlFor(instance_id);
+        script_element->put_event(event_name);
+        script_element->put_text(script);
+
+        hr = insert_after->insertAdjacentElement(StackBstr(L"afterEnd"),
+                                                 element,
+                                                 new_element.Receive());
+      }
+    }
+  }
+
+  return hr;
+}
+
+HRESULT ChromeFrameActivex::CreateDomEvent(const std::string& event_type,
+                                           const std::string& data,
+                                           const std::string& origin,
+                                           IDispatch** event) {
+  DCHECK(event_type.length() > 0);
+  DCHECK(event != NULL);
+
+  CComObject<ComMessageEvent>* ev = NULL;
+  HRESULT hr = CComObject<ComMessageEvent>::CreateInstance(&ev);
+  if (SUCCEEDED(hr)) {
+    ev->AddRef();
+
+    ScopedComPtr<IOleContainer> container;
+    m_spClientSite->GetContainer(container.Receive());
+    if (ev->Initialize(container, data, origin, event_type)) {
+      *event = ev;
+    } else {
+      NOTREACHED() << "event->Initialize";
+      ev->Release();
+      hr = E_UNEXPECTED;
+    }
+  }
+
+  return hr;
+}
+
+void ChromeFrameActivex::FireEvent(const EventHandlers& handlers,
+                                   const std::string& arg) {
+  if (handlers.size()) {
+    ScopedComPtr<IDispatch> event;
+    if (SUCCEEDED(CreateDomEvent("event", arg, "", event.Receive()))) {
+      FireEvent(handlers, event);
+    }
+  }
+}
+
+void ChromeFrameActivex::FireEvent(const EventHandlers& handlers,
+                                   IDispatch* event) {
+  DCHECK(event != NULL);
+  VARIANT arg = { VT_DISPATCH };
+  arg.pdispVal = event;
+  DISPPARAMS params = { &arg, NULL, 1, 0 };
+  for (EventHandlers::const_iterator it = handlers.begin();
+       it != handlers.end();
+       ++it) {
+    HRESULT hr = (*it)->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT,
+                               DISPATCH_METHOD, &params, NULL, NULL, NULL);
+    // 0x80020101 == SCRIPT_E_REPORTED.
+    // When the script we're invoking has an error, we get this error back.
+    DLOG_IF(ERROR, FAILED(hr) && hr != 0x80020101)
+        << StringPrintf(L"Failed to invoke script: 0x%08X", hr);
+  }
+}
+
+void ChromeFrameActivex::FireEvent(const EventHandlers& handlers,
+                                   IDispatch* event, BSTR target) {
+  DCHECK(event != NULL);
+  // Arguments in reverse order to event handler function declaration,
+  // because that's what DISPPARAMS requires.
+  VARIANT args[2] = { { VT_BSTR }, { VT_DISPATCH }, };
+  args[0].bstrVal = target;
+  args[1].pdispVal = event;
+  DISPPARAMS params = { args, NULL, arraysize(args), 0 };
+  for (EventHandlers::const_iterator it = handlers.begin();
+       it != handlers.end();
+       ++it) {
+    HRESULT hr = (*it)->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT,
+                               DISPATCH_METHOD, &params, NULL, NULL, NULL);
+    // 0x80020101 == SCRIPT_E_REPORTED.
+    // When the script we're invoking has an error, we get this error back.
+    DLOG_IF(ERROR, FAILED(hr) && hr != 0x80020101)
+        << StringPrintf(L"Failed to invoke script: 0x%08X", hr);
+  }
+}
diff --git a/chrome_frame/chrome_frame_activex.h b/chrome_frame/chrome_frame_activex.h
new file mode 100644
index 0000000..07b8122
--- /dev/null
+++ b/chrome_frame/chrome_frame_activex.h
@@ -0,0 +1,140 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_CHROME_FRAME_ACTIVEX_H_
+#define CHROME_FRAME_CHROME_FRAME_ACTIVEX_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <atlctl.h>
+
+#include <set>
+#include <string>
+
+#include "base/scoped_bstr_win.h"
+#include "base/scoped_comptr_win.h"
+#include "base/scoped_variant_win.h"
+#include "chrome_frame/chrome_frame_activex_base.h"
+#include "chrome_frame/com_type_info_holder.h"
+#include "grit/chrome_frame_resources.h"
+
+// Include without path to make GYP build see it.
+#include "chrome_tab.h"  // NOLINT
+
+// ChromeFrameActivex: Implementation of the ActiveX control that is
+// responsible for hosting a chrome frame, i.e. an iframe like widget which
+// hosts the the chrome window. This object delegates to Chrome.exe
+// (via the Chrome IPC-based automation mechanism) for the actual rendering.
+class ATL_NO_VTABLE ChromeFrameActivex
+    : public ChromeFrameActivexBase<ChromeFrameActivex, CLSID_ChromeFrame>,
+      public IObjectSafetyImpl<ChromeFrameActivex,
+                                 INTERFACESAFE_FOR_UNTRUSTED_CALLER |
+                                 INTERFACESAFE_FOR_UNTRUSTED_DATA>,
+      public IPersistPropertyBag {
+ public:
+  typedef ChromeFrameActivexBase<ChromeFrameActivex, CLSID_ChromeFrame> Base;
+  ChromeFrameActivex();
+  ~ChromeFrameActivex();
+
+DECLARE_REGISTRY_RESOURCEID(IDR_CHROMEFRAME)
+
+BEGIN_COM_MAP(ChromeFrameActivex)
+  COM_INTERFACE_ENTRY(IObjectSafety)
+  COM_INTERFACE_ENTRY(IPersistPropertyBag)
+  COM_INTERFACE_ENTRY(IConnectionPointContainer)
+  COM_INTERFACE_ENTRY_CHAIN(Base)
+END_COM_MAP()
+
+BEGIN_MSG_MAP(ChromeFrameActivex)
+  MESSAGE_HANDLER(WM_CREATE, OnCreate)
+  CHAIN_MSG_MAP(Base)
+END_MSG_MAP()
+
+  HRESULT FinalConstruct();
+
+  virtual HRESULT OnDraw(ATL_DRAWINFO& draw_info);
+
+  // IPersistPropertyBag implementation
+  STDMETHOD(GetClassID)(CLSID* class_id) {
+    if (class_id != NULL)
+      *class_id = GetObjectCLSID();
+    return S_OK;
+  }
+
+  STDMETHOD(InitNew)() {
+    return S_OK;
+  }
+
+  STDMETHOD(Load)(IPropertyBag* bag, IErrorLog* error_log);
+
+  STDMETHOD(Save)(IPropertyBag* bag, BOOL clear_dirty, BOOL save_all) {
+    return E_NOTIMPL;
+  }
+
+  // Used to setup the document_url_ member needed for completing navigation.
+  // Create external tab (possibly in incognito mode).
+  HRESULT IOleObject_SetClientSite(IOleClientSite *pClientSite);
+
+  // Overridden to perform security checks.
+  STDMETHOD(put_src)(BSTR src);
+
+ protected:
+  virtual void OnAcceleratorPressed(int tab_handle, const MSG& accel_message);
+  virtual void OnLoad(int tab_handle, const GURL& url);
+  virtual void OnMessageFromChromeFrame(int tab_handle,
+                                        const std::string& message,
+                                        const std::string& origin,
+                                        const std::string& target);
+
+ private:
+
+  LRESULT OnCreate(UINT message, WPARAM wparam, LPARAM lparam,
+                   BOOL& handled);  // NO_LINT
+
+  // ChromeFrameDelegate overrides
+  virtual void ChromeFrameActivex::OnAutomationServerLaunchFailed(
+      AutomationLaunchResult reason, const std::string& server_version);
+  virtual void OnLoadFailed(int error_code, const std::string& url);
+
+  // Helper function to execute a function on a script IDispatch interface.
+  HRESULT InvokeScriptFunction(const VARIANT& script, const std::string& param);
+  HRESULT InvokeScriptFunction(const VARIANT& script, VARIANT* param);
+  HRESULT GetContainingDocument(IHTMLDocument2** doc);
+  HRESULT GetDocumentWindow(IHTMLWindow2** window);
+
+  // Gets the value of the 'id' attribute of the object element.
+  HRESULT GetObjectScriptId(IHTMLObjectElement* object_elem, BSTR* id);
+
+  // Returns the object element in the HTML page.
+  // Note that if we're not being hosted inside an HTML
+  // document, then this call will fail.
+  HRESULT GetObjectElement(IHTMLObjectElement** element);
+
+  HRESULT CreateScriptBlockForEvent(IHTMLElement2* insert_after,
+                                    BSTR instance_id, BSTR script,
+                                    BSTR event_name);
+
+  // Creates a new event object that supports the |data| property.
+  // Note: you should supply an empty string for |origin| unless you're
+  // creating a "message" event.
+  HRESULT CreateDomEvent(const std::string& event_type, const std::string& data,
+                         const std::string& origin, IDispatch** event);
+
+  // Utility function that checks the size of the vector and if > 0 creates
+  // a variant for the string argument and forwards the call to the other
+  // FireEvent method.
+  void FireEvent(const EventHandlers& handlers, const std::string& arg);
+
+  // Invokes all registered handlers in a vector of event handlers.
+  void FireEvent(const EventHandlers& handlers, IDispatch* event);
+
+  // This variant is used for the privatemessage handler only.
+  void FireEvent(const EventHandlers& handlers, IDispatch* event,
+                 BSTR target);
+
+};
+
+OBJECT_ENTRY_AUTO(__uuidof(ChromeFrame), ChromeFrameActivex)
+
+#endif  // CHROME_FRAME_CHROME_FRAME_ACTIVEX_H_
diff --git a/chrome_frame/chrome_frame_activex.rgs b/chrome_frame/chrome_frame_activex.rgs
new file mode 100644
index 0000000..27edbcf
--- /dev/null
+++ b/chrome_frame/chrome_frame_activex.rgs
@@ -0,0 +1,79 @@
+HKLM {

+  NoRemove Software {

+    NoRemove Classes {

+      ChromeTab.ChromeFrame.1 = s 'Chrome Frame' {

+        CLSID = s '{E0A900DF-9611-4446-86BD-4B1D47E7DB2A}'

+      }

+      ChromeTab.ChromeFrame = s 'Chrome Frame' {

+        CLSID = s '{E0A900DF-9611-4446-86BD-4B1D47E7DB2A}'

+        CurVer = s 'ChromeTab.ChromeFrame.1'

+      }

+      NoRemove CLSID {

+        ForceRemove {E0A900DF-9611-4446-86BD-4B1D47E7DB2A} = s 'Chrome Frame' {

+          ProgID = s 'ChromeTab.ChromeFrame.1'

+          VersionIndependentProgID = s 'ChromeTab.ChromeFrame'

+          ForceRemove 'Programmable'

+          InprocServer32 = s '%MODULE%' {

+            val ThreadingModel = s 'Apartment'

+          }

+          val AppID = s '%APPID%'

+          ForceRemove 'Control'

+          ForceRemove 'Programmable'

+          ForceRemove 'Insertable'

+          ForceRemove 'ToolboxBitmap32' = s '%MODULE%, 1'

+          'MiscStatus' = s '0'

+          {

+            '1' = s '%OLEMISC%'

+          }

+          'TypeLib' = s '{6F2664E1-FF6E-488A-BCD1-F4CA6001DFCC}'

+          'Version' = s '1.0'

+        }

+      }

+    }

+

+    NoRemove Microsoft {

+      NoRemove Windows {

+        NoRemove CurrentVersion {

+          NoRemove Ext {

+            NoRemove PreApproved {

+              ForceRemove '{E0A900DF-9611-4446-86BD-4B1D47E7DB2A}' = s '' {

+              }

+            }

+            NoRemove Stats {

+              ForceRemove {E0A900DF-9611-4446-86BD-4B1D47E7DB2A} {

+                ForceRemove 'iexplore' {

+                  val Type = d '1'

+                  val Flags = d '4'

+                  val Count = d '0'

+                  val Time = b '%SYSTIME%'

+                  ForceRemove AllowedDomains {

+                    ForceRemove '*' {

+                    }

+                  }

+                }

+              }

+            }

+          }

+        }

+      }

+      NoRemove 'Internet Explorer' {

+        NoRemove 'Low Rights' {

+          NoRemove ElevationPolicy {

+            ForceRemove '{E0A900DF-9611-4446-86BD-4B1D47E7DB2A}' = s '' {

+              val Policy = d '3'

+              val AppName = s '%CHROME_LAUNCHER_APPNAME%'

+              val AppPath = s '%CHROME_LAUNCHER_APPPATH%'

+            }

+          }

+          NoRemove DragDrop {

+            ForceRemove '{E0A900DF-9611-4446-86BD-4B1D47E7DB2A}' = s '' {

+              val Policy = d '3'

+              val AppName = s '%CHROME_APPNAME%'

+              val AppPath = s '%CHROME_APPPATH%'

+            }

+          }

+        }

+      }

+    }

+  }

+}
\ No newline at end of file
diff --git a/chrome_frame/chrome_frame_activex_base.h b/chrome_frame/chrome_frame_activex_base.h
new file mode 100644
index 0000000..7a1993a3f
--- /dev/null
+++ b/chrome_frame/chrome_frame_activex_base.h
@@ -0,0 +1,848 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_CHROME_FRAME_ACTIVEX_BASE_H_
+#define CHROME_FRAME_CHROME_FRAME_ACTIVEX_BASE_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <atlctl.h>
+
+// Copied min/max defs from windows headers to appease atlimage.h.
+// TODO(slightlyoff): Figure out of more recent platform SDK's (> 6.1) 
+//   undo the janky "#define NOMINMAX" train wreck. See:
+// http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100703
+#ifndef max
+#define max(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+#ifndef min
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+#include <atlimage.h>
+#undef max
+#undef min
+
+#include <shdeprecated.h>  // for IBrowserService2
+#include <shlguid.h>
+
+#include <set>
+#include <string>
+
+#include "base/histogram.h"
+#include "base/scoped_bstr_win.h"
+#include "base/scoped_comptr_win.h"
+#include "base/scoped_variant_win.h"
+#include "base/string_util.h"
+#include "grit/chrome_frame_resources.h"
+#include "grit/chrome_frame_strings.h"
+#include "chrome_frame/chrome_frame_plugin.h"
+#include "chrome_frame/com_type_info_holder.h"
+#include "chrome_frame/urlmon_url_request.h"
+
+// Include without path to make GYP build see it.
+#include "chrome_tab.h"  // NOLINT
+
+// Connection point class to support firing IChromeFrameEvents (dispinterface).
+template<class T>
+class ATL_NO_VTABLE ProxyDIChromeFrameEvents
+    : public IConnectionPointImpl<T, &DIID_DIChromeFrameEvents> {
+ public:
+  void FireMethodWithParams(ChromeFrameEventDispId dispid,
+                            const VARIANT* params, size_t num_params) {
+    T* me = static_cast<T*>(this);
+    int connections = m_vec.GetSize();
+
+    for (int connection = 0; connection < connections; ++connection) {
+      me->Lock();
+      CComPtr<IUnknown> sink(m_vec.GetAt(connection));
+      me->Unlock();
+
+      DIChromeFrameEvents* events = static_cast<DIChromeFrameEvents*>(sink.p);
+      if (events) {
+        DISPPARAMS disp_params = {
+            const_cast<VARIANT*>(params),
+            NULL,
+            num_params,
+            0};
+        HRESULT hr = events->Invoke(static_cast<DISPID>(dispid),
+                                    DIID_DIChromeFrameEvents,
+                                    LOCALE_USER_DEFAULT, DISPATCH_METHOD,
+                                    &disp_params, NULL, NULL, NULL);
+        DLOG_IF(ERROR, FAILED(hr)) << "invoke(" << dispid << ") failed" <<
+            StringPrintf("0x%08X", hr);
+      }
+    }
+  }
+
+  void FireMethodWithParam(ChromeFrameEventDispId dispid,
+                           const VARIANT& param) {
+    FireMethodWithParams(dispid, &param, 1);
+  }
+
+  void Fire_onload(IDispatch* event) {
+    VARIANT var = { VT_DISPATCH };
+    var.pdispVal = event;
+    FireMethodWithParam(CF_EVENT_DISPID_ONLOAD, var);
+  }
+
+  void Fire_onloaderror(IDispatch* event) {
+    VARIANT var = { VT_DISPATCH };
+    var.pdispVal = event;
+    FireMethodWithParam(CF_EVENT_DISPID_ONLOADERROR, var);
+  }
+
+  void Fire_onmessage(IDispatch* event) {
+    VARIANT var = { VT_DISPATCH };
+    var.pdispVal = event;
+    FireMethodWithParam(CF_EVENT_DISPID_ONMESSAGE, var);
+  }
+
+  void Fire_onreadystatechanged(long readystate) {
+    VARIANT var = { VT_I4 };
+    var.lVal = readystate;
+    FireMethodWithParam(CF_EVENT_DISPID_ONREADYSTATECHANGED, var);
+  }
+
+  void Fire_onprivatemessage(IDispatch* event, BSTR target) {
+    // Arguments in reverse order to the function declaration, because
+    // that's what DISPPARAMS requires.
+    VARIANT args[2] = { { VT_BSTR, }, {VT_DISPATCH, } };
+    args[0].bstrVal = target;
+    args[1].pdispVal = event;
+
+    FireMethodWithParams(CF_EVENT_DISPID_ONPRIVATEMESSAGE,
+                         args,
+                         arraysize(args));
+  }
+};
+
+extern bool g_first_launch_by_process_;
+
+// Common implementation for ActiveX and Active Document
+template <class T, const CLSID& class_id>
+class ATL_NO_VTABLE ChromeFrameActivexBase :
+  public CComObjectRootEx<CComSingleThreadModel>,
+  public IOleControlImpl<T>,
+  public IOleObjectImpl<T>,
+  public IOleInPlaceActiveObjectImpl<T>,
+  public IViewObjectExImpl<T>,
+  public IOleInPlaceObjectWindowlessImpl<T>,
+  public ISupportErrorInfo,
+  public IQuickActivateImpl<T>,
+  public com_util::IProvideClassInfo2Impl<class_id,
+                                          DIID_DIChromeFrameEvents>,
+  public com_util::IDispatchImpl<IChromeFrame>,
+  public IConnectionPointContainerImpl<T>,
+  public ProxyDIChromeFrameEvents<T>,
+  public IPropertyNotifySinkCP<T>,
+  public CComCoClass<T, &class_id>,
+  public CComControl<T>,
+  public ChromeFramePlugin<T> {
+ protected:
+  typedef std::set<ScopedComPtr<IDispatch> > EventHandlers;
+
+ public:
+  ChromeFrameActivexBase()
+      : ready_state_(READYSTATE_UNINITIALIZED) {
+    m_bWindowOnly = TRUE;
+  }
+
+  ~ChromeFrameActivexBase() {
+  }
+
+DECLARE_OLEMISC_STATUS(OLEMISC_RECOMPOSEONRESIZE | OLEMISC_CANTLINKINSIDE |
+                       OLEMISC_INSIDEOUT | OLEMISC_ACTIVATEWHENVISIBLE |
+                       OLEMISC_SETCLIENTSITEFIRST)
+
+DECLARE_NOT_AGGREGATABLE(T)
+
+BEGIN_COM_MAP(ChromeFrameActivexBase)
+  COM_INTERFACE_ENTRY(IChromeFrame)
+  COM_INTERFACE_ENTRY(IDispatch)
+  COM_INTERFACE_ENTRY(IViewObjectEx)
+  COM_INTERFACE_ENTRY(IViewObject2)
+  COM_INTERFACE_ENTRY(IViewObject)
+  COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)
+  COM_INTERFACE_ENTRY(IOleInPlaceObject)
+  COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)
+  COM_INTERFACE_ENTRY(IOleInPlaceActiveObject)
+  COM_INTERFACE_ENTRY(IOleControl)
+  COM_INTERFACE_ENTRY(IOleObject)
+  COM_INTERFACE_ENTRY(ISupportErrorInfo)
+  COM_INTERFACE_ENTRY(IQuickActivate)
+  COM_INTERFACE_ENTRY(IProvideClassInfo)
+  COM_INTERFACE_ENTRY(IProvideClassInfo2)
+  COM_INTERFACE_ENTRY_FUNC_BLIND(0, InterfaceNotSupported)
+END_COM_MAP()
+
+BEGIN_CONNECTION_POINT_MAP(T)
+  CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink)
+  CONNECTION_POINT_ENTRY(DIID_DIChromeFrameEvents)
+END_CONNECTION_POINT_MAP()
+
+BEGIN_MSG_MAP(ChromeFrameActivexBase)
+  MESSAGE_HANDLER(WM_CREATE, OnCreate)
+  CHAIN_MSG_MAP(ChromeFramePlugin<T>)
+  CHAIN_MSG_MAP(CComControl<T>)
+  DEFAULT_REFLECTION_HANDLER()
+END_MSG_MAP()
+
+  // IViewObjectEx
+  DECLARE_VIEW_STATUS(VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE)
+
+  inline HRESULT IViewObject_Draw(DWORD draw_aspect, LONG index,

+      void* aspect_info, DVTARGETDEVICE* ptd, HDC info_dc, HDC dc,

+      LPCRECTL bounds, LPCRECTL win_bounds) {

+    // ATL ASSERTs if dwDrawAspect is DVASPECT_DOCPRINT, so we cheat.

+    DWORD aspect = draw_aspect;

+    if (aspect == DVASPECT_DOCPRINT)

+      aspect = DVASPECT_CONTENT;

+

+    return CComControl<T>::IViewObject_Draw(aspect, index, aspect_info, ptd,

+        info_dc, dc, bounds, win_bounds);

+  }

+
+  DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+  HRESULT FinalConstruct() {
+    if (!Initialize())
+      return E_OUTOFMEMORY;
+
+    // Set to true if this is the first launch by this process.
+    // Used to perform one time tasks.
+    if (g_first_launch_by_process_) {
+      g_first_launch_by_process_ = false;
+      UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.IEVersion",
+                                  GetIEVersion(),
+                                  IE_INVALID,
+                                  IE_8,
+                                  IE_8 + 1);
+    }
+    return S_OK;
+  }
+
+  void FinalRelease() {
+  }
+
+  static HRESULT WINAPI InterfaceNotSupported(void* pv, REFIID riid, void** ppv,
+                                              DWORD dw) {
+#ifndef NDEBUG
+    wchar_t buffer[64] = {0};
+    ::StringFromGUID2(riid, buffer, arraysize(buffer));
+    DLOG(INFO) << "E_NOINTERFACE: " << buffer;
+#endif
+    return E_NOINTERFACE;
+  }
+
+  // ISupportsErrorInfo
+  STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid) {
+    static const IID* interfaces[] = {
+      &IID_IChromeFrame,
+      &IID_IDispatch
+    };
+
+    for (int i = 0; i < arraysize(interfaces); ++i) {
+      if (InlineIsEqualGUID(*interfaces[i], riid))
+        return S_OK;
+    }
+    return S_FALSE;
+  }
+
+  // Called to draw our control when chrome hasn't been initialized.
+  virtual HRESULT OnDraw(ATL_DRAWINFO& draw_info) {  // NO_LINT
+    if (NULL == draw_info.prcBounds) {
+      NOTREACHED();
+      return E_FAIL;
+    }
+    // Don't draw anything.
+    return S_OK;
+  }
+
+
+  // Used to setup the document_url_ member needed for completing navigation.
+  // Create external tab (possibly in incognito mode).
+  HRESULT IOleObject_SetClientSite(IOleClientSite* client_site) {
+    // If we currently have a document site pointer, release it.
+    doc_site_.Release();
+    if (client_site) {
+      doc_site_.QueryFrom(client_site);
+    }
+
+    return CComControlBase::IOleObject_SetClientSite(client_site);
+  }
+
+  bool HandleContextMenuCommand(UINT cmd) {
+    if (cmd == IDC_ABOUT_CHROME_FRAME) {
+      int tab_handle = automation_client_->tab()->handle();
+      OnOpenURL(tab_handle, GURL("about:version"), NEW_WINDOW);
+      return true;
+    }
+
+    return false;
+  }
+
+  // Should connections initiated by this class try to block 
+  // responses served with the X-Frame-Options header? 
+  // ActiveX controls genereally will want to do this, 
+  // returning true, while true top-level documents 
+  // (ActiveDocument servers) will not. Your specialization 
+  // of this template should implement this method based on how 
+  // it "feels" from a security perspective. If it's hosted in another 
+  // scriptable document, return true, else false.
+  virtual bool is_frame_busting_enabled() const {
+    return true;
+  }
+
+ protected:
+  virtual void OnTabbedOut(int tab_handle, bool reverse) {
+    DCHECK(m_bInPlaceActive);
+
+    HWND parent = ::GetParent(m_hWnd);
+    ::SetFocus(parent);
+    ScopedComPtr<IOleControlSite> control_site;
+    control_site.QueryFrom(m_spClientSite);
+    if (control_site)
+      control_site->OnFocus(FALSE);
+  }
+
+  virtual void OnOpenURL(int tab_handle, const GURL& url_to_open,
+                         int open_disposition) {
+    ScopedComPtr<IWebBrowser2> web_browser2;
+    DoQueryService(SID_SWebBrowserApp, m_spClientSite, web_browser2.Receive());
+    DCHECK(web_browser2);
+
+    ScopedVariant url;
+    // Check to see if the URL uses a "view-source:" prefix, if so, open it
+    // using chrome frame full tab mode by using 'cf:' protocol handler.
+    // Also change the disposition to NEW_WINDOW since IE6 doesn't have tabs.
+    if (url_to_open.has_scheme() && (url_to_open.SchemeIs("view-source") ||
+                                     url_to_open.SchemeIs("about"))) {
+      std::string chrome_url;
+      chrome_url.append("cf:");
+      chrome_url.append(url_to_open.spec());
+      url.Set(UTF8ToWide(chrome_url).c_str());
+      open_disposition = NEW_WINDOW;
+    } else {
+      url.Set(UTF8ToWide(url_to_open.spec()).c_str());
+    }
+
+    VARIANT flags = { VT_I4 };
+    V_I4(&flags) = 0;
+
+    IEVersion ie_version = GetIEVersion();
+    DCHECK(ie_version != NON_IE && ie_version != IE_UNSUPPORTED);
+    // Since IE6 doesn't support tabs, so we just use window instead.
+    if (ie_version == IE_6) {
+      if (open_disposition == NEW_FOREGROUND_TAB ||
+          open_disposition == NEW_BACKGROUND_TAB ||
+          open_disposition == NEW_WINDOW) {
+        V_I4(&flags) = navOpenInNewWindow;
+      } else if (open_disposition != CURRENT_TAB) {
+        NOTREACHED() << "Unsupported open disposition in IE6";
+      }
+    } else {
+      switch (open_disposition) {
+        case NEW_FOREGROUND_TAB:
+          V_I4(&flags) = navOpenInNewTab;
+          break;
+        case NEW_BACKGROUND_TAB:
+          V_I4(&flags) = navOpenInBackgroundTab;
+          break;
+        case NEW_WINDOW:
+          V_I4(&flags) = navOpenInNewWindow;
+          break;
+        default:
+          break;
+      }
+    }
+
+    // TODO(sanjeevr): The navOpenInNewWindow flag causes IE to open this
+    // in a new window ONLY if the user has specified
+    // "Always open popups in a new window". Otherwise it opens in a new tab.
+    // We need to investigate more and see if we can force IE to display the
+    // link in a new window. MSHTML uses the below code to force an open in a
+    // new window. But this logic also fails for us. Perhaps this flag is not
+    // honoured if the ActiveDoc is not MSHTML.
+    // Even the HLNF_DISABLEWINDOWRESTRICTIONS flag did not work.
+    // Start of MSHTML-like logic.
+    // CComQIPtr<ITargetFramePriv2> target_frame = web_browser2;
+    // if (target_frame) {
+    //   CComPtr<IUri> uri;
+    //   CreateUri(UTF8ToWide(open_url_command->url_.spec()).c_str(),
+    //             Uri_CREATE_IE_SETTINGS, 0, &uri);
+    //   CComPtr<IBindCtx> bind_ctx;
+    //   CreateBindCtx(0, &bind_ctx);
+    //   target_frame->AggregatedNavigation2(
+    //       HLNF_TRUSTFIRSTDOWNLOAD|HLNF_OPENINNEWWINDOW, bind_ctx, NULL,
+    //       L"No_Name", uri, L"");
+    // }
+    // End of MSHTML-like logic
+    VARIANT empty = ScopedVariant::kEmptyVariant;
+    web_browser2->Navigate2(url.AsInput(), &flags, &empty, &empty, &empty);
+    web_browser2->put_Visible(VARIANT_TRUE);
+  }
+
+  virtual void OnRequestStart(int tab_handle, int request_id,
+                              const IPC::AutomationURLRequest& request_info) {
+    scoped_refptr<CComObject<UrlmonUrlRequest> > request;
+    if (base_url_request_.get() &&
+        GURL(base_url_request_->url()) == GURL(request_info.url)) {
+      request.swap(base_url_request_);
+    } else {
+      CComObject<UrlmonUrlRequest>* new_request = NULL;
+      CComObject<UrlmonUrlRequest>::CreateInstance(&new_request);
+      request = new_request;
+    }
+
+    DCHECK(request.get() != NULL);
+
+    if (request->Initialize(automation_client_.get(), tab_handle, request_id,
+                            request_info.url, request_info.method,
+                            request_info.referrer,
+                            request_info.extra_request_headers,
+                            request_info.upload_data.get(),
+                            static_cast<T*>(this)->is_frame_busting_enabled())) {
+      // If Start is successful, it will add a self reference.
+      request->Start();
+      request->set_parent_window(m_hWnd);
+    }
+  }
+
+  virtual void OnRequestRead(int tab_handle, int request_id,
+                             int bytes_to_read) {
+    automation_client_->ReadRequest(request_id, bytes_to_read);
+  }
+
+  virtual void OnRequestEnd(int tab_handle, int request_id,
+                            const URLRequestStatus& status) {
+    automation_client_->RemoveRequest(request_id, status.status(), true);
+  }
+
+  virtual void OnSetCookieAsync(int tab_handle, const GURL& url,
+                                const std::string& cookie) {
+    std::string name;
+    std::string data;
+    size_t name_end = cookie.find('=');
+    if (std::string::npos != name_end) {
+      name = cookie.substr(0, name_end);
+      data = cookie.substr(name_end + 1);
+    } else {
+      data = cookie;
+    }
+
+    BOOL ret = InternetSetCookieA(url.spec().c_str(), name.c_str(),
+                                  data.c_str());
+    DCHECK(ret) << "InternetSetcookie failed. Error: " << GetLastError();
+  }
+
+  virtual void OnAttachExternalTab(int tab_handle,
+                                   intptr_t cookie,
+                                   int disposition) {
+    std::string url;
+    url = StringPrintf("cf:attach_external_tab&%d&%d",
+                       cookie, disposition);
+    OnOpenURL(tab_handle, GURL(url), disposition);
+  }
+
+  LRESULT OnCreate(UINT message, WPARAM wparam, LPARAM lparam,
+                   BOOL& handled) {  // NO_LINT
+    ModifyStyle(0, WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0);
+    automation_client_->SetParentWindow(m_hWnd);
+    // Only fire the 'interactive' ready state if we aren't there already.
+    if (ready_state_ < READYSTATE_INTERACTIVE) {
+      ready_state_ = READYSTATE_INTERACTIVE;
+      FireOnChanged(DISPID_READYSTATE);
+    }
+    return 0;
+  }
+
+  // ChromeFrameDelegate override
+  virtual void OnAutomationServerReady() {
+    ChromeFramePlugin<T>::OnAutomationServerReady();
+
+    ready_state_ = READYSTATE_COMPLETE;
+    FireOnChanged(DISPID_READYSTATE);
+  }
+
+  // ChromeFrameDelegate override
+  virtual void OnAutomationServerLaunchFailed(
+      AutomationLaunchResult reason, const std::string& server_version) {
+    ready_state_ = READYSTATE_UNINITIALIZED;
+    FireOnChanged(DISPID_READYSTATE);
+  }
+
+  // Overridden to take advantage of readystate prop changes and send those
+  // to potential listeners.
+  HRESULT FireOnChanged(DISPID dispid) {
+    if (dispid == DISPID_READYSTATE) {
+      Fire_onreadystatechanged(ready_state_);
+    }
+    return __super::FireOnChanged(dispid);
+  }
+
+  // IChromeFrame
+  // Property getter/setters for the src attribute, which contains a URL.
+  // The ChromeFrameActivex control initiates navigation to this URL
+  // when instantiated.
+  STDMETHOD(get_src)(BSTR* src) {
+    if (NULL == src) {
+      return E_POINTER;
+    }
+
+    *src = SysAllocString(url_);
+    return S_OK;
+  }
+
+  STDMETHOD(put_src)(BSTR src) {
+    if (src == NULL)
+      return E_INVALIDARG;
+
+    // Switch the src to UTF8 and try to expand to full URL
+    std::string src_utf8;
+    WideToUTF8(src, SysStringLen(src), &src_utf8);
+    std::string full_url = ResolveURL(GetDocumentUrl(), src_utf8);
+    if (full_url.empty()) {
+      return E_INVALIDARG;
+    }
+
+    // We can initiate navigation here even if ready_state is not complete.
+    // We do not have to set proxy, and AutomationClient will take care
+    // of navigation just after CreateExternalTab is done.
+    if (!automation_client_->InitiateNavigation(full_url)) {
+      // TODO(robertshield): Make InitiateNavigation return more useful
+      // error information.
+      return E_INVALIDARG;
+    }
+
+    // Save full URL in BSTR member
+    url_.Reset(::SysAllocString(UTF8ToWide(full_url).c_str()));
+
+    return S_OK;
+  }
+
+  STDMETHOD(get_onload)(VARIANT* onload_handler) {
+    if (NULL == onload_handler)
+      return E_INVALIDARG;
+
+    *onload_handler = onload_handler_.Copy();
+
+    return S_OK;
+  }
+
+  // Property setter for the onload attribute, which contains a
+  // javascript function to be invoked on successful navigation.
+  STDMETHOD(put_onload)(VARIANT onload_handler) {
+    if (V_VT(&onload_handler) != VT_DISPATCH) {
+      DLOG(WARNING) << "Invalid onload handler type: "
+                    << onload_handler.vt
+                    << " specified";
+      return E_INVALIDARG;
+    }
+
+    onload_handler_ = onload_handler;
+
+    return S_OK;
+  }
+
+  // Property getter/setters for the onloaderror attribute, which contains a
+  // javascript function to be invoked on navigation failure.
+  STDMETHOD(get_onloaderror)(VARIANT* onerror_handler) {
+    if (NULL == onerror_handler)
+      return E_INVALIDARG;
+
+    *onerror_handler = onerror_handler_.Copy();
+
+    return S_OK;
+  }
+
+  STDMETHOD(put_onloaderror)(VARIANT onerror_handler) {
+    if (V_VT(&onerror_handler) != VT_DISPATCH) {
+      DLOG(WARNING) << "Invalid onloaderror handler type: "
+                    << onerror_handler.vt
+                    << " specified";
+      return E_INVALIDARG;
+    }
+
+    onerror_handler_ = onerror_handler;
+
+    return S_OK;
+  }
+
+  // Property getter/setters for the onmessage attribute, which contains a
+  // javascript function to be invoked when we receive a message from the
+  // chrome frame.
+  STDMETHOD(put_onmessage)(VARIANT onmessage_handler) {
+    if (V_VT(&onmessage_handler) != VT_DISPATCH) {
+      DLOG(WARNING) << "Invalid onmessage handler type: "
+                    << onmessage_handler.vt
+                    << " specified";
+      return E_INVALIDARG;
+    }
+
+    onmessage_handler_ = onmessage_handler;
+
+    return S_OK;
+  }
+
+  STDMETHOD(get_onmessage)(VARIANT* onmessage_handler) {
+    if (NULL == onmessage_handler)
+      return E_INVALIDARG;
+
+    *onmessage_handler = onmessage_handler_.Copy();
+
+    return S_OK;
+  }
+
+  STDMETHOD(get_readyState)(long* ready_state) {  // NOLINT
+    DLOG(INFO) << __FUNCTION__;
+    DCHECK(ready_state);
+
+    if (!ready_state)
+      return E_INVALIDARG;
+
+    *ready_state = ready_state_;
+
+    return S_OK;
+  }
+
+  // Property getter/setters for use_chrome_network flag. This flag
+  // indicates if chrome network stack is to be used for fetching
+  // network requests.
+  STDMETHOD(get_useChromeNetwork)(VARIANT_BOOL* use_chrome_network) {
+    if (!use_chrome_network)
+      return E_INVALIDARG;
+
+    *use_chrome_network =
+        automation_client_->use_chrome_network() ? VARIANT_TRUE : VARIANT_FALSE;
+    return S_OK;
+  }
+
+  STDMETHOD(put_useChromeNetwork)(VARIANT_BOOL use_chrome_network) {
+    if (!is_privileged_) {
+      DLOG(ERROR) << "Attempt to set useChromeNetwork in non-privileged mode";
+      return E_ACCESSDENIED;
+    }
+
+    automation_client_->set_use_chrome_network(
+        (VARIANT_FALSE != use_chrome_network));
+    return S_OK;
+  }
+
+  // Posts a message to the chrome frame.
+  STDMETHOD(postMessage)(BSTR message, VARIANT target) {
+    if (NULL == message) {
+      return E_INVALIDARG;
+    }
+
+    if (!automation_client_.get())
+      return E_FAIL;
+
+    std::string utf8_target;
+    if (target.vt == VT_BSTR) {
+      int len = ::SysStringLen(target.bstrVal);
+      if (len == 1 && target.bstrVal[0] == L'*') {
+        utf8_target = "*";
+      } else {
+        GURL resolved(target.bstrVal);
+        if (!resolved.is_valid()) {
+          Error(L"Unable to parse the specified target URL.");
+          return E_INVALIDARG;
+        }
+
+        utf8_target = resolved.spec();
+      }
+    } else {
+      utf8_target = "*";
+    }
+
+    std::string utf8_message;
+    WideToUTF8(message, ::SysStringLen(message), &utf8_message);
+
+    GURL url(GURL(document_url_).GetOrigin());
+    std::string origin(url.is_empty() ? "null" : url.spec());
+    if (!automation_client_->ForwardMessageFromExternalHost(utf8_message,
+                                                            origin,
+                                                            utf8_target)) {
+      Error(L"Failed to post message to chrome frame");
+      return E_FAIL;
+    }
+
+    return S_OK;
+  }
+
+  STDMETHOD(addEventListener)(BSTR event_type, IDispatch* listener,
+                              VARIANT use_capture) {
+    EventHandlers* handlers = NULL;
+    HRESULT hr = GetHandlersForEvent(event_type, &handlers);
+    if (FAILED(hr))
+      return hr;
+
+    DCHECK(handlers != NULL);
+
+    handlers->insert(ScopedComPtr<IDispatch>(listener));
+
+    return hr;
+  }
+
+  STDMETHOD(removeEventListener)(BSTR event_type, IDispatch* listener,
+                                 VARIANT use_capture) {
+    EventHandlers* handlers = NULL;
+    HRESULT hr = GetHandlersForEvent(event_type, &handlers);
+    if (FAILED(hr))
+      return hr;
+
+    DCHECK(handlers != NULL);
+    std::remove(handlers->begin(), handlers->end(), listener);
+
+    return hr;
+  }
+
+  STDMETHOD(get_version)(BSTR* version) {
+    if (!automation_client_.get()) {
+      NOTREACHED();
+      return E_FAIL;
+    }
+
+    if (version == NULL) {
+      return E_INVALIDARG;
+    }
+
+    *version = SysAllocString(automation_client_->GetVersion().c_str());
+    return S_OK;
+  }
+
+  STDMETHOD(postPrivateMessage)(BSTR message, BSTR origin, BSTR target) {
+    if (NULL == message)
+      return E_INVALIDARG;
+
+    if (!is_privileged_) {
+      DLOG(ERROR) << "Attempt to postPrivateMessage in non-privileged mode";
+      return E_ACCESSDENIED;
+    }
+
+    DCHECK(automation_client_.get());
+    std::string utf8_message, utf8_origin, utf8_target;
+    WideToUTF8(message, ::SysStringLen(message), &utf8_message);
+    WideToUTF8(origin, ::SysStringLen(origin), &utf8_origin);
+    WideToUTF8(target, ::SysStringLen(target), &utf8_target);
+
+    if (!automation_client_->ForwardMessageFromExternalHost(utf8_message,
+                                                            utf8_origin,
+                                                            utf8_target)) {
+      Error(L"Failed to post message to chrome frame");
+      return E_FAIL;
+    }
+
+    return S_OK;
+  }
+
+  // Returns the vector of event handlers for a given event (e.g. "load").
+  // If the event type isn't recognized, the function fills in a descriptive
+  // error (IErrorInfo) and returns E_INVALIDARG.
+  HRESULT GetHandlersForEvent(BSTR event_type, EventHandlers** handlers) {
+    DCHECK(handlers != NULL);
+
+    HRESULT hr = S_OK;
+    const wchar_t* event_type_end = event_type + ::SysStringLen(event_type);
+    if (LowerCaseEqualsASCII(event_type, event_type_end, "message")) {
+      *handlers = &onmessage_;
+    } else if (LowerCaseEqualsASCII(event_type, event_type_end, "load")) {
+      *handlers = &onload_;
+    } else if (LowerCaseEqualsASCII(event_type, event_type_end, "loaderror")) {
+      *handlers = &onloaderror_;
+    } else if (LowerCaseEqualsASCII(event_type, event_type_end,
+                                    "readystatechanged")) {
+      *handlers = &onreadystatechanged_;
+    } else if (LowerCaseEqualsASCII(event_type, event_type_end,
+                                   "privatemessage")) {
+      // This event handler is only available in privileged mode.
+      if (!is_privileged_) {
+        Error("Event type 'privatemessage' is privileged");
+        return E_ACCESSDENIED;
+      }
+      *handlers = &onprivatemessage_;
+    } else {
+      Error(StringPrintf("Event type '%ls' not found", event_type).c_str());
+      hr = E_INVALIDARG;
+    }
+
+    return hr;
+  }
+
+  // Gives the browser a chance to handle an accelerator that was
+  // sent to the out of proc chromium instance.
+  // Returns S_OK iff the accelerator was handled by the browser.
+  HRESULT AllowFrameToTranslateAccelerator(const MSG& msg) {
+    // Although IBrowserService2 is officially deprecated, it's still alive
+    // and well in IE7 and earlier.  We have to use it here to correctly give
+    // the browser a chance to handle keyboard shortcuts.
+    // This happens automatically for activex components that have windows that
+    // belong to the current thread.  In that circumstance IE owns the message
+    // loop and can walk the line of components allowing each participant the
+    // chance to handle the keystroke and eventually falls back to
+    // v_MayTranslateAccelerator.  However in our case, the message loop is
+    // owned by the out-of-proc chromium instance so IE doesn't have a chance to
+    // fall back on its default behavior.  Instead we give IE a chance to
+    // handle the shortcut here.
+
+    HRESULT hr = S_FALSE;
+    ScopedComPtr<IBrowserService2> bs2;
+    if (S_OK == DoQueryService(SID_STopLevelBrowser, m_spInPlaceSite,
+                               bs2.Receive())) {
+      hr = bs2->v_MayTranslateAccelerator(const_cast<MSG*>(&msg));
+    } else {
+      // IE8 doesn't support IBrowserService2 unless you enable a special,
+      // undocumented flag with CoInternetSetFeatureEnabled and even then,
+      // the object you get back implements only a couple of methods of
+      // that interface... all the other entries in the vtable are NULL.
+      // In addition, the class that implements it is called
+      // ImpostorBrowserService2 :)
+      // IE8 does have a new interface though, presumably called
+      // ITabBrowserService or something that can be abbreviated to TBS.
+      // That interface has a method, TranslateAcceleratorTBS that does
+      // call the root MayTranslateAccelerator function, but alas the
+      // first argument to MayTranslateAccelerator is hard coded to FALSE
+      // which means that global accelerators are not handled and we're
+      // out of luck.
+      // A third thing that's notable with regards to IE8 is that
+      // none of the *MayTranslate* functions exist in a vtable inside
+      // ieframe.dll.  I checked this by scanning for the address of
+      // those functions inside the dll and found none, which means that
+      // all calls to those functions are relative.
+      // So, for IE8, our approach is very simple.  Just post the message
+      // to our parent window and IE will pick it up if it's an
+      // accelerator. We won't know for sure if the browser handled the
+      // keystroke or not.
+      ::PostMessage(::GetParent(m_hWnd), msg.message, msg.wParam,
+                    msg.lParam);
+    }
+
+    return hr;
+  }
+
+ protected:
+  ScopedBstr url_;
+  ScopedComPtr<IOleDocumentSite> doc_site_;
+
+  // For more information on the ready_state_ property see:
+  // http://msdn.microsoft.com/en-us/library/aa768179(VS.85).aspx#
+  READYSTATE ready_state_;
+
+  // The following members contain IDispatch interfaces representing the
+  // onload/onerror/onmessage handlers on the page.
+  ScopedVariant onload_handler_;
+  ScopedVariant onerror_handler_;
+  ScopedVariant onmessage_handler_;
+
+  EventHandlers onmessage_;
+  EventHandlers onloaderror_;
+  EventHandlers onload_;
+  EventHandlers onreadystatechanged_;
+  EventHandlers onprivatemessage_;
+
+  // The UrlmonUrlRequest instance instantiated for downloading the base URL.
+  scoped_refptr<CComObject<UrlmonUrlRequest> > base_url_request_;
+};
+
+#endif  // CHROME_FRAME_CHROME_FRAME_ACTIVEX_BASE_H_
diff --git a/chrome_frame/chrome_frame_automation.cc b/chrome_frame/chrome_frame_automation.cc
new file mode 100644
index 0000000..9da5e43
--- /dev/null
+++ b/chrome_frame/chrome_frame_automation.cc
@@ -0,0 +1,975 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/chrome_frame_automation.h"
+
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/file_version_info.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/singleton.h"
+#include "base/string_util.h"
+#include "base/sys_info.h"
+#include "chrome/app/client_util.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "chrome_frame/chrome_launcher.h"
+#include "chrome_frame/utils.h"
+#include "chrome_frame/sync_msg_reply_dispatcher.h"
+
+#ifdef NDEBUG
+int64 kAutomationServerReasonableLaunchDelay = 1000;  // in milliseconds
+#else
+int64 kAutomationServerReasonableLaunchDelay = 1000 * 10;
+#endif
+
+int kDefaultSendUMADataInterval = 20000; // in milliseconds.
+
+static const wchar_t kUmaSendIntervalValue[] = L"UmaSendInterval";
+
+class TabProxyNotificationMessageFilter
+    : public IPC::ChannelProxy::MessageFilter {
+ public:
+  explicit TabProxyNotificationMessageFilter(AutomationHandleTracker* tracker)
+      : tracker_(tracker) {
+  }
+
+  virtual bool OnMessageReceived(const IPC::Message& message) {
+    if (message.is_reply())
+      return false;
+
+    int tab_handle = 0;
+    if (!ChromeFrameDelegateImpl::IsTabMessage(message, &tab_handle))
+      return false;
+
+    // Get AddRef-ed pointer to corresponding TabProxy object
+    TabProxy* tab = static_cast<TabProxy*>(tracker_->GetResource(tab_handle));
+    if (tab) {
+      tab->OnMessageReceived(message);
+      tab->Release();
+    }
+    return true;
+  }
+
+
+ private:
+  AutomationHandleTracker* tracker_;
+};
+
+class ChromeFrameAutomationProxyImpl::CFMsgDispatcher
+    : public SyncMessageReplyDispatcher {
+ public:
+  CFMsgDispatcher() : SyncMessageReplyDispatcher() {}
+ protected:
+  virtual bool HandleMessageType(const IPC::Message& msg,
+                                 const MessageSent& origin) {
+    switch (origin.type) {
+      case AutomationMsg_CreateExternalTab::ID:
+      case AutomationMsg_ConnectExternalTab::ID:
+        InvokeCallback<Tuple3<HWND, HWND, int> >(msg, origin);
+        break;
+      case AutomationMsg_NavigateInExternalTab::ID:
+        InvokeCallback<Tuple1<AutomationMsg_NavigationResponseValues> >(msg,
+              origin);
+        break;
+      default:
+        NOTREACHED();
+    }
+    return true;
+  }
+};
+
+ChromeFrameAutomationProxyImpl::ChromeFrameAutomationProxyImpl(
+    int launch_timeout)
+    : AutomationProxy(launch_timeout) {
+  sync_ = new CFMsgDispatcher();
+  // Order of filters is not important.
+  channel_->AddFilter(new TabProxyNotificationMessageFilter(tracker_.get()));
+  channel_->AddFilter(sync_.get());
+}
+
+ChromeFrameAutomationProxyImpl::~ChromeFrameAutomationProxyImpl() {
+}
+
+void ChromeFrameAutomationProxyImpl::SendAsAsync(IPC::SyncMessage* msg,
+                                                 void* callback, void* key) {
+  sync_->Push(msg, callback, key);
+  channel_->ChannelProxy::Send(msg);
+}
+
+void ChromeFrameAutomationProxyImpl::CancelAsync(void* key) {
+  sync_->Cancel(key);
+}
+
+scoped_refptr<TabProxy> ChromeFrameAutomationProxyImpl::CreateTabProxy(
+    int handle) {
+  DCHECK(tracker_->GetResource(handle) == NULL);
+  return new TabProxy(this, tracker_.get(), handle);
+}
+
+struct LaunchTimeStats {
+#ifndef NDEBUG
+  LaunchTimeStats() {
+    launch_time_begin_ = base::Time::Now();
+  }
+
+  void Dump() {
+    base::TimeDelta launch_time = base::Time::Now() - launch_time_begin_;
+    HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchTime", launch_time);
+    const int64 launch_milliseconds = launch_time.InMilliseconds();
+    if (launch_milliseconds > kAutomationServerReasonableLaunchDelay) {
+      LOG(WARNING) << "Automation server launch took longer than expected: " <<
+          launch_milliseconds << " ms.";
+    }
+  }
+
+  base::Time launch_time_begin_;
+#else
+  void Dump() {}
+#endif
+};
+
+
+ProxyFactory::ProxyCacheEntry::ProxyCacheEntry(const std::wstring& profile)
+    : proxy(NULL), profile_name(profile), ref_count(1),
+    launch_result(AutomationLaunchResult(-1)) {
+  thread.reset(new base::Thread(WideToASCII(profile_name).c_str()));
+  thread->Start();
+}
+
+template <> struct RunnableMethodTraits<ProxyFactory> {
+  static void RetainCallee(ProxyFactory* obj) {}
+  static void ReleaseCallee(ProxyFactory* obj) {}
+};
+
+ProxyFactory::ProxyFactory()
+    : uma_send_interval_(0) {
+  uma_send_interval_ = GetConfigInt(kDefaultSendUMADataInterval,
+                                    kUmaSendIntervalValue);
+}
+
+ProxyFactory::~ProxyFactory() {
+  DCHECK_EQ(proxies_.container().size(), 0);
+}
+
+void* ProxyFactory::GetAutomationServer(int launch_timeout,
+    const std::wstring& profile_name,
+    const std::wstring& extra_argument,
+    bool perform_version_check,
+    LaunchDelegate* delegate) {
+  ProxyCacheEntry* entry = NULL;
+  // Find already existing launcher thread for given profile
+  AutoLock lock(lock_);
+  for (size_t i = 0; i < proxies_.container().size(); ++i) {
+    if (!lstrcmpiW(proxies_[i]->profile_name.c_str(), profile_name.c_str())) {
+      entry = proxies_[i];
+      DCHECK(entry->thread.get() != NULL);
+      break;
+    }
+  }
+
+  if (entry == NULL) {
+    entry = new ProxyCacheEntry(profile_name);
+    proxies_.container().push_back(entry);
+  } else {
+    entry->ref_count++;
+  }
+
+
+  // Note we always queue request to the launch thread, even if we already
+  // have established proxy object. A simple lock around entry->proxy = proxy
+  // would allow calling LaunchDelegate directly from here if
+  // entry->proxy != NULL. Drawback is that callback may be invoked either in
+  // main thread or in background thread, which may confuse the client.
+  entry->thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
+      &ProxyFactory::CreateProxy, entry,
+      launch_timeout, extra_argument,
+      perform_version_check, delegate));
+
+  entry->thread->message_loop()->PostDelayedTask(FROM_HERE,
+      NewRunnableMethod(this, &ProxyFactory::SendUMAData, entry),
+      uma_send_interval_);
+
+  return entry;
+}
+
+void ProxyFactory::CreateProxy(ProxyFactory::ProxyCacheEntry* entry,
+                               int launch_timeout,
+                               const std::wstring& extra_chrome_arguments,
+                               bool perform_version_check,
+                               LaunchDelegate* delegate) {
+  DCHECK(entry->thread->thread_id() == PlatformThread::CurrentId());
+  if (entry->proxy) {
+    delegate->LaunchComplete(entry->proxy, entry->launch_result);
+    return;
+  }
+
+  // We *must* create automationproxy in a thread that has message loop,
+  // since SyncChannel::Context construction registers event to be watched
+  // through ObjectWatcher which subscribes for the current thread message loop
+  // destruction notification.
+
+  // At same time we must destroy/stop the thread from another thread.
+  ChromeFrameAutomationProxyImpl* proxy =
+      new ChromeFrameAutomationProxyImpl(launch_timeout);
+
+  // Launch browser
+  scoped_ptr<CommandLine> command_line(
+      chrome_launcher::CreateLaunchCommandLine());
+  command_line->AppendSwitchWithValue(switches::kAutomationClientChannelID,
+      ASCIIToWide(proxy->channel_id()));
+
+  // The metrics bug out because they attempt to use URLFetcher with a
+  // null URLRequestContext::default_request_context_. Turn them off for now.
+  // TODO(robertshield): Figure out why this is. It appears to have something
+  // to do with an improperly set up profile...
+  command_line->AppendSwitch(switches::kDisableMetrics);
+
+  // Chrome Frame never wants Chrome to start up with a First Run UI.
+  command_line->AppendSwitch(switches::kNoFirstRun);
+
+  // Place the profile directory in
+  // "<chrome_exe_path>\..\User Data\<profile-name>"
+  if (!entry->profile_name.empty()) {
+    std::wstring profile_path;
+    if (GetUserProfileBaseDirectory(&profile_path)) {
+      file_util::AppendToPath(&profile_path, entry->profile_name);
+      command_line->AppendSwitchWithValue(switches::kUserDataDir,
+          profile_path);
+    } else {
+      // Can't get the profile dir :-( We need one to work, so fail.
+      // We have no code for launch failure.
+      entry->launch_result = AutomationLaunchResult(-1);
+    }
+  }
+
+  std::wstring command_line_string(command_line->command_line_string());
+  // If there are any extra arguments, append them to the command line.
+  if (!extra_chrome_arguments.empty()) {
+    command_line_string += L' ' + extra_chrome_arguments;
+  }
+
+  automation_server_launch_start_time_ = base::TimeTicks::Now();
+
+  if (!base::LaunchApp(command_line_string, false, false, NULL)) {
+    // We have no code for launch failure.
+    entry->launch_result = AutomationLaunchResult(-1);
+  } else {
+    // Launch timeout may happen if the new instance tries to communicate
+    // with an existing Chrome instance that is hung and displays msgbox
+    // asking to kill the previous one. This could be easily observed if the
+    // already running Chrome instance is running as high-integrity process
+    // (started with "Run as Administrator" or launched by another high
+    // integrity process) hence our medium-integrity process
+    // cannot SendMessage to it with request to activate itself.
+
+    // TODO(stoyan) AutomationProxy eats Hello message, hence installing
+    // message filter is pointless, we can leverage ObjectWatcher and use
+    // system thread pool to notify us when proxy->AppLaunch event is signaled.
+    LaunchTimeStats launch_stats;
+    // Wait for the automation server launch result, then stash away the
+    // version string it reported.
+    entry->launch_result = proxy->WaitForAppLaunch();
+    launch_stats.Dump();
+
+    base::TimeDelta delta =
+        base::TimeTicks::Now() - automation_server_launch_start_time_;
+
+    if (entry->launch_result == AUTOMATION_SUCCESS) {
+      UMA_HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchSuccessTime",
+                          delta);
+    } else {
+      UMA_HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchFailedTime",
+                          delta);
+    }
+
+    UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.LaunchResult",
+                                entry->launch_result,
+                                AUTOMATION_SUCCESS,
+                                AUTOMATION_CREATE_TAB_FAILED,
+                                AUTOMATION_CREATE_TAB_FAILED + 1);
+  }
+
+  // Finally set the proxy.
+  entry->proxy = proxy;
+  delegate->LaunchComplete(proxy, entry->launch_result);
+}
+
+bool ProxyFactory::ReleaseAutomationServer(void* server_id) {
+  DLOG(INFO) << __FUNCTION__;
+
+  if (!server_id) {
+    NOTREACHED();
+    return false;
+  }
+
+  ProxyCacheEntry* entry = reinterpret_cast<ProxyCacheEntry*>(server_id);
+
+  lock_.Acquire();
+  Vector::ContainerType::iterator it = std::find(proxies_.container().begin(),
+                                                 proxies_.container().end(),
+                                                 entry);
+  DCHECK(it != proxies_.container().end());
+  DCHECK(entry->thread->thread_id() != PlatformThread::CurrentId());
+  if (--entry->ref_count == 0) {
+    proxies_.container().erase(it);
+  }
+
+  lock_.Release();
+
+  // Destroy it.
+  if (entry->ref_count == 0) {
+    entry->thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
+        &ProxyFactory::DestroyProxy, entry));
+    // Wait until thread exits
+    entry->thread.reset();
+    DCHECK(entry->proxy == NULL);
+    delete entry;
+  }
+
+  return true;
+}
+
+void ProxyFactory::DestroyProxy(ProxyCacheEntry* entry) {
+  DCHECK(entry->thread->thread_id() == PlatformThread::CurrentId());
+  // Send pending UMA data if any.
+  SendUMAData(entry);
+  delete entry->proxy;
+  entry->proxy = NULL;
+}
+
+Singleton<ProxyFactory> g_proxy_factory;
+
+void ProxyFactory::SendUMAData(ProxyCacheEntry* proxy_entry) {
+  if (!proxy_entry) {
+    NOTREACHED() << __FUNCTION__ << " Invalid proxy entry";
+    return;
+  }
+
+  DCHECK(proxy_entry->thread->thread_id() == PlatformThread::CurrentId());
+
+  if (proxy_entry->proxy) {
+    ChromeFrameHistogramSnapshots::HistogramPickledList histograms =
+        chrome_frame_histograms_.GatherAllHistograms();
+
+    if (!histograms.empty()) {
+      proxy_entry->proxy->Send(
+          new AutomationMsg_RecordHistograms(0, histograms));
+    }
+  } else {
+    DLOG(INFO) << __FUNCTION__ << " No proxy available to service the request";
+    return;
+  }
+
+  MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod(
+      this, &ProxyFactory::SendUMAData, proxy_entry), uma_send_interval_);
+}
+
+template <> struct RunnableMethodTraits<ChromeFrameAutomationClient> {
+  static void RetainCallee(ChromeFrameAutomationClient* obj) {}
+  static void ReleaseCallee(ChromeFrameAutomationClient* obj) {}
+};
+
+ChromeFrameAutomationClient::ChromeFrameAutomationClient()
+    : chrome_frame_delegate_(NULL),
+      chrome_window_(NULL),
+      tab_window_(NULL),
+      parent_window_(NULL),
+      automation_server_(NULL),
+      automation_server_id_(NULL),
+      ui_thread_id_(NULL),
+      incognito_(false),
+      init_state_(UNINITIALIZED),
+      use_chrome_network_(false),
+      proxy_factory_(g_proxy_factory.get()),
+      handle_top_level_requests_(false),
+      tab_handle_(-1),
+      external_tab_cookie_(NULL) {
+}
+
+ChromeFrameAutomationClient::~ChromeFrameAutomationClient() {
+  // Uninitialize must be called prior to the destructor
+  DCHECK(automation_server_ == NULL);
+}
+
+bool ChromeFrameAutomationClient::Initialize(
+    ChromeFrameDelegate* chrome_frame_delegate,
+    int automation_server_launch_timeout,
+    bool perform_version_check,
+    const std::wstring& profile_name,
+    const std::wstring& extra_chrome_arguments,
+    bool incognito) {
+  DCHECK(!IsWindow());
+  chrome_frame_delegate_ = chrome_frame_delegate;
+  incognito_ = incognito;
+  ui_thread_id_ = PlatformThread::CurrentId();
+
+#ifndef NDEBUG
+  // In debug mode give more time to work with a debugger.
+  if (automation_server_launch_timeout != INFINITE)
+    automation_server_launch_timeout *= 2;
+#endif  // NDEBUG
+
+  // Create a window on the UI thread for marshaling messages back and forth
+  // from the IPC thread. This window cannot be a message only window as the
+  // external chrome tab window is created as a child of this window. This
+  // window is eventually reparented to the ActiveX/NPAPI plugin window.
+  if (!Create(GetDesktopWindow(), NULL, NULL,
+              WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
+              WS_EX_TOOLWINDOW)) {
+    NOTREACHED();
+    return false;
+  }
+
+  // Mark our state as initializing.  We'll reach initialized once
+  // InitializeComplete is called successfully.
+  init_state_ = INITIALIZING;
+
+  automation_server_id_ = proxy_factory_->GetAutomationServer(
+      automation_server_launch_timeout,
+      profile_name, extra_chrome_arguments, perform_version_check,
+      static_cast<ProxyFactory::LaunchDelegate*>(this));
+
+  return true;
+}
+
+void ChromeFrameAutomationClient::Uninitialize() {
+  DLOG(INFO) << __FUNCTION__;
+
+  init_state_ = UNINITIALIZING;
+
+  // Called from client's FinalRelease() / destructor
+  // ChromeFrameAutomationClient may wait for the initialization (launch)
+  // to complete while Uninitialize is called.
+  // We either have to:
+  // 1) Make ReleaseAutomationServer blocking call (wait until thread exits)
+  // 2) Behave like a COM object i.e. increase module lock count.
+  // Otherwise the DLL may get unloaded while we have running threads.
+  // Unfortunately in NPAPI case we cannot increase module lock count, hence
+  // we stick with blocking/waiting
+  if (tab_.get()) {
+    tab_->RemoveObserver(this);
+    tab_ = NULL;    // scoped_refptr::Release
+  }
+
+  // Clean up any outstanding requests
+  CleanupRequests();
+
+  // Wait for the background thread to exit.
+  ReleaseAutomationServer();
+
+  // We must destroy the window, since if there are pending tasks
+  // window procedure may be invoked after DLL is unloaded.
+  // Unfortunately pending tasks are leaked.
+  if (m_hWnd)
+    DestroyWindow();
+
+  chrome_frame_delegate_ = NULL;
+  init_state_ = UNINITIALIZED;
+}
+
+bool ChromeFrameAutomationClient::InitiateNavigation(const std::string& url) {
+  if (url.empty())
+    return false;
+
+  url_ = GURL(url);
+
+  // Catch invalid URLs early.
+  if (!url_.is_valid()) {
+    DLOG(ERROR) << "Invalid URL passed to InitiateNavigation: " << url;
+    return false;
+  }
+
+  if (is_initialized()) {
+    BeginNavigate(GURL(url));
+  }
+
+  return true;
+}
+
+bool ChromeFrameAutomationClient::NavigateToIndex(int index) {
+  // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
+  if (!automation_server_ || !tab_.get() || !tab_->is_valid()) {
+    return false;
+  }
+
+  DCHECK(::IsWindow(chrome_window_));
+
+  IPC::SyncMessage* msg = new AutomationMsg_NavigateExternalTabAtIndex(
+      0, tab_->handle(), index, NULL);
+  automation_server_->SendAsAsync(msg, NewCallback(this,
+      &ChromeFrameAutomationClient::BeginNavigateCompleted), this);
+  return true;
+}
+
+bool ChromeFrameAutomationClient::ForwardMessageFromExternalHost(
+    const std::string& message, const std::string& origin,
+    const std::string& target) {
+  // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
+  if (!is_initialized())
+    return false;
+
+  tab_->HandleMessageFromExternalHost(message, origin, target);
+  return true;
+}
+
+bool ChromeFrameAutomationClient::SetProxySettings(
+    const std::string& json_encoded_proxy_settings) {
+  if (!is_initialized())
+    return false;
+  automation_server_->SendProxyConfig(json_encoded_proxy_settings);
+  return true;
+}
+
+void ChromeFrameAutomationClient::BeginNavigate(const GURL& url) {
+  // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
+  if (!automation_server_ || !tab_.get()) {
+    DLOG(WARNING) << "BeginNavigate - can't navigate.";
+    ReportNavigationError(AUTOMATION_MSG_NAVIGATION_ERROR, url_.spec());
+    return;
+  }
+
+  DCHECK(::IsWindow(chrome_window_));
+
+  if (!tab_->is_valid()) {
+    DLOG(WARNING) << "BeginNavigate - tab isn't valid.";
+    return;
+  }
+
+  IPC::SyncMessage* msg =
+      new AutomationMsg_NavigateInExternalTab(0, tab_->handle(), url, NULL);
+  automation_server_->SendAsAsync(msg, NewCallback(this,
+      &ChromeFrameAutomationClient::BeginNavigateCompleted), this);
+
+  RECT client_rect = {0};
+  chrome_frame_delegate_->GetBounds(&client_rect);
+  Resize(client_rect.right - client_rect.left,
+         client_rect.bottom - client_rect.top,
+         SWP_NOACTIVATE | SWP_NOZORDER);
+}
+
+void ChromeFrameAutomationClient::BeginNavigateCompleted(
+    AutomationMsg_NavigationResponseValues result) {
+  if (result == AUTOMATION_MSG_NAVIGATION_ERROR)
+     ReportNavigationError(AUTOMATION_MSG_NAVIGATION_ERROR, url_.spec());
+}
+
+void ChromeFrameAutomationClient::FindInPage(const std::wstring& search_string,
+                                             FindInPageDirection forward,
+                                             FindInPageCase match_case,
+                                             bool find_next) {
+  DCHECK(tab_.get());
+
+  // What follows is quite similar to TabProxy::FindInPage() but uses
+  // the SyncMessageReplyDispatcher to avoid concerns about blocking
+  // synchronous messages.
+  AutomationMsg_Find_Params params;
+  params.unused = 0;
+  params.search_string = WideToUTF16Hack(search_string);
+  params.find_next = find_next;
+  params.match_case = (match_case == CASE_SENSITIVE);
+  params.forward = (forward == FWD);
+
+  IPC::SyncMessage* msg =
+      new AutomationMsg_Find(0, tab_->handle(), params, NULL, NULL);
+  automation_server_->SendAsAsync(msg, NULL, this);
+}
+
+void ChromeFrameAutomationClient::CreateExternalTab() {
+  AutomationLaunchResult launch_result = AUTOMATION_SUCCESS;
+  DCHECK(IsWindow());
+  DCHECK(automation_server_ != NULL);
+
+  const IPC::ExternalTabSettings settings = {
+    m_hWnd,
+    gfx::Rect(),
+    WS_CHILD,
+    incognito_,
+    !use_chrome_network_,
+    handle_top_level_requests_,
+    GURL(url_)
+  };
+
+  UMA_HISTOGRAM_CUSTOM_COUNTS(
+      "ChromeFrame.HostNetworking", !use_chrome_network_, 0, 1, 2);
+
+  UMA_HISTOGRAM_CUSTOM_COUNTS(
+      "ChromeFrame.HandleTopLevelRequests", handle_top_level_requests_, 0, 1, 
+      2);
+
+  IPC::SyncMessage* message =
+      new AutomationMsg_CreateExternalTab(0, settings, NULL, NULL, NULL);
+  automation_server_->SendAsAsync(message, NewCallback(this,
+      &ChromeFrameAutomationClient::CreateExternalTabComplete), this);
+}
+
+void ChromeFrameAutomationClient::CreateExternalTabComplete(HWND chrome_window,
+    HWND tab_window, int tab_handle) {
+  if (!automation_server_) {
+    // If we receive this notification while shutting down, do nothing.
+    DLOG(ERROR) << "CreateExternalTabComplete called when automation server "
+                << "was null!";
+    return;
+  }
+
+  AutomationLaunchResult launch_result = AUTOMATION_SUCCESS;
+  if (tab_handle == 0 || !::IsWindow(chrome_window) ||
+      !::IsWindow(chrome_window)) {
+    launch_result = AUTOMATION_CREATE_TAB_FAILED;
+  } else {
+    chrome_window_ = chrome_window;
+    tab_window_ = tab_window;
+    tab_ = automation_server_->CreateTabProxy(tab_handle);
+    tab_->AddObserver(this);
+    tab_handle_ = tab_handle;
+  }
+
+  PostTask(FROM_HERE, NewRunnableMethod(this,
+      &ChromeFrameAutomationClient::InitializeComplete, launch_result));
+}
+
+void ChromeFrameAutomationClient::SetEnableExtensionAutomation(
+    bool enable_automation) {
+  if (!is_initialized())
+    return;
+
+  automation_server_->SetEnableExtensionAutomation(enable_automation);
+}
+
+// Invoked in launch background thread.
+void ChromeFrameAutomationClient::LaunchComplete(
+    ChromeFrameAutomationProxy* proxy,
+    AutomationLaunchResult result) {
+  // If we're shutting down we don't keep a pointer to the automation server.
+  if (init_state_ != UNINITIALIZING) {
+    DCHECK(init_state_ == INITIALIZING);
+    automation_server_ = proxy;
+  } else {
+    DLOG(INFO) << "Not storing automation server pointer due to shutting down";
+  }
+
+  if (result == AUTOMATION_SUCCESS) {
+    // NOTE: A potential problem here is that Uninitialize() may just have
+    // been called so we need to be careful and check the automation_server_
+    // pointer.
+    if (automation_server_ != NULL) {
+      // If we have a valid tab_handle here it means that we are attaching to
+      // an existing ExternalTabContainer instance, in which case we don't
+      // want to create an external tab instance in Chrome.
+      if (external_tab_cookie_ == NULL) {
+        // Continue with Initialization - Create external tab
+        CreateExternalTab();
+      } else {
+        // Send a notification to Chrome that we are ready to connect to the
+        // ExternalTab.
+        IPC::SyncMessage* message =
+            new AutomationMsg_ConnectExternalTab(0, external_tab_cookie_, NULL,
+                                                 NULL, NULL);
+        automation_server_->SendAsAsync(message, NewCallback(this,
+            &ChromeFrameAutomationClient::CreateExternalTabComplete), this);
+      }
+    }
+  } else {
+    // Launch failed. Note, we cannot delete proxy here.
+    PostTask(FROM_HERE, NewRunnableMethod(this,
+        &ChromeFrameAutomationClient::InitializeComplete, result));
+  }
+}
+
+void ChromeFrameAutomationClient::InitializeComplete(
+    AutomationLaunchResult result) {
+  DCHECK(PlatformThread::CurrentId() == ui_thread_id_);
+  std::string version = automation_server_->server_version();
+
+  if (result != AUTOMATION_SUCCESS) {
+    DLOG(WARNING) << "InitializeComplete: failure " << result;
+    ReleaseAutomationServer();
+  } else {
+    init_state_ = INITIALIZED;
+
+    // If the host already have a window, ask Chrome to re-parent.
+    if (parent_window_)
+      SetParentWindow(parent_window_);
+  }
+
+  if (chrome_frame_delegate_) {
+    if (result == AUTOMATION_SUCCESS) {
+      chrome_frame_delegate_->OnAutomationServerReady();
+    } else {
+      chrome_frame_delegate_->OnAutomationServerLaunchFailed(result, version);
+    }
+  }
+}
+
+// This is invoked in channel's background thread.
+// Cannot call any method of the activex/npapi here since they are STA
+// kind of beings.
+// By default we marshal the IPC message to the main/GUI thread and from there
+// we safely invoke chrome_frame_delegate_->OnMessageReceived(msg).
+void ChromeFrameAutomationClient::OnMessageReceived(TabProxy* tab,
+                                                    const IPC::Message& msg) {
+  DCHECK(tab == tab_.get());
+
+  // Early check to avoid needless marshaling
+  if (chrome_frame_delegate_ == NULL)
+    return;
+
+  CallDelegate(FROM_HERE, NewRunnableMethod(chrome_frame_delegate_,
+      &ChromeFrameDelegate::OnMessageReceived, msg));
+}
+
+void ChromeFrameAutomationClient::ReportNavigationError(
+    AutomationMsg_NavigationResponseValues error_code,
+    const std::string& url) {
+  CallDelegate(FROM_HERE, NewRunnableMethod(chrome_frame_delegate_,
+      &ChromeFrameDelegate::OnLoadFailed,
+      error_code,
+      url));
+}
+
+void ChromeFrameAutomationClient::Resize(int width, int height,
+                                         int flags) {
+  if (tab_.get() && ::IsWindow(chrome_window())) {
+    SetWindowPos(HWND_TOP, 0, 0, width, height, flags);
+    tab_->Reposition(chrome_window(), HWND_TOP, 0, 0, width, height,
+                     flags, m_hWnd);
+  }
+}
+
+void ChromeFrameAutomationClient::SetParentWindow(HWND parent_window) {
+  parent_window_ = parent_window;
+  // If we're done with the initialization step, go ahead
+  if (is_initialized()) {
+    if (parent_window == NULL) {
+      // Hide and reparent the automation window. This window will get
+      // reparented to the new ActiveX/Active document window when it gets
+      // created.
+      ShowWindow(SW_HIDE);
+      SetParent(GetDesktopWindow());
+    } else {
+      if (!::IsWindow(chrome_window())) {
+        DLOG(WARNING) << "Invalid Chrome Window handle in SetParentWindow";
+        return;
+      }
+
+      if (!SetParent(parent_window)) {
+        NOTREACHED();
+        DLOG(WARNING) << "Failed to set parent window for automation window. "
+                      << "Error = "
+                      << GetLastError();
+        return;
+      }
+
+      RECT parent_client_rect = {0};
+      ::GetClientRect(parent_window, &parent_client_rect);
+      int width = parent_client_rect.right - parent_client_rect.left;
+      int height = parent_client_rect.bottom - parent_client_rect.top;
+
+      Resize(width, height, SWP_SHOWWINDOW | SWP_NOZORDER);
+    }
+  }
+}
+
+void ChromeFrameAutomationClient::ReleaseAutomationServer() {
+  DLOG(INFO) << __FUNCTION__;
+  if (automation_server_id_) {
+    // Cache the server id and clear the automation_server_id_ before 
+    // calling ReleaseAutomationServer.  The reason we do this is that
+    // we must cancel pending messages before we release the automation server.
+    // Furthermore, while ReleaseAutomationServer is running, we could get
+    // a callback to LaunchComplete which is where we normally get our pointer
+    // to the automation server and there we check the server id for NULLness
+    // and do nothing if it is NULL.
+    void* server_id = automation_server_id_;
+    automation_server_id_ = NULL;
+
+    if (automation_server_) {
+      // Make sure to clean up any pending sync messages before we go away.
+      automation_server_->CancelAsync(this);
+      automation_server_ = NULL;
+    }
+
+    proxy_factory_->ReleaseAutomationServer(server_id);
+
+    // automation_server_ must not have been set to non NULL.
+    // (if this regresses, start by looking at LaunchComplete()).
+    DCHECK(automation_server_ == NULL);
+  } else {
+    DCHECK(automation_server_ == NULL);
+  }
+}
+
+void ChromeFrameAutomationClient::SendContextMenuCommandToChromeFrame(
+  int selected_command) {
+  DCHECK(tab_ != NULL);
+  tab_->SendContextMenuCommand(selected_command);
+}
+
+std::wstring ChromeFrameAutomationClient::GetVersion() const {
+  static FileVersionInfo* version_info =
+      FileVersionInfo::CreateFileVersionInfoForCurrentModule();
+
+  std::wstring version;
+  if (version_info)
+    version = version_info->product_version();
+
+  return version;
+}
+
+void ChromeFrameAutomationClient::CallDelegate(
+    const tracked_objects::Location& from_here, Task* delegate_task ) {
+  delegate_task->SetBirthPlace(from_here);
+  PostTask(FROM_HERE, NewRunnableMethod(this,
+      &ChromeFrameAutomationClient::CallDelegateImpl,
+      delegate_task));
+}
+
+void ChromeFrameAutomationClient::CallDelegateImpl(Task* delegate_task) {
+  if (chrome_frame_delegate_) {
+    // task's object should be == chrome_frame_delegate_
+    delegate_task->Run();
+  }
+
+  delete delegate_task;
+}
+
+void ChromeFrameAutomationClient::Print(HDC print_dc,
+                                        const RECT& print_bounds) {
+  if (!tab_window_) {
+    NOTREACHED();
+    return;
+  }
+
+  HDC window_dc = ::GetDC(tab_window_);
+
+  BitBlt(print_dc, print_bounds.left, print_bounds.top,
+         print_bounds.right - print_bounds.left,
+         print_bounds.bottom - print_bounds.top,
+         window_dc, print_bounds.left, print_bounds.top,
+         SRCCOPY);
+
+  ::ReleaseDC(tab_window_, window_dc);
+}
+
+void ChromeFrameAutomationClient::PrintTab() {
+  tab_->PrintAsync();
+}
+
+// IPC:Message::Sender implementation
+bool ChromeFrameAutomationClient::Send(IPC::Message* msg) {
+  return automation_server_->Send(msg);
+}
+
+bool ChromeFrameAutomationClient::AddRequest(PluginUrlRequest* request) {
+  if (!request) {
+    NOTREACHED();
+    return false;
+  }
+
+  DCHECK(request_map_.end() == request_map_.find(request->id()));
+  request_map_[request->id()] =  request;
+  return true;
+}
+
+bool ChromeFrameAutomationClient::ReadRequest(
+    int request_id, int bytes_to_read) {
+  bool result = false;
+  PluginUrlRequest* request = LookupRequest(request_id);
+  if (request)
+    result = request->Read(bytes_to_read);
+
+  return result;
+}
+
+void ChromeFrameAutomationClient::RemoveRequest(PluginUrlRequest* request) {
+  DCHECK(request_map_.end() != request_map_.find(request->id()));
+  request_map_.erase(request->id());
+}
+
+void ChromeFrameAutomationClient::RemoveRequest(
+    int request_id, int reason, bool abort) {
+  PluginUrlRequest* request = LookupRequest(request_id);
+  if (request) {
+    if (abort) {
+      request->Stop();
+      DCHECK(request_map_.end() == request_map_.find(request_id));
+    } else {
+      request_map_.erase(request_id);
+    }
+  }
+}
+
+PluginUrlRequest* ChromeFrameAutomationClient::LookupRequest(
+    int request_id) const {
+  PluginUrlRequest* request = NULL;
+  RequestMap::const_iterator it = request_map_.find(request_id);
+  if (request_map_.end() != it)
+    request = (*it).second;
+  return request;
+}
+
+bool ChromeFrameAutomationClient::IsValidRequest(
+    PluginUrlRequest* request) const {
+  bool is_valid = false;
+  // if request is invalid then request->id() won't work
+  // hence perform reverse map lookup for validity of the
+  // request pointer.
+  if (request) {
+    for (RequestMap::const_iterator it = request_map_.begin();
+        it != request_map_.end(); it++) {
+      if (request == (*it).second) {
+        is_valid = true;
+        break;
+      }
+    }
+  }
+
+  return is_valid;
+}
+
+void ChromeFrameAutomationClient::CleanupRequests() {
+  while (request_map_.size()) {
+    PluginUrlRequest* request = request_map_.begin()->second;
+    if (request) {
+      int request_id = request->id();
+      request->Stop();
+      DCHECK(request_map_.end() == request_map_.find(request_id));
+    }
+  }
+
+  DCHECK(request_map_.empty());
+  request_map_.clear();
+}
+
+bool ChromeFrameAutomationClient::Reinitialize(
+    ChromeFrameDelegate* delegate) {
+  if (!tab_.get() || !::IsWindow(chrome_window_)) {
+    NOTREACHED();
+    DLOG(WARNING) << "ChromeFrameAutomationClient instance reused "
+                  << "with invalid tab";
+    return false;
+  }
+
+  if (!delegate) {
+    NOTREACHED();
+    return false;
+  }
+
+  chrome_frame_delegate_ = delegate;
+  SetParentWindow(NULL);
+  return true;
+}
+
+void ChromeFrameAutomationClient::AttachExternalTab(
+    intptr_t external_tab_cookie) {
+  DCHECK(tab_.get() == NULL);
+  DCHECK(tab_handle_ == -1);
+
+  external_tab_cookie_ = external_tab_cookie;
+}
diff --git a/chrome_frame/chrome_frame_automation.h b/chrome_frame/chrome_frame_automation.h
new file mode 100644
index 0000000..796facb
--- /dev/null
+++ b/chrome_frame/chrome_frame_automation.h
@@ -0,0 +1,356 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_CHROME_FRAME_AUTOMATION_H_
+#define CHROME_FRAME_CHROME_FRAME_AUTOMATION_H_
+
+#include <atlbase.h>
+#include <atlwin.h>
+#include <string>
+#include <map>
+
+#include "base/lock.h"
+#include "base/ref_counted.h"
+#include "base/scoped_handle.h"
+#include "base/stack_container.h"
+#include "base/task.h"
+#include "base/timer.h"
+#include "base/thread.h"
+#include "chrome/test/automation/automation_proxy.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "chrome_frame/chrome_frame_delegate.h"
+#include "chrome_frame/chrome_frame_histograms.h"
+#include "chrome_frame/plugin_url_request.h"
+
+const unsigned long kCommandExecutionTimeout = 4000;  // NOLINT, 4 seconds
+
+class ProxyFactory;
+
+struct DECLSPEC_NOVTABLE ChromeFrameAutomationProxy {
+  virtual bool Send(IPC::Message* msg) = 0;
+
+  virtual void SendAsAsync(IPC::SyncMessage* msg, void* callback,
+                           void* key) = 0;
+  virtual void CancelAsync(void* key) = 0;
+  virtual scoped_refptr<TabProxy> CreateTabProxy(int handle) = 0;
+  virtual std::string server_version() = 0;
+
+  virtual void SendProxyConfig(const std::string&) = 0;
+  virtual void SetEnableExtensionAutomation(bool enable) = 0;
+ protected:
+  ~ChromeFrameAutomationProxy() {}
+};
+
+// We extend the AutomationProxy class to handle our custom
+// IPC messages
+class ChromeFrameAutomationProxyImpl : public ChromeFrameAutomationProxy,
+  // We have to derive from automationproxy since we want access to some members
+  // (tracker_ & channel_) - simple aggregation wont work;
+  // .. and non-public inheritance is verboten.
+  public AutomationProxy {
+ public:
+  virtual void SendAsAsync(IPC::SyncMessage* msg, void* callback, void* key);
+
+  virtual void CancelAsync(void* key);
+
+  virtual scoped_refptr<TabProxy> CreateTabProxy(int handle);
+  virtual std::string server_version() {
+    return AutomationProxy::server_version();
+  }
+
+
+  virtual bool Send(IPC::Message* msg) {
+    return AutomationProxy::Send(msg);
+  }
+
+  virtual void SendProxyConfig(const std::string& p) {
+    AutomationProxy::SendProxyConfig(p);
+  }
+
+  virtual void SetEnableExtensionAutomation(bool e) {
+    AutomationProxy::SetEnableExtensionAutomation(e);
+  }
+
+ protected:
+  explicit ChromeFrameAutomationProxyImpl(int launch_timeout);
+  ~ChromeFrameAutomationProxyImpl();
+  class CFMsgDispatcher;
+  scoped_refptr<CFMsgDispatcher> sync_;
+  friend class ProxyFactory;
+};
+
+// We must create and destroy automation proxy in a thread with a message loop.
+// Hence thread cannot be a member of the proxy.
+class ProxyFactory {
+ public:
+  // Callback when chrome process launch is complete and automation handshake
+  // (Hello message) is established.
+  struct DECLSPEC_NOVTABLE LaunchDelegate {
+    virtual void LaunchComplete(ChromeFrameAutomationProxy* proxy,
+                                AutomationLaunchResult result) = 0;
+  };
+
+  ProxyFactory();
+  ~ProxyFactory();
+  // FIXME: we should pass the result as output parameter, not as return value
+  // since, LaunchDelegate can be invoked before this function returns.
+  virtual void* GetAutomationServer(int launch_timeout,
+                                    const std::wstring& profile_name,
+                            // Extra command line argument when launching Chrome
+                                    const std::wstring& extra_argument,
+                                    bool perform_version_check,
+                                    LaunchDelegate* delegate);
+  virtual bool ReleaseAutomationServer(void* server_id);
+
+ private:
+  struct ProxyCacheEntry {
+    std::wstring profile_name;
+    int ref_count;
+    scoped_ptr<base::Thread> thread;
+    ChromeFrameAutomationProxyImpl* proxy;
+    AutomationLaunchResult launch_result;
+    explicit ProxyCacheEntry(const std::wstring& profile);
+  };
+
+  void CreateProxy(ProxyCacheEntry* entry,
+                   int launch_timeout,
+                   const std::wstring& extra_chrome_arguments,
+                   bool perform_version_check,
+                   LaunchDelegate* delegate);
+  void DestroyProxy(ProxyCacheEntry* entry);
+
+  void SendUMAData(ProxyCacheEntry* proxy_entry);
+
+  typedef StackVector<ProxyCacheEntry*, 4> Vector;
+  Vector proxies_;
+  // Lock if we are going to call GetAutomationServer from more than one thread.
+  Lock lock_;
+
+  // Used for UMA histogram logging to measure the time for the chrome
+  // automation server to start;
+  base::TimeTicks automation_server_launch_start_time_;
+
+  // Gathers histograms to be sent to Chrome.
+  ChromeFrameHistogramSnapshots chrome_frame_histograms_;
+
+  // Interval for sending UMA data
+  int uma_send_interval_;
+};
+
+// T is expected to be something CWindowImpl derived, or at least to have
+// PostMessage(UINT, WPARAM) method. Do not forget to CHAIN_MSG_MAP
+template <class T> class TaskMarshallerThroughWindowsMessages {
+ public:
+  void PostTask(const tracked_objects::Location& from_here, Task* task) {
+    task->SetBirthPlace(from_here);
+    T* this_ptr = static_cast<T*>(this);
+    if (this_ptr->IsWindow()) {
+      this_ptr->PostMessage(MSG_EXECUTE_TASK, reinterpret_cast<WPARAM>(task));
+    } else {
+      DLOG(INFO) << "Dropping MSG_EXECUTE_TASK message for destroyed window.";
+    }
+  }
+
+  BEGIN_MSG_MAP(PostMessageMarshaller)
+    MESSAGE_HANDLER(MSG_EXECUTE_TASK, ExecuteTask)
+  END_MSG_MAP()
+
+ private:
+  enum { MSG_EXECUTE_TASK = WM_APP + 6 };
+  inline LRESULT ExecuteTask(UINT, WPARAM wparam, LPARAM,
+                             BOOL& handled) {  // NOLINT
+    Task* task = reinterpret_cast<Task*>(wparam);
+    task->Run();
+    delete task;
+    return 0;
+  }
+};
+
+// Handles all automation requests initiated from the chrome frame objects.
+// These include the chrome tab/chrome frame activex/chrome frame npapi
+// plugin objects.
+class ChromeFrameAutomationClient
+    : public CWindowImpl<ChromeFrameAutomationClient>,
+      public TaskMarshallerThroughWindowsMessages<ChromeFrameAutomationClient>,
+      public PluginRequestHandler,
+      public TabProxy::TabProxyDelegate,
+      public ProxyFactory::LaunchDelegate {
+ public:
+  ChromeFrameAutomationClient();
+  ~ChromeFrameAutomationClient();
+
+  // Called from UI thread.
+  virtual bool Initialize(ChromeFrameDelegate* chrome_frame_delegate,
+                          int automation_server_launch_timeout,
+                          bool perform_version_check,
+                          const std::wstring& profile_name,
+                          const std::wstring& extra_chrome_arguments,
+                          bool incognito);
+  void Uninitialize();
+
+  virtual bool InitiateNavigation(const std::string& url);
+  virtual bool NavigateToIndex(int index);
+  bool ForwardMessageFromExternalHost(const std::string& message,
+                                      const std::string& origin,
+                                      const std::string& target);
+  bool SetProxySettings(const std::string& json_encoded_proxy_settings);
+
+  virtual void SetEnableExtensionAutomation(bool enable_automation);
+
+  void FindInPage(const std::wstring& search_string,
+                  FindInPageDirection forward,
+                  FindInPageCase match_case,
+                  bool find_next);
+
+  TabProxy* tab() const { return tab_.get(); }
+
+  BEGIN_MSG_MAP(ChromeFrameAutomationClient)
+    CHAIN_MSG_MAP(
+        TaskMarshallerThroughWindowsMessages<ChromeFrameAutomationClient>)
+  END_MSG_MAP()
+
+  void set_delegate(ChromeFrameDelegate* d) {
+    chrome_frame_delegate_ = d;
+  }
+
+  // Resizes the hosted chrome window. This is brokered to the chrome
+  // automation instance as the host browser could be running under low IL,
+  // which would cause the SetWindowPos call to fail.
+  void Resize(int width, int height, int flags);
+
+  // Sets the passed in window as the parent of the external tab.
+  void SetParentWindow(HWND parent_window);
+
+  void SendContextMenuCommandToChromeFrame(int selected_command);
+
+  HWND tab_window() const {
+    return tab_window_;
+  }
+
+  void ReleaseAutomationServer();
+
+  // Returns the version number of plugin dll.
+  std::wstring GetVersion() const;
+
+  // BitBlts the contents of the chrome window to the print dc.
+  void Print(HDC print_dc, const RECT& print_bounds);
+
+  // Called in full tab mode and indicates a request to chrome to print
+  // the whole tab.
+  void PrintTab();
+
+  // PluginRequestHandler
+  bool AddRequest(PluginUrlRequest* request);
+  void RemoveRequest(PluginUrlRequest* request);
+  virtual bool Send(IPC::Message* msg);
+
+  // URL request related
+  bool ReadRequest(int request_id, int bytes_to_read);
+  void RemoveRequest(int request_id, int reason, bool abort);
+  PluginUrlRequest* LookupRequest(int request_id) const;
+  bool IsValidRequest(PluginUrlRequest* request) const;
+  void CleanupRequests();
+
+  void set_use_chrome_network(bool use_chrome_network) {
+    use_chrome_network_ = use_chrome_network;
+  }
+  bool use_chrome_network() const {
+    return use_chrome_network_;
+  }
+
+#ifdef UNIT_TEST
+  void set_proxy_factory(ProxyFactory* factory) {
+    proxy_factory_ = factory;
+  }
+#endif
+
+  void set_handle_top_level_requests(bool handle_top_level_requests) {
+    handle_top_level_requests_ = handle_top_level_requests;
+  }
+
+  // Called if the same instance of the ChromeFrameAutomationClient object
+  // is reused.
+  bool Reinitialize(ChromeFrameDelegate* chrome_frame_delegate);
+
+  // Attaches an existing external tab to this automation client instance.
+  void AttachExternalTab(intptr_t external_tab_cookie);
+
+ protected:
+  // ChromeFrameAutomationProxy::LaunchDelegate implementation.
+  virtual void LaunchComplete(ChromeFrameAutomationProxy* proxy,
+                            AutomationLaunchResult result);
+  // TabProxyDelegate implementation
+  virtual void OnMessageReceived(TabProxy* tab, const IPC::Message& msg);
+
+  void CreateExternalTab();
+  void CreateExternalTabComplete(HWND chrome_window, HWND tab_window,
+                                 int tab_handle);
+  // Called in UI thread. Here we fire event to the client notifying for
+  // the result of Initialize() method call.
+  void InitializeComplete(AutomationLaunchResult result);
+
+ private:
+  typedef std::map<int, scoped_refptr<PluginUrlRequest> > RequestMap;
+
+  // Usage: From bkgnd thread invoke:
+  // CallDelegate(FROM_HERE, NewRunnableMethod(chrome_frame_delegate_,
+  //                                           ChromeFrameDelegate::Something,
+  //                                           param1,
+  //                                           param2));
+  void CallDelegate(const tracked_objects::Location& from_here,
+                    Task* delegate_task);
+  // The workhorse method called in main/GUI thread which is going to
+  // execute ChromeFrameDelegate method encapsulated in delegate_task.
+  void CallDelegateImpl(Task* delegate_task);
+
+  HWND chrome_window() const { return chrome_window_; }
+  void BeginNavigate(const GURL& url);
+  void BeginNavigateCompleted(AutomationMsg_NavigationResponseValues result);
+
+  // Helpers
+  void ReportNavigationError(AutomationMsg_NavigationResponseValues error_code,
+                             const std::string& url);
+
+  bool is_initialized() const {
+    return init_state_ == INITIALIZED;
+  }
+
+  bool incognito_;
+  HWND parent_window_;
+  PlatformThreadId ui_thread_id_;
+
+  void* automation_server_id_;
+  ChromeFrameAutomationProxy* automation_server_;
+  HWND chrome_window_;
+  scoped_refptr<TabProxy> tab_;
+  ChromeFrameDelegate* chrome_frame_delegate_;
+  GURL url_;
+
+  // Handle to the underlying chrome window. This is a child of the external
+  // tab window.
+  HWND tab_window_;
+
+  // Keeps track of the version of Chrome we're talking to.
+  std::string automation_server_version_;
+
+  // Map of outstanding requests
+  RequestMap request_map_;
+
+  typedef enum InitializationState {
+    UNINITIALIZED = 0,
+    INITIALIZING,
+    INITIALIZED,
+    UNINITIALIZING,
+  };
+
+  InitializationState init_state_;
+  bool use_chrome_network_;
+  bool handle_top_level_requests_;
+  ProxyFactory* proxy_factory_;
+  int tab_handle_;
+  // Only used if we attach to an existing tab.
+  intptr_t external_tab_cookie_;
+};
+
+#endif  // CHROME_FRAME_CHROME_FRAME_AUTOMATION_H_
diff --git a/chrome_frame/chrome_frame_delegate.cc b/chrome_frame/chrome_frame_delegate.cc
new file mode 100644
index 0000000..9a1069b
--- /dev/null
+++ b/chrome_frame/chrome_frame_delegate.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/chrome_frame_delegate.h"
+
+bool ChromeFrameDelegateImpl::IsTabMessage(const IPC::Message& message,
+                                           int* tab_handle) {
+  bool is_tab_message = true;
+  IPC_BEGIN_MESSAGE_MAP(ChromeFrameDelegateImpl, message)
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_NavigationStateChanged, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_UpdateTargetUrl, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_HandleAccelerator, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_TabbedOut, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_OpenURL, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_NavigationFailed, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_DidNavigate, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_TabLoaded, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_ForwardMessageToExternalHost, )
+    IPC_MESSAGE_HANDLER_GENERIC(
+        AutomationMsg_ForwardContextMenuToExternalHost, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_RequestStart, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_RequestRead, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_RequestEnd, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_SetCookieAsync, )
+    IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_AttachExternalTab, )
+    IPC_MESSAGE_UNHANDLED(is_tab_message = false);
+  IPC_END_MESSAGE_MAP()
+
+  if (is_tab_message) {
+    // Read tab handle from the message.
+    void* iter = NULL;
+    is_tab_message = message.ReadInt(&iter, tab_handle);
+  }
+
+  return is_tab_message;
+}
+
+void ChromeFrameDelegateImpl::OnMessageReceived(const IPC::Message& msg) {
+  if (!IsValid()) {
+    DLOG(WARNING) << __FUNCTION__
+                  << " Msgs received for a NULL automation client instance";
+    return;
+  }
+
+  IPC_BEGIN_MESSAGE_MAP(ChromeFrameDelegateImpl, msg)
+    IPC_MESSAGE_HANDLER(AutomationMsg_NavigationStateChanged,
+                        OnNavigationStateChanged)
+    IPC_MESSAGE_HANDLER(AutomationMsg_UpdateTargetUrl, OnUpdateTargetUrl)
+    IPC_MESSAGE_HANDLER(AutomationMsg_HandleAccelerator,
+                        OnAcceleratorPressed)
+    IPC_MESSAGE_HANDLER(AutomationMsg_TabbedOut, OnTabbedOut)
+    IPC_MESSAGE_HANDLER(AutomationMsg_OpenURL, OnOpenURL)
+    IPC_MESSAGE_HANDLER(AutomationMsg_NavigationFailed, OnNavigationFailed)
+    IPC_MESSAGE_HANDLER(AutomationMsg_DidNavigate, OnDidNavigate)
+    IPC_MESSAGE_HANDLER(AutomationMsg_TabLoaded, OnLoad)
+    IPC_MESSAGE_HANDLER(AutomationMsg_ForwardMessageToExternalHost,
+                        OnMessageFromChromeFrame)
+    IPC_MESSAGE_HANDLER(AutomationMsg_ForwardContextMenuToExternalHost,
+                        OnHandleContextMenu)
+    IPC_MESSAGE_HANDLER(AutomationMsg_RequestStart, OnRequestStart)
+    IPC_MESSAGE_HANDLER(AutomationMsg_RequestRead, OnRequestRead)
+    IPC_MESSAGE_HANDLER(AutomationMsg_RequestEnd, OnRequestEnd)
+    IPC_MESSAGE_HANDLER(AutomationMsg_SetCookieAsync, OnSetCookieAsync)
+    IPC_MESSAGE_HANDLER(AutomationMsg_AttachExternalTab, OnAttachExternalTab)
+  IPC_END_MESSAGE_MAP()
+}
diff --git a/chrome_frame/chrome_frame_delegate.h b/chrome_frame/chrome_frame_delegate.h
new file mode 100644
index 0000000..a3302da
--- /dev/null
+++ b/chrome_frame/chrome_frame_delegate.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_CHROME_FRAME_DELEGATE_H_
+#define CHROME_FRAME_CHROME_FRAME_DELEGATE_H_
+
+#include "chrome/test/automation/automation_messages.h"
+#include "ipc/ipc_message.h"
+
+// A common interface supported by all the browser specific ChromeFrame
+// implementations.
+class ChromeFrameDelegate {
+ public:
+
+  typedef HWND WindowType;
+
+  virtual WindowType GetWindow() const = 0;
+  virtual void GetBounds(RECT* bounds) = 0;
+  virtual std::string GetDocumentUrl() = 0;
+  virtual void OnAutomationServerReady() = 0;
+  virtual void OnAutomationServerLaunchFailed(
+      AutomationLaunchResult reason, const std::string& server_version) = 0;
+  virtual void OnMessageReceived(const IPC::Message& msg) = 0;
+
+  // This remains in interface since we call it if Navigate()
+  // returns immediate error.
+  virtual void OnLoadFailed(int error_code, const std::string& url) = 0;
+
+  // Returns true if this instance is alive and well for processing automation
+  // messages.
+  virtual bool IsValid() const = 0;
+
+ protected:
+  ~ChromeFrameDelegate() {}
+};
+
+// Template specialization
+template <> struct RunnableMethodTraits<ChromeFrameDelegate> {
+  static void RetainCallee(ChromeFrameDelegate* obj) {
+  }
+
+  static void ReleaseCallee(ChromeFrameDelegate* obj) {
+  }
+};
+
+extern UINT kAutomationServerReady;
+extern UINT kMessageFromChromeFrame;
+
+class ChromeFrameDelegateImpl : public ChromeFrameDelegate {
+ public:
+  virtual WindowType GetWindow() { return NULL; }
+  virtual void GetBounds(RECT* bounds) {}
+  virtual std::string GetDocumentUrl() { return std::string(); }
+  virtual void OnAutomationServerReady() {}
+  virtual void OnAutomationServerLaunchFailed(
+      AutomationLaunchResult reason, const std::string& server_version) {}
+  virtual void OnLoadFailed(int error_code, const std::string& url) {}
+  virtual void OnMessageReceived(const IPC::Message& msg);
+  static bool IsTabMessage(const IPC::Message& message, int* tab_handle);
+
+  virtual bool IsValid() const {
+    return true;
+  }
+
+ protected:
+  // Protected methods to be overriden.
+  virtual void OnNavigationStateChanged(int tab_handle, int flags,
+                                        const IPC::NavigationInfo& nav_info) {}
+  virtual void OnUpdateTargetUrl(int tab_handle,
+                                 const std::wstring& new_target_url) {}
+  virtual void OnAcceleratorPressed(int tab_handle, const MSG& accel_message) {}
+  virtual void OnTabbedOut(int tab_handle, bool reverse) {}
+  virtual void OnOpenURL(int tab_handle, const GURL& url,
+                         int open_disposition) {}
+  virtual void OnDidNavigate(int tab_handle,
+                             const IPC::NavigationInfo& navigation_info) {}
+  virtual void OnNavigationFailed(int tab_handle, int error_code,
+                                  const GURL& gurl) {}
+  virtual void OnLoad(int tab_handle, const GURL& url) {}
+  virtual void OnMessageFromChromeFrame(int tab_handle,
+                                        const std::string& message,
+                                        const std::string& origin,
+                                        const std::string& target) {}
+  virtual void OnHandleContextMenu(int tab_handle, HANDLE menu_handle,
+                                   int x_pos, int y_pos, int align_flags) {}
+  virtual void OnRequestStart(int tab_handle, int request_id,
+                              const IPC::AutomationURLRequest& request) {}
+  virtual void OnRequestRead(int tab_handle, int request_id,
+                             int bytes_to_read) {}
+  virtual void OnRequestEnd(int tab_handle, int request_id,
+                            const URLRequestStatus& status) {}
+  virtual void OnSetCookieAsync(int tab_handle, const GURL& url,
+                                const std::string& cookie) {}
+  virtual void OnAttachExternalTab(int tab_handle, intptr_t cookie,
+                                   int disposition) {}
+};
+
+#endif  // CHROME_FRAME_CHROME_FRAME_DELEGATE_H_
diff --git a/chrome_frame/chrome_frame_histograms.cc b/chrome_frame/chrome_frame_histograms.cc
new file mode 100644
index 0000000..e1ea548
--- /dev/null
+++ b/chrome_frame/chrome_frame_histograms.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/chrome_frame_histograms.h"
+
+#include "base/histogram.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/pickle.h"
+
+  // Initialize histogram statistics gathering system.
+base::LazyInstance<StatisticsRecorder>
+    g_statistics_recorder_(base::LINKER_INITIALIZED);
+
+ChromeFrameHistogramSnapshots::ChromeFrameHistogramSnapshots() {
+  // Ensure that an instance of the StatisticsRecorder object is created.
+  g_statistics_recorder_.Get();
+}
+
+ChromeFrameHistogramSnapshots::HistogramPickledList
+  ChromeFrameHistogramSnapshots::GatherAllHistograms() {
+
+  StatisticsRecorder::Histograms histograms;
+  StatisticsRecorder::GetHistograms(&histograms);
+
+  HistogramPickledList pickled_histograms;
+
+  for (StatisticsRecorder::Histograms::iterator it = histograms.begin();
+       histograms.end() != it;
+       it++) {
+    GatherHistogram(**it, &pickled_histograms);
+  }
+
+  return pickled_histograms;
+}
+
+void ChromeFrameHistogramSnapshots::GatherHistogram(
+    const Histogram& histogram,
+    HistogramPickledList* pickled_histograms) {
+
+  // Get up-to-date snapshot of sample stats.
+  Histogram::SampleSet snapshot;
+  histogram.SnapshotSample(&snapshot);
+  const std::string& histogram_name = histogram.histogram_name();
+
+  // Check if we already have a log of this histogram and if not create an
+  // empty set.
+  LoggedSampleMap::iterator it = logged_samples_.find(histogram_name);
+  Histogram::SampleSet* already_logged;
+  if (logged_samples_.end() == it) {
+    // Add new entry.
+    already_logged = &logged_samples_[histogram.histogram_name()];
+    already_logged->Resize(histogram);  // Complete initialization.
+  } else {
+    already_logged = &(it->second);
+    // Deduct any stats we've already logged from our snapshot.
+    snapshot.Subtract(*already_logged);
+  }
+
+  // Snapshot now contains only a delta to what we've already_logged.
+  if (snapshot.TotalCount() > 0) {
+    GatherHistogramDelta(histogram, snapshot, pickled_histograms);
+    // Add new data into our running total.
+    already_logged->Add(snapshot);
+  }
+}
+
+void ChromeFrameHistogramSnapshots::GatherHistogramDelta(
+    const Histogram& histogram,
+    const Histogram::SampleSet& snapshot,
+    HistogramPickledList* pickled_histograms) {
+
+  DCHECK(0 != snapshot.TotalCount());
+  snapshot.CheckSize(histogram);
+
+  std::string histogram_info =
+      Histogram::SerializeHistogramInfo(histogram, snapshot);
+  pickled_histograms->push_back(histogram_info);
+}
diff --git a/chrome_frame/chrome_frame_histograms.h b/chrome_frame/chrome_frame_histograms.h
new file mode 100644
index 0000000..03aaab5
--- /dev/null
+++ b/chrome_frame/chrome_frame_histograms.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_HISTOGRAM_SNAPSHOTS_H_
+#define CHROME_FRAME_HISTOGRAM_SNAPSHOTS_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/histogram.h"
+#include "base/process.h"
+#include "base/scoped_ptr.h"
+#include "base/task.h"
+
+// This class gathers histogram data in the host browser process and
+// serializes the data into a vector of strings to be uploaded to the
+// Chrome browser process. It records the histogram data which has been
+// logged and only uploads the delta with the next log.
+// TODO(iyengar)
+// This class does not contain any ChromeFrame specific stuff. It should
+// be moved to base.
+class ChromeFrameHistogramSnapshots {
+ public:
+  // Maintain a map of histogram names to the sample stats we've sent.
+  typedef std::map<std::string, Histogram::SampleSet> LoggedSampleMap;
+  typedef std::vector<std::string> HistogramPickledList;
+
+  ChromeFrameHistogramSnapshots();
+  ~ChromeFrameHistogramSnapshots() {}
+
+  // Extract snapshot data and return it to be sent off to the Chrome browser
+  // process.
+  // Return only a delta to what we have already sent.
+  HistogramPickledList GatherAllHistograms();
+
+ private:
+  void GatherHistogram(const Histogram& histogram,
+                       HistogramPickledList* histograms);
+
+  void GatherHistogramDelta(const Histogram& histogram,
+                            const Histogram::SampleSet& snapshot,
+                            HistogramPickledList* histograms);
+
+  // For histograms, record what we've already logged (as a sample for each
+  // histogram) so that we can send only the delta with the next log.
+  LoggedSampleMap logged_samples_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChromeFrameHistogramSnapshots);
+};
+
+#endif  // CHROME_RENDERER_HISTOGRAM_SNAPSHOTS_H_
diff --git a/chrome_frame/chrome_frame_npapi.cc b/chrome_frame/chrome_frame_npapi.cc
new file mode 100644
index 0000000..0c58cffc
--- /dev/null
+++ b/chrome_frame/chrome_frame_npapi.cc
@@ -0,0 +1,1462 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/chrome_frame_npapi.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "base/win_util.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "chrome_frame/ff_privilege_check.h"
+#include "chrome_frame/utils.h"
+
+MessageLoop* ChromeFrameNPAPI::message_loop_ = NULL;
+int ChromeFrameNPAPI::instance_count_ = 0;
+
+static const char* kNpEventNames[] = {
+  "focus",
+  "blur",
+};
+
+NPClass ChromeFrameNPAPI::plugin_class_ = {
+  NP_CLASS_STRUCT_VERSION,
+  ChromeFrameNPAPI::AllocateObject,
+  ChromeFrameNPAPI::DeallocateObject,
+  ChromeFrameNPAPI::Invalidate,
+  ChromeFrameNPAPI::HasMethod,
+  ChromeFrameNPAPI::Invoke,
+  NULL,  // invokeDefault
+  ChromeFrameNPAPI::HasProperty,
+  ChromeFrameNPAPI::GetProperty,
+  ChromeFrameNPAPI::SetProperty,
+  NULL,  // remove property
+  NULL,  // enumeration
+  NULL,  // construct
+};
+
+NPIdentifier
+    ChromeFrameNPAPI::plugin_property_identifiers_[PLUGIN_PROPERTY_COUNT]
+        = {0};
+
+const NPUTF8* ChromeFrameNPAPI::plugin_property_identifier_names_[] = {
+  "version",
+  "src",
+  "onload",
+  "onloaderror",
+  "onmessage",
+  "readystate",
+  "onprivatemessage",
+  "usechromenetwork",
+};
+
+const NPUTF8* ChromeFrameNPAPI::plugin_method_identifier_names_[] = {
+  "postMessage",
+  "postPrivateMessage",
+};
+
+ChromeFrameNPAPI::PluginMethod ChromeFrameNPAPI::plugin_methods_[] = {
+  &ChromeFrameNPAPI::postMessage,
+  &ChromeFrameNPAPI::postPrivateMessage,
+};
+
+NPIdentifier
+    ChromeFrameNPAPI::plugin_method_identifiers_[arraysize(plugin_methods_)]
+        = {0};
+
+
+void ChromeFrameNPAPI::CompileAsserts() {
+  NOTREACHED();  // This function should never be invoked.
+
+  COMPILE_ASSERT(arraysize(plugin_method_identifier_names_) ==
+                 arraysize(plugin_methods_),
+                 you_must_add_both_plugin_method_and_name);
+
+  COMPILE_ASSERT(arraysize(plugin_property_identifier_names_) ==
+                 arraysize(plugin_property_identifiers_),
+                 you_must_add_both_plugin_property_and_name);
+}
+
+static const int kMaxBytesForPluginConsumption = 0x7FFFFFFF;
+
+static const char kPluginSrcAttribute[] = "src";
+static const char kPluginForceFullPageAttribute[] = "force_full_page";
+static const char kPluginOnloadAttribute[] = "onload";
+static const char kPluginOnErrorAttribute[] = "onloaderror";
+static const char kPluginOnMessageAttribute[] = "onmessage";
+static const char kPluginOnPrivateMessageAttribute[] = "onprivatemessage";
+// These properties can only be set in arguments at control instantiation.
+// When the privileged_mode property is provided and set to true, the control
+// will probe for whether its hosting document has the system principal, in
+// which case privileged mode will be enabled.
+static const char kPluginPrivilegedModeAttribute[] = "privileged_mode";
+// If privileged mode is enabled, the string value of this argument will
+// be appended to the chrome.exe command line.
+static const char kPluginChromeExtraArguments[] = "chrome_extra_arguments";
+// If privileged mode is enabled, the string value of this argument will
+// be used as the profile name for our chrome.exe instance.
+static const char kPluginChromeProfileName[] = "chrome_profile_name";
+// If chrome network stack is to be used
+static const char kPluginUseChromeNetwork[] = "usechromenetwork";
+
+
+NPError NPP_New(NPMIMEType plugin_type, NPP instance, uint16 mode, int16 argc,
+                char* argn[], char* argv[], NPSavedData* saved) {
+  if (instance == NULL)
+    return NPERR_INVALID_INSTANCE_ERROR;
+
+  ChromeFrameNPAPI::ChromeFrameNPObject* chrome_frame_npapi_obj =
+      reinterpret_cast<ChromeFrameNPAPI::ChromeFrameNPObject*>(
+          npapi::CreateObject(instance, ChromeFrameNPAPI::PluginClass()));
+  DCHECK(chrome_frame_npapi_obj != NULL);
+
+  ChromeFrameNPAPI* plugin_instance =
+      chrome_frame_npapi_obj->chrome_frame_plugin_instance;
+  DCHECK(plugin_instance != NULL);
+
+  // Note that we MUST set instance->pdata BEFORE calling Initialize. This is
+  // because Initialize can call back into the NPAPI host which will need the
+  // pdata field to be set.
+  chrome_frame_npapi_obj->chrome_frame_plugin_instance =
+      plugin_instance;
+  instance->pdata = chrome_frame_npapi_obj;
+
+  bool init = plugin_instance->Initialize(plugin_type, instance,
+                                          mode, argc, argn, argv);
+  DCHECK(init);
+
+  return NPERR_NO_ERROR;
+}
+
+NPError NPP_Destroy(NPP instance, NPSavedData** save) {
+  // Takes ownership and releases the object at the end of scope.
+  ScopedNpObject<ChromeFrameNPAPI::ChromeFrameNPObject> chrome_frame_npapi_obj(
+      reinterpret_cast<ChromeFrameNPAPI::ChromeFrameNPObject*>(
+          instance->pdata));
+
+  if (chrome_frame_npapi_obj.get()) {
+    ChromeFrameNPAPI* plugin_instance =
+        ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance);
+
+    plugin_instance->Uninitialize();
+    instance->pdata = NULL;
+  }
+
+  return NPERR_NO_ERROR;
+}
+
+NPError NPP_SetWindow(NPP instance, NPWindow* window_info) {
+  if (window_info == NULL) {
+    NOTREACHED();
+    return NPERR_GENERIC_ERROR;
+  }
+
+  ChromeFrameNPAPI* plugin_instance =
+      ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance);
+
+  if (plugin_instance == NULL) {
+    return NPERR_INVALID_INSTANCE_ERROR;
+  }
+
+  plugin_instance->SetWindow(window_info);
+  return NPERR_NO_ERROR;
+}
+
+NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream,
+                      NPBool seekable, uint16* stream_type) {
+  NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest(
+      instance, stream->notifyData);
+  if (url_request) {
+    if (!url_request->OnStreamCreated(type, stream))
+      return NPERR_GENERIC_ERROR;
+  }
+
+  // We need to return the requested stream mode if we are returning a success
+  // code. If we don't do this it causes Opera to blow up.
+  *stream_type = NP_NORMAL;
+  return NPERR_NO_ERROR;
+}
+
+NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason) {
+  NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest(
+      instance, stream->notifyData);
+  if (url_request) {
+    url_request->OnStreamDestroyed(reason);
+  }
+
+  return NPERR_NO_ERROR;
+}
+
+NPError NPP_GetValue(NPP instance, NPPVariable variable, void* value) {
+  if (variable == NPPVpluginScriptableNPObject) {
+    void** plugin = reinterpret_cast<void**>(value);
+    ChromeFrameNPAPI::ChromeFrameNPObject* chrome_frame_npapi_obj =
+        reinterpret_cast<ChromeFrameNPAPI::ChromeFrameNPObject*>(
+            instance->pdata);
+    // Return value is expected to be retained
+    npapi::RetainObject(reinterpret_cast<NPObject*>(chrome_frame_npapi_obj));
+    *plugin = chrome_frame_npapi_obj;
+    return NPERR_NO_ERROR;
+  }
+  return NPERR_GENERIC_ERROR;
+}
+
+NPError NPP_SetValue(NPP instance, NPNVariable variable, void* value) {
+  return NPERR_GENERIC_ERROR;
+}
+
+int32 NPP_WriteReady(NPP instance, NPStream* stream) {
+  NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest(
+      instance, stream->notifyData);
+  if (url_request) {
+    return url_request->OnWriteReady();
+  }
+
+  return kMaxBytesForPluginConsumption;
+}
+
+int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len,
+                void* buffer) {
+  NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest(
+      instance, stream->notifyData);
+  if (url_request) {
+    return url_request->OnWrite(buffer, len);
+  }
+
+  return len;
+}
+
+void NPP_URLNotify(NPP instance, const char* url, NPReason reason,
+                   void* notifyData) {
+  ChromeFrameNPAPI* plugin_instance =
+      ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance);
+  if (plugin_instance) {
+    plugin_instance->UrlNotify(url, reason, notifyData);
+  }
+}
+
+void NPP_Print(NPP instance, NPPrint* print_info) {
+  ChromeFrameNPAPI* plugin_instance =
+      ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance);
+
+  if (plugin_instance == NULL) {
+    NOTREACHED();
+    return;
+  }
+
+  plugin_instance->Print(print_info);
+}
+
+// ChromeFrameNPAPI member defines.
+
+// TODO(tommi): remove ignore_setfocus_ since that's not how focus is
+//  handled anymore.
+
+ChromeFrameNPAPI::ChromeFrameNPAPI()
+    : instance_(NULL),
+    mode_(NP_EMBED),
+    force_full_page_plugin_(false),
+    ready_state_(READYSTATE_LOADING),
+    enabled_popups_(false) {
+}
+
+ChromeFrameNPAPI::~ChromeFrameNPAPI() {
+  if (IsWindow()) {
+    if (!UnsubclassWindow()) {
+      // TODO(tommi): Figure out why this can sometimes happen in the
+      // WidgetModeFF_Resize unittest.
+      DLOG(ERROR) << "Couldn't unsubclass safely!";
+      UnsubclassWindow(TRUE);
+    }
+  }
+  m_hWnd = NULL;
+
+  instance_count_--;
+  if (instance_count_ <= 0) {
+    delete message_loop_;
+    message_loop_ = NULL;
+  }
+
+  Uninitialize();
+}
+
+std::string GetLocation(NPP instance, NPObject* window) {
+  if (!window) {
+    // Can fail if the browser is closing (seen in Opera).
+    return "";
+  }
+
+  std::string result;
+  ScopedNpVariant href;
+  ScopedNpVariant location;
+
+  bool ok = npapi::GetProperty(instance, window,
+      npapi::GetStringIdentifier("location"), &location);
+  DCHECK(ok);
+  DCHECK(location.type == NPVariantType_Object);
+
+  if (ok) {
+    ok = npapi::GetProperty(instance,
+        location.value.objectValue,
+        npapi::GetStringIdentifier("href"),
+        &href);
+    DCHECK(ok);
+    DCHECK(href.type == NPVariantType_String);
+    if (ok) {
+      result.assign(href.value.stringValue.UTF8Characters,
+                    href.value.stringValue.UTF8Length);
+    }
+  }
+
+  return result;
+}
+
+std::string ChromeFrameNPAPI::GetLocation() {
+  // Note that GetWindowObject() will cache the window object here.
+  return ::GetLocation(instance_, GetWindowObject());
+}
+
+bool ChromeFrameNPAPI::Initialize(NPMIMEType mime_type, NPP instance,
+                                  uint16 mode, int16 argc, char* argn[],
+                                  char* argv[]) {
+  if (!Base::Initialize())
+    return false;
+
+  instance_ = instance;
+  mime_type_ = mime_type;
+  mode_ = mode;
+  document_url_ = GetLocation();
+
+  if (instance_count_ == 0) {
+    DCHECK(message_loop_ == NULL);
+    message_loop_ = new MessageLoop();
+  }
+
+  instance_count_++;
+
+  // Create our prefs service wrapper here.
+  DCHECK(!pref_service_.get());
+  pref_service_ = CreatePrefService();
+  if (!pref_service_.get()) {
+    NOTREACHED() << "new NpProxyService";
+    return false;
+  }
+
+  // Temporary variables for privileged only parameters
+  const char* onprivatemessage_arg = NULL;
+  const char* chrome_extra_arguments_arg = NULL;
+  const char* chrome_profile_name_arg = NULL;
+  bool chrome_network_arg_set = false;
+  bool chrome_network_arg = false;
+  bool wants_privileged = false;
+
+  for (int i = 0; i < argc; ++i) {
+    if (LowerCaseEqualsASCII(argn[i], kPluginSrcAttribute)) {
+      src_ = ResolveURL(GetDocumentUrl(), argv[i]);
+    } else if (LowerCaseEqualsASCII(argn[i], kPluginForceFullPageAttribute)) {
+      force_full_page_plugin_ = atoi(argv[i]) ? true : false;
+    } else if (LowerCaseEqualsASCII(argn[i], kPluginOnErrorAttribute)) {
+      onerror_handler_ = JavascriptToNPObject(argv[i]);
+    } else if (LowerCaseEqualsASCII(argn[i], kPluginOnMessageAttribute)) {
+      onmessage_handler_ = JavascriptToNPObject(argv[i]);
+    } else if (LowerCaseEqualsASCII(argn[i],
+                                   kPluginPrivilegedModeAttribute)) {
+      // Test for the FireFox privileged mode if the user requests it
+      // in initialization parameters.
+      wants_privileged = atoi(argv[i]) ? true : false;
+    } else if (LowerCaseEqualsASCII(argn[i],
+                                    kPluginOnPrivateMessageAttribute)) {
+      onprivatemessage_arg = argv[i];
+    } else if (LowerCaseEqualsASCII(argn[i], kPluginChromeExtraArguments)) {
+      chrome_extra_arguments_arg = argv[i];
+    } else if (LowerCaseEqualsASCII(argn[i], kPluginChromeProfileName)) {
+      chrome_profile_name_arg = argv[i];
+    } else if (LowerCaseEqualsASCII(argn[i], kPluginUseChromeNetwork)) {
+      chrome_network_arg_set = true;
+      chrome_network_arg = atoi(argv[i]) ? true : false;
+    }
+  }
+
+  // Is the privileged mode requested?
+  if (wants_privileged) {
+    is_privileged_ = IsFireFoxPrivilegedInvocation(instance);
+    if (!is_privileged_) {
+      DLOG(WARNING) << "Privileged mode requested in non-privileged context";
+    }
+  }
+
+  std::wstring extra_arguments;
+  std::wstring profile_name(GetHostProcessName(false));
+  if (is_privileged_) {
+    // Process any privileged mode-only arguments we were handed.
+    if (onprivatemessage_arg)
+      onprivatemessage_handler_ = JavascriptToNPObject(onprivatemessage_arg);
+
+    if (chrome_extra_arguments_arg)
+      extra_arguments = UTF8ToWide(chrome_extra_arguments_arg);
+
+    if (chrome_profile_name_arg)
+      profile_name = UTF8ToWide(chrome_profile_name_arg);
+
+    if (chrome_network_arg_set)
+      automation_client_->set_use_chrome_network(chrome_network_arg);
+
+  }
+
+  // TODO(joshia): Initialize navigation here and send proxy config as
+  // part of LaunchSettings
+  /*
+  if (!src_.empty())
+    automation_client_->InitiateNavigation(src_);
+
+  std::string proxy_settings;
+  bool has_prefs = pref_service_->Initialize(instance_,
+                                             automation_client_.get());
+  if (has_prefs && pref_service_->GetProxyValueJSONString(&proxy_settings)) {
+    automation_client_->SetProxySettings(proxy_settings);
+  }
+  */
+
+  // We can't call SubscribeToFocusEvents here since
+  // when Initialize gets called, Opera is in a state where
+  // it can't handle calls back and the thread will hang.
+  // Instead, we call SubscribeToFocusEvents when we initialize
+  // our plugin window.
+
+  // TODO(stoyan): Ask host for specific interface whether to honor
+  // host's in-private mode.
+  return InitializeAutomation(profile_name, extra_arguments,
+                              GetBrowserIncognitoMode());
+}
+
+void ChromeFrameNPAPI::Uninitialize() {
+  // Don't call SetReadyState as it will end up calling FireEvent.
+  // We are in the context of NPP_DESTROY.
+  ready_state_ = READYSTATE_UNINITIALIZED;
+
+  UnsubscribeFromFocusEvents();
+
+  if (pref_service_) {
+    pref_service_->UnInitialize();
+    pref_service_ = NULL;
+  }
+
+  window_object_.Free();
+  onerror_handler_.Free();
+  onmessage_handler_.Free();
+  onprivatemessage_handler_.Free();
+
+  Base::Uninitialize();
+}
+
+void ChromeFrameNPAPI::OnFinalMessage(HWND window) {
+  // The automation server should be gone by now.
+  Uninitialize();
+}
+
+void ChromeFrameNPAPI::SubscribeToFocusEvents() {
+  DCHECK(focus_listener_.get() == NULL);
+
+  focus_listener_ = new DomEventListener(this);
+  if (!focus_listener_->Subscribe(instance_, kNpEventNames,
+                                  arraysize(kNpEventNames))) {
+    focus_listener_ = NULL;
+    focus_listener_ = new NPObjectEventListener(this);
+    if (!focus_listener_->Subscribe(instance_, kNpEventNames,
+                                    arraysize(kNpEventNames))) {
+      DLOG(ERROR) << "Failed to subscribe to focus events";
+      focus_listener_ = NULL;
+    }
+  }
+}
+
+void ChromeFrameNPAPI::UnsubscribeFromFocusEvents() {
+  if (!focus_listener_.get())
+    return;
+
+  bool ret = focus_listener_->Unsubscribe(instance_, kNpEventNames,
+                                          arraysize(kNpEventNames));
+  DLOG_IF(WARNING, !ret) << "focus_listener_->Unsubscribe failed";
+  focus_listener_ = NULL;
+}
+
+bool ChromeFrameNPAPI::SetWindow(NPWindow* window_info) {
+  if (!window_info || !automation_client_.get()) {
+    NOTREACHED();
+    return false;
+  }
+
+  HWND window = reinterpret_cast<HWND>(window_info->window);
+  if (!::IsWindow(window)) {
+    // No window created yet. Ignore this call.
+    return false;
+  }
+
+  if (IsWindow()) {
+    // We've already subclassed, make sure that SetWindow doesn't get called
+    // with an HWND other than the one we subclassed during our lifetime.
+    DCHECK(window == m_hWnd);
+    return true;
+  }
+
+  automation_client_->SetParentWindow(window);
+
+  SubscribeToFocusEvents();
+
+  if (force_full_page_plugin_) {
+    // By default full page mode is only enabled when the plugin is loaded off
+    // a separate file, i.e. it is the primary content in the window. Even if
+    // we specify the width/height attributes for the plugin as 100% each, FF
+    // instantiates the plugin passing in a width/height of 100px each. To
+    // workaround this we resize the plugin window passed in by FF to the size
+    // of its parent.
+    HWND plugin_parent_window = ::GetParent(window);
+    RECT plugin_parent_rect = {0};
+    ::GetClientRect(plugin_parent_window, &plugin_parent_rect);
+    ::SetWindowPos(window, NULL, plugin_parent_rect.left,
+                   plugin_parent_rect.top,
+                   plugin_parent_rect.right - plugin_parent_rect.left,
+                   plugin_parent_rect.bottom - plugin_parent_rect.top, 0);
+  }
+
+  // Subclass the browser's plugin window here.
+  if (SubclassWindow(window)) {
+    DWORD new_style_flags = WS_CLIPCHILDREN;
+    ModifyStyle(0, new_style_flags, 0);
+
+    if (ready_state_ < READYSTATE_INTERACTIVE) {
+      SetReadyState(READYSTATE_INTERACTIVE);
+    }
+  }
+
+  return true;
+}
+
+void ChromeFrameNPAPI::Print(NPPrint* print_info) {
+  if (!print_info) {
+    NOTREACHED();
+    return;
+  }
+
+  // We dont support full tab mode yet.
+  if (print_info->mode != NP_EMBED) {
+    NOTREACHED();
+    return;
+  }
+
+  NPWindow window = print_info->print.embedPrint.window;
+
+  RECT print_bounds = {0};
+  print_bounds.left = window.x;
+  print_bounds.top = window.y;
+  print_bounds.right = window.x + window.width;
+  print_bounds.bottom = window.x + window.height;
+
+  automation_client_->Print(
+      reinterpret_cast<HDC>(print_info->print.embedPrint.platformPrint),
+      print_bounds);
+}
+
+void ChromeFrameNPAPI::UrlNotify(const char* url, NPReason reason,
+                                 void* notify_data) {
+  if (enabled_popups_) {
+    // We have opened the URL so tell the browser to restore popup settings
+    enabled_popups_ = false;
+    npapi::PopPopupsEnabledState(instance_);
+  }
+
+  // It is now safe to release the additional reference on the request
+  NPAPIUrlRequest* request = RequestFromNotifyData(notify_data);
+  if (request) {
+    request->Stop();
+    request->Release();
+  }
+}
+
+void ChromeFrameNPAPI::OnAcceleratorPressed(int tab_handle,
+                                            const MSG& accel_message) {
+  DLOG(INFO) << __FUNCTION__ << " msg:"
+      << StringPrintf("0x%04X", accel_message.message) << " key:"
+      << accel_message.wParam;
+
+  // The host browser does call TranslateMessage on messages like WM_KEYDOWN
+  // WM_KEYUP, etc, which will result in messages like WM_CHAR, WM_SYSCHAR, etc
+  // being posted to the message queue. We don't post these messages here to
+  // avoid these messages from getting handled twice.
+  if (accel_message.message != WM_CHAR &&
+      accel_message.message != WM_DEADCHAR &&
+      accel_message.message != WM_SYSCHAR &&
+      accel_message.message != WM_SYSDEADCHAR) {
+    // A very primitive way to handle keystrokes.
+    // TODO(tommi): When we've implemented a way for chrome to
+    //  know when keystrokes are handled (deterministically) on that side,
+    //  then this function should get called and not otherwise.
+    ::PostMessage(::GetParent(m_hWnd), accel_message.message,
+                  accel_message.wParam, accel_message.lParam);
+  }
+
+  if (automation_client_.get()) {
+    TabProxy* tab = automation_client_->tab();
+    if (tab) {
+      tab->ProcessUnhandledAccelerator(accel_message);
+    }
+  }
+}
+
+void ChromeFrameNPAPI::OnTabbedOut(int tab_handle, bool reverse) {
+  DLOG(INFO) << __FUNCTION__;
+
+  ignore_setfocus_ = true;
+  HWND parent = ::GetParent(m_hWnd);
+  ::SetFocus(parent);
+
+  INPUT input = {0};
+  input.type = INPUT_KEYBOARD;
+  input.ki.wVk = VK_TAB;
+  SendInput(1, &input, sizeof(input));
+  input.ki.dwFlags = KEYEVENTF_KEYUP;
+  SendInput(1, &input, sizeof(input));
+
+  ignore_setfocus_ = false;
+}
+
+void ChromeFrameNPAPI::OnOpenURL(int tab_handle,
+                                 const GURL& url, int open_disposition) {
+  std::string target;
+  switch (open_disposition) {
+    case NEW_FOREGROUND_TAB:
+      target = "_blank";
+      break;
+    case NEW_BACKGROUND_TAB:
+      target = "_blank";
+      break;
+    case NEW_WINDOW:
+      target = "_new";
+      break;
+    default:
+      break;
+  }
+
+  // Tell the browser to temporarily allow popups
+  enabled_popups_ = true;
+  npapi::PushPopupsEnabledState(instance_, TRUE);
+  npapi::GetURLNotify(instance_, url.spec().c_str(), target.c_str(), NULL);
+}
+
+void ChromeFrameNPAPI::OnRequestStart(int tab_handle, int request_id,
+    const IPC::AutomationURLRequest& request) {
+  scoped_refptr<NPAPIUrlRequest> new_request(new NPAPIUrlRequest(instance_));
+  DCHECK(new_request);
+  if (new_request->Initialize(automation_client_.get(), tab_handle,
+                              request_id, request.url, request.method,
+                              request.referrer, request.extra_request_headers,
+                              request.upload_data.get(), true)) {
+    if (new_request->Start()) {
+      // Keep additional reference on request for NPSTREAM
+      // This will be released in NPP_UrlNotify
+      new_request->AddRef();
+    }
+  }
+}
+
+void ChromeFrameNPAPI::OnRequestRead(int tab_handle, int request_id,
+                                     int bytes_to_read) {
+  automation_client_->ReadRequest(request_id, bytes_to_read);
+}
+
+void ChromeFrameNPAPI::OnRequestEnd(int tab_handle, int request_id,
+                                    const URLRequestStatus& status) {
+  automation_client_->RemoveRequest(request_id, status.status(), true);
+}
+
+void ChromeFrameNPAPI::OnSetCookieAsync(int tab_handle, const GURL& url,
+                                        const std::string& cookie) {
+  // Use the newer NPAPI way if available
+  if (npapi::VersionMinor() >= NPVERS_HAS_URL_AND_AUTH_INFO) {
+    npapi::SetValueForURL(instance_, NPNURLVCookie, url.spec().c_str(),
+                          cookie.c_str(), cookie.length());
+  } else if (url == GURL(document_url_)) {
+    std::string script = "javascript:document.cookie=";
+    script.append(cookie);
+    script.append(1, ';');
+    ExecuteScript(script, NULL);
+  } else {
+    // Third party cookie, use nsICookieService to set the cookie.
+    NOTREACHED();
+  }
+}
+
+bool ChromeFrameNPAPI::HasMethod(NPObject* obj, NPIdentifier name) {
+  for (int i = 0; i < arraysize(plugin_methods_); ++i) {
+    if (name == plugin_method_identifiers_[i])
+      return true;
+  }
+
+  return false;
+}
+
+bool ChromeFrameNPAPI::Invoke(NPObject* header, NPIdentifier name,
+                              const NPVariant* args, uint32_t arg_count,
+                              NPVariant* result) {
+  ChromeFrameNPAPI* plugin_instance = ChromeFrameInstanceFromNPObject(header);
+  if (!plugin_instance && (plugin_instance->automation_client_.get()))
+    return false;
+
+  bool success = false;
+  for (int i = 0; i < arraysize(plugin_methods_); ++i) {
+    if (name == plugin_method_identifiers_[i]) {
+      PluginMethod method = plugin_methods_[i];
+      success = (plugin_instance->*method)(header, args, arg_count, result);
+      break;
+    }
+  }
+
+  return success;
+}
+
+void ChromeFrameNPAPI::InitializeIdentifiers() {
+  npapi::GetStringIdentifiers(plugin_method_identifier_names_,
+                              arraysize(plugin_methods_),
+                              plugin_method_identifiers_);
+
+  npapi::GetStringIdentifiers(plugin_property_identifier_names_,
+                              PLUGIN_PROPERTY_COUNT,
+                              plugin_property_identifiers_);
+}
+
+NPObject* ChromeFrameNPAPI::AllocateObject(NPP instance, NPClass* class_name) {
+  static bool identifiers_initialized = false;
+
+  ChromeFrameNPObject* plugin_object = new ChromeFrameNPObject();
+  DCHECK(plugin_object != NULL);
+
+  plugin_object->chrome_frame_plugin_instance = new ChromeFrameNPAPI();
+  DCHECK(plugin_object->chrome_frame_plugin_instance != NULL);
+
+  plugin_object->npp = NULL;
+
+  COMPILE_ASSERT(arraysize(plugin_method_identifiers_) ==
+                 arraysize(plugin_method_identifier_names_),
+                 method_count_mismatch);
+
+  COMPILE_ASSERT(arraysize(plugin_method_identifiers_) ==
+                 arraysize(plugin_methods_),
+                 method_count_mismatch);
+
+  if (!identifiers_initialized) {
+    InitializeIdentifiers();
+    identifiers_initialized  = true;
+  }
+
+  return reinterpret_cast<NPObject*>(plugin_object);
+}
+
+void ChromeFrameNPAPI::DeallocateObject(NPObject* header) {
+  ChromeFrameNPObject* plugin_object =
+      reinterpret_cast<ChromeFrameNPObject*>(header);
+  DCHECK(plugin_object != NULL);
+
+  if (plugin_object) {
+    delete plugin_object->chrome_frame_plugin_instance;
+    delete plugin_object;
+  }
+}
+
+void ChromeFrameNPAPI::Invalidate(NPObject* header) {
+  DCHECK(header);
+  ChromeFrameNPObject* plugin_object =
+      reinterpret_cast<ChromeFrameNPObject*>(header);
+  if (plugin_object) {
+    DCHECK(plugin_object->chrome_frame_plugin_instance);
+    plugin_object->chrome_frame_plugin_instance->Uninitialize();
+  }
+}
+
+ChromeFrameNPAPI* ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(
+    NPP instance) {
+  if ((instance == NULL) || (instance->pdata == NULL)) {
+    NOTREACHED();
+    return NULL;
+  }
+
+  return ChromeFrameInstanceFromNPObject(instance->pdata);
+}
+
+ChromeFrameNPAPI* ChromeFrameNPAPI::ChromeFrameInstanceFromNPObject(
+    void* object) {
+  ChromeFrameNPObject* plugin_object =
+      reinterpret_cast<ChromeFrameNPObject*>(object);
+  if (!plugin_object) {
+    NOTREACHED();
+    return NULL;
+  }
+
+  DCHECK(plugin_object->chrome_frame_plugin_instance);
+  return plugin_object->chrome_frame_plugin_instance;
+}
+
+bool ChromeFrameNPAPI::HasProperty(NPObject* obj, NPIdentifier name) {
+  for (int i = 0; i < PLUGIN_PROPERTY_COUNT; ++i) {
+    if (name == plugin_property_identifiers_[i])
+      return true;
+  }
+  return false;
+}
+
+bool ChromeFrameNPAPI::GetProperty(NPIdentifier name,
+                                   NPVariant* variant) {
+  if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONERROR]) {
+    if (onerror_handler_) {
+      variant->type = NPVariantType_Object;
+      variant->value.objectValue = onerror_handler_.Copy();
+      return true;
+    }
+  } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONMESSAGE]) {
+    if (onmessage_handler_) {
+      variant->type = NPVariantType_Object;
+      variant->value.objectValue = onmessage_handler_.Copy();
+      return true;
+    }
+  } else if (name ==
+             plugin_property_identifiers_[PLUGIN_PROPERTY_ONPRIVATEMESSAGE]) {
+    if (!is_privileged_) {
+      DLOG(WARNING) << "Attempt to read onprivatemessage property while not "
+                       "privileged";
+    } else {
+      if (onprivatemessage_handler_) {
+        variant->type = NPVariantType_Object;
+        variant->value.objectValue =
+            onprivatemessage_handler_.Copy();
+        return true;
+      }
+    }
+  } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_SRC]) {
+    AllocateStringVariant(src_, variant);
+    return true;
+  } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_VERSION]) {
+    const std::wstring version =
+        automation_client_->GetVersion();
+    AllocateStringVariant(WideToUTF8(version), variant);
+    return true;
+  } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_READYSTATE]) {
+    INT32_TO_NPVARIANT(ready_state_, *variant);
+    return true;
+  } else if (name ==
+        plugin_property_identifiers_[PLUGIN_PROPERTY_USECHROMENETWORK]) {
+    BOOLEAN_TO_NPVARIANT(automation_client_->use_chrome_network(), *variant);
+    return true;
+  }
+
+  return false;
+}
+
+bool ChromeFrameNPAPI::GetProperty(NPObject* object, NPIdentifier name,
+                                   NPVariant* variant) {
+  if (!object || !variant) {
+    NOTREACHED();
+    return false;
+  }
+
+  ChromeFrameNPAPI* plugin_instance = ChromeFrameInstanceFromNPObject(object);
+  if (!plugin_instance) {
+    NOTREACHED();
+    return false;
+  }
+
+  return plugin_instance->GetProperty(name, variant);
+}
+
+bool ChromeFrameNPAPI::SetProperty(NPIdentifier name,
+                                   const NPVariant* variant) {
+  if (NPVARIANT_IS_OBJECT(*variant)) {
+    if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONERROR]) {
+      onerror_handler_.Free();
+      onerror_handler_ = variant->value.objectValue;
+      return true;
+    } else if (
+        name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONMESSAGE]) {
+      onmessage_handler_.Free();
+      onmessage_handler_ = variant->value.objectValue;
+      return true;
+    } else if (name ==
+              plugin_property_identifiers_[PLUGIN_PROPERTY_ONPRIVATEMESSAGE]) {
+      if (!is_privileged_) {
+        DLOG(WARNING) << "Attempt to set onprivatemessage while not privileged";
+      } else {
+        onprivatemessage_handler_.Free();
+        onprivatemessage_handler_ = variant->value.objectValue;
+        return true;
+      }
+    }
+  } else if (NPVARIANT_IS_STRING(*variant) || NPVARIANT_IS_NULL(*variant)) {
+    if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_SRC]) {
+      return NavigateToURL(variant, 1, NULL);
+    }
+  } else if (NPVARIANT_IS_BOOLEAN(*variant)) {
+    if (name ==
+        plugin_property_identifiers_[PLUGIN_PROPERTY_USECHROMENETWORK]) {
+      automation_client_->set_use_chrome_network(
+          NPVARIANT_TO_BOOLEAN(*variant));
+    }
+  }
+
+  return false;
+}
+
+bool ChromeFrameNPAPI::SetProperty(NPObject* object, NPIdentifier name,
+                                   const NPVariant* variant) {
+  if (!object || !variant) {
+    DLOG(ERROR) << "Cannot set property: " << npapi::StringFromIdentifier(name);
+    return false;
+  }
+
+  ChromeFrameNPAPI* plugin_instance = ChromeFrameInstanceFromNPObject(object);
+  if (!plugin_instance) {
+    NOTREACHED();
+    return false;
+  }
+
+  return plugin_instance->SetProperty(name, variant);
+}
+
+void ChromeFrameNPAPI::OnFocus() {
+  DLOG(INFO) << __FUNCTION__;
+  PostMessage(WM_SETFOCUS, 0, 0);
+}
+
+void ChromeFrameNPAPI::OnEvent(const char* event_name) {
+  DCHECK(event_name);
+  DLOG(INFO) << event_name;
+
+  if (lstrcmpiA(event_name, "focus") == 0) {
+    OnFocus();
+  } else if (lstrcmpiA(event_name, "blur") == 0) {
+    OnBlur();
+  } else {
+    NOTREACHED() << event_name;
+  }
+}
+
+LRESULT CALLBACK ChromeFrameNPAPI::DropKillFocusHook(int code, WPARAM wparam,
+                                                     LPARAM lparam) {
+  LRESULT ret = 0;
+  CWPSTRUCT* wp = reinterpret_cast<CWPSTRUCT*>(lparam);
+  if ((code < 0) || (wp->message != WM_KILLFOCUS))
+    ret = ::CallNextHookEx(NULL, code, wparam, lparam);
+
+  return ret;
+}
+
+LRESULT ChromeFrameNPAPI::OnSetFocus(UINT message, WPARAM wparam,
+                                     LPARAM lparam, BOOL& handled) {  // NO_LINT
+  // Opera has a WH_CALLWNDPROC hook that handles WM_KILLFOCUS and
+  // prevents us from setting the focus to the tab.
+  // To work around that, we set a temporary hook here that does nothing
+  // (not even call other hooks) when it sees WM_KILLFOCUS.
+  HHOOK hook = NULL;
+  hook = ::SetWindowsHookEx(WH_CALLWNDPROC, DropKillFocusHook, NULL,
+                            ::GetCurrentThreadId());
+  // Since we chain message maps, make sure we are not calling base class
+  // twice for WM_SETFOCUS.
+  BOOL handled_by_base = TRUE;
+  LRESULT ret = Base::OnSetFocus(message, wparam, lparam, handled_by_base);
+  if (hook)
+    ::UnhookWindowsHookEx(hook);
+
+  return ret;
+}
+
+void ChromeFrameNPAPI::OnBlur() {
+  DLOG(INFO) << __FUNCTION__;
+}
+
+void ChromeFrameNPAPI::OnLoad(int, const GURL& gurl) {
+  DLOG(INFO) << "Firing onload";
+  FireEvent("load", gurl.spec());
+}
+
+void ChromeFrameNPAPI::OnLoadFailed(int error_code, const std::string& url) {
+  FireEvent("loaderror", url);
+
+  ScopedNpVariant result;
+  InvokeDefault(onerror_handler_, url, &result);
+}
+
+void ChromeFrameNPAPI::OnMessageFromChromeFrame(int tab_handle,
+                                                const std::string& message,
+                                                const std::string& origin,
+                                                const std::string& target) {
+  bool private_message = false;
+  if (target.compare("*") != 0) {
+    if (is_privileged_) {
+      private_message = true;
+    } else {
+      if (!HaveSameOrigin(target, document_url_)) {
+        DLOG(WARNING) << "Dropping posted message since target doesn't match "
+            "the current document's origin. target=" << target;
+        return;
+      }
+    }
+  }
+
+  // Create a MessageEvent object that contains the message and origin
+  // as well as supporting other MessageEvent (see the HTML5 spec) properties.
+  // Then call the onmessage handler.
+  ScopedNpObject<NPObject> event;
+  bool ok = CreateMessageEvent(false, true, message, origin, event.Receive());
+  if (ok) {
+    // Don't call FireEvent here (or we'll have an event wrapped by an event).
+    DispatchEvent(event);
+
+    ScopedNpVariant result;
+    NPVariant params[2];
+    OBJECT_TO_NPVARIANT(event, params[0]);
+    bool invoke = false;
+    if (private_message) {
+      DCHECK(is_privileged_);
+      STRINGN_TO_NPVARIANT(target.c_str(), target.length(), params[1]);
+      invoke = InvokeDefault(onprivatemessage_handler_,
+                             arraysize(params),
+                             params,
+                             &result);
+    } else {
+      invoke = InvokeDefault(onmessage_handler_, params[0], &result);
+    }
+    DLOG_IF(WARNING, !invoke) << "InvokeDefault failed";
+  } else {
+    NOTREACHED() << "CreateMessageEvent";
+  }
+}
+
+void ChromeFrameNPAPI::OnAutomationServerReady() {
+  Base::OnAutomationServerReady();
+
+  std::string proxy_settings;
+  bool has_prefs = pref_service_->Initialize(instance_,
+                                             automation_client_.get());
+  if (has_prefs && pref_service_->GetProxyValueJSONString(&proxy_settings)) {
+    automation_client_->SetProxySettings(proxy_settings);
+  }
+
+  if (!src_.empty()) {
+    if (!automation_client_->InitiateNavigation(src_)) {
+      DLOG(ERROR) << "Failed to navigate to: " << src_;
+      src_.clear();
+    }
+  }
+
+  SetReadyState(READYSTATE_COMPLETE);
+}
+
+void ChromeFrameNPAPI::OnAutomationServerLaunchFailed(
+    AutomationLaunchResult reason, const std::string& server_version) {
+  SetReadyState(READYSTATE_UNINITIALIZED);
+
+  if (reason == AUTOMATION_VERSION_MISMATCH) {
+    DisplayVersionMismatchWarning(m_hWnd, server_version);
+  }
+}
+
+bool ChromeFrameNPAPI::InvokeDefault(NPObject* object,
+                                     unsigned param_count,
+                                     const NPVariant* params,
+                                     NPVariant* result) {
+  if (!object)
+    return false;
+
+  bool ret = npapi::InvokeDefault(instance_, object, params, param_count,
+                                  result);
+  // InvokeDefault can return false in FF even though we do see the call
+  // go through.  It's not clear to me what the circumstances are, so
+  // we log it as a warning while tracking it down.
+  DLOG_IF(WARNING, !ret) << "npapi::InvokeDefault failed";
+  return ret;
+}
+
+bool ChromeFrameNPAPI::InvokeDefault(NPObject* object, const std::string& param,
+                                     NPVariant* result) {
+  NPVariant arg;
+  STRINGN_TO_NPVARIANT(param.c_str(), param.length(), arg);
+  return InvokeDefault(object, arg, result);
+}
+
+bool ChromeFrameNPAPI::InvokeDefault(NPObject* object, const NPVariant& param,
+                                     NPVariant* result) {
+  return InvokeDefault(object, 1, &param, result);
+}
+
+bool ChromeFrameNPAPI::CreateEvent(const std::string& type, bool bubbles,
+                                   bool cancelable, NPObject** basic_event) {
+  DCHECK(basic_event);
+  NPObject* window = GetWindowObject();
+  if (!window) {
+    // Can fail if the browser is closing (seen in Opera).
+    return false;
+  }
+
+  const char* identifier_names[] = {
+    "document",
+    "createEvent",
+    "initEvent",
+  };
+
+  NPIdentifier identifiers[arraysize(identifier_names)];
+  npapi::GetStringIdentifiers(identifier_names, arraysize(identifier_names),
+                              identifiers);
+
+  // Fetch the document object from the window.
+  ScopedNpVariant document;
+  bool ok = npapi::GetProperty(instance_, window, identifiers[0], &document);
+  if (!ok) {
+    // This could happen if the page is being unloaded.
+    DLOG(WARNING) << "Failed to fetch the document object";
+    return false;
+  }
+
+  bool success = false;
+  if (ok && NPVARIANT_IS_OBJECT(document)) {
+    // Call document.createEvent("Event") to create a basic event object.
+    NPVariant event_type;
+    STRINGN_TO_NPVARIANT("Event", sizeof("Event") - 1, event_type);
+    ScopedNpVariant result;
+    success = npapi::Invoke(instance_, NPVARIANT_TO_OBJECT(document),
+                            identifiers[1], &event_type, 1, &result);
+    if (!NPVARIANT_IS_OBJECT(result)) {
+      DLOG(WARNING) << "Failed to invoke createEvent";
+      success = false;
+    } else {
+      NPVariant init_args[3];
+      STRINGN_TO_NPVARIANT(type.c_str(), type.length(), init_args[0]);
+      BOOLEAN_TO_NPVARIANT(bubbles, init_args[1]);
+      BOOLEAN_TO_NPVARIANT(cancelable, init_args[2]);
+
+      // Now initialize the event object by calling
+      // event.initEvent(type, bubbles, cancelable);
+      ScopedNpVariant init_results;
+      ok = npapi::Invoke(instance_, NPVARIANT_TO_OBJECT(result), identifiers[2],
+                         init_args, arraysize(init_args), &init_results);
+      if (ok) {
+        success = true;
+        // Finally, pass the ownership to the caller.
+        *basic_event = NPVARIANT_TO_OBJECT(result);
+        VOID_TO_NPVARIANT(result);  // Prevent the object from being released.
+      } else {
+        DLOG(ERROR) << "initEvent failed";
+        success = false;
+      }
+    }
+  }
+
+  return success;
+}
+
+bool ChromeFrameNPAPI::CreateMessageEvent(bool bubbles, bool cancelable,
+                                          const std::string& data,
+                                          const std::string& origin,
+                                          NPObject** message_event) {
+  DCHECK(message_event);
+  ScopedNpObject<NPObject> event;
+  bool ok = CreateEvent("message", false, true, event.Receive());
+  if (ok) {
+    typedef enum {
+      DATA,
+      ORIGIN,
+      LAST_EVENT_ID,
+      SOURCE,
+      MESSAGE_PORT,
+      IDENTIFIER_COUNT,  // Must be last.
+    } StringIdentifiers;
+
+    static NPIdentifier identifiers[IDENTIFIER_COUNT] = {0};
+    if (!identifiers[0]) {
+      const NPUTF8* identifier_names[] = {
+        "data",
+        "origin",
+        "lastEventId",
+        "source",
+        "messagePort",
+      };
+      COMPILE_ASSERT(arraysize(identifier_names) == arraysize(identifiers),
+                     mismatched_array_size);
+      npapi::GetStringIdentifiers(identifier_names, IDENTIFIER_COUNT,
+                                  identifiers);
+    }
+
+    NPVariant arg;
+    STRINGN_TO_NPVARIANT(data.c_str(), data.length(), arg);
+    npapi::SetProperty(instance_, event, identifiers[DATA], &arg);
+    STRINGN_TO_NPVARIANT(origin.c_str(), origin.length(), arg);
+    npapi::SetProperty(instance_, event, identifiers[ORIGIN], &arg);
+    STRINGN_TO_NPVARIANT("", 0, arg);
+    npapi::SetProperty(instance_, event, identifiers[LAST_EVENT_ID], &arg);
+    NULL_TO_NPVARIANT(arg);
+    npapi::SetProperty(instance_, event, identifiers[SOURCE], &arg);
+    npapi::SetProperty(instance_, event, identifiers[MESSAGE_PORT], &arg);
+    *message_event = event.Detach();
+  }
+
+  return ok;
+}
+
+
+void ChromeFrameNPAPI::DispatchEvent(NPObject* event) {
+  DCHECK(event != NULL);
+
+  ScopedNpObject<NPObject> embed;
+  npapi::GetValue(instance_, NPNVPluginElementNPObject, &embed);
+  if (embed != NULL) {
+    NPVariant param;
+    OBJECT_TO_NPVARIANT(event, param);
+    ScopedNpVariant result;
+    bool invoke = npapi::Invoke(instance_, embed,
+        npapi::GetStringIdentifier("dispatchEvent"), &param, 1, &result);
+    DLOG_IF(WARNING, !invoke) << "dispatchEvent failed";
+  } else {
+    NOTREACHED() << "NPNVPluginElementNPObject";
+  }
+}
+
+bool ChromeFrameNPAPI::ExecuteScript(const std::string& script,
+                                     NPVariant* result) {
+  NPObject* window = GetWindowObject();
+  if (!window) {
+    NOTREACHED();
+    return false;
+  }
+
+  NPString script_for_execution;
+  script_for_execution.UTF8Characters = script.c_str();
+  script_for_execution.UTF8Length = script.length();
+
+  return npapi::Evaluate(instance_, window, &script_for_execution, result);
+}
+
+NPObject* ChromeFrameNPAPI::JavascriptToNPObject(const std::string& script) {
+  // Convert the passed in script to an invocable NPObject
+  // To achieve this we save away the function in a dummy window property
+  // which is then read to get the script object representing the function.
+
+  std::string script_code =
+      "javascript:window.__cf_get_function_object =";
+
+  // If we are able to look up the name in the javascript namespace, then it
+  // means that the caller passed in a function name. Convert the function
+  // name to a NPObject we can invoke on.
+  if (IsValidJavascriptFunction(script)) {
+    script_code += script;
+  } else {
+    script_code += "new Function(\"";
+    script_code += script;
+    script_code += "\");";
+  }
+
+  NPVariant result;
+  if (!ExecuteScript(script_code, &result)) {
+    NOTREACHED();
+    return NULL;
+  }
+
+  DCHECK(result.type == NPVariantType_Object);
+  DCHECK(result.value.objectValue != NULL);
+  return result.value.objectValue;
+}
+
+bool ChromeFrameNPAPI::IsValidJavascriptFunction(const std::string& script) {
+  std::string script_code = "javascript:window['";
+  script_code += script;
+  script_code += "'];";
+
+  ScopedNpVariant result;
+  if (!ExecuteScript(script_code, &result)) {
+    NOTREACHED();
+    return NULL;
+  }
+
+  return result.type == NPVariantType_Object;
+}
+
+bool ChromeFrameNPAPI::NavigateToURL(const NPVariant* args, uint32_t arg_count,
+                                     NPVariant* result) {
+  // Note that 'result' might be NULL.
+  if (arg_count != 1 || !(NPVARIANT_IS_STRING(args[0]) ||
+                          NPVARIANT_IS_NULL(args[0]))) {
+    NOTREACHED();
+    return false;
+  }
+
+  if (ready_state_ == READYSTATE_UNINITIALIZED) {
+    // Error(L"Chrome Frame failed to initialize.");
+    // TODO(tommi): call NPN_SetException
+    DLOG(WARNING) << "NavigateToURL called after failed initialization";
+    return false;
+  }
+
+  std::string url("about:blank");
+
+  if (!NPVARIANT_IS_NULL(args[0])) {
+    const NPString& str = args[0].value.stringValue;
+    if (str.UTF8Length) {
+      url.assign(std::string(str.UTF8Characters, str.UTF8Length));
+    }
+  }
+  DLOG(WARNING) << __FUNCTION__ << " " << url;
+  std::string full_url = ResolveURL(GetDocumentUrl(), url);
+  if (full_url.empty())
+    return false;
+
+  src_ = full_url;
+  // Navigate only if we completed initialization i.e. proxy is set etc.
+  if (ready_state_ == READYSTATE_COMPLETE) {
+    if (!automation_client_->InitiateNavigation(full_url)) {
+      // TODO(tommi): call NPN_SetException.
+      src_.clear();
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ChromeFrameNPAPI::postMessage(NPObject* npobject, const NPVariant* args,
+                                   uint32_t arg_count, NPVariant* result) {
+  if (arg_count < 1 || arg_count > 2 || !NPVARIANT_IS_STRING(args[0])) {
+    NOTREACHED();
+    return false;
+  }
+
+  const NPString& str = args[0].value.stringValue;
+  std::string message(str.UTF8Characters, str.UTF8Length);
+  std::string target;
+  if (arg_count == 2 && NPVARIANT_IS_STRING(args[1])) {
+    const NPString& str = args[1].value.stringValue;
+    target.assign(str.UTF8Characters, str.UTF8Length);
+    if (target.compare("*") != 0) {
+      GURL resolved(target);
+      if (!resolved.is_valid()) {
+        npapi::SetException(npobject,
+                            "Unable to parse the specified target URL.");
+        return false;
+      }
+      target = resolved.spec();
+    }
+  } else {
+    target = "*";
+  }
+
+  GURL url(GURL(document_url_).GetOrigin());
+  std::string origin(url.is_empty() ? "null" : url.spec());
+
+  automation_client_->ForwardMessageFromExternalHost(message, origin, target);
+
+  return true;
+}
+
+bool ChromeFrameNPAPI::postPrivateMessage(NPObject* npobject,
+                                          const NPVariant* args,
+                                          uint32_t arg_count,
+                                          NPVariant* result) {
+  if (!is_privileged_) {
+    DLOG(WARNING) << "postPrivateMessage invoked in non-privileged mode";
+    return false;
+  }
+
+  if (arg_count != 3 || !NPVARIANT_IS_STRING(args[0]) ||
+      !NPVARIANT_IS_STRING(args[1]) || !NPVARIANT_IS_STRING(args[2])) {
+    NOTREACHED();
+    return false;
+  }
+
+  const NPString& message_str = args[0].value.stringValue;
+  const NPString& origin_str = args[1].value.stringValue;
+  const NPString& target_str = args[2].value.stringValue;
+  std::string message(message_str.UTF8Characters, message_str.UTF8Length);
+  std::string origin(origin_str.UTF8Characters, origin_str.UTF8Length);
+  std::string target(target_str.UTF8Characters, target_str.UTF8Length);
+
+  automation_client_->ForwardMessageFromExternalHost(message, origin, target);
+
+  return true;
+}
+
+void ChromeFrameNPAPI::FireEvent(const std::string& event_type,
+                                 const std::string& data) {
+  NPVariant arg;
+  STRINGN_TO_NPVARIANT(data.c_str(), data.length(), arg);
+  FireEvent(event_type, arg);
+}
+
+void ChromeFrameNPAPI::FireEvent(const std::string& event_type,
+                                 const NPVariant& data) {
+  // Check that we're not bundling an event inside an event.
+  // Right now we're only expecting simple types for the data argument.
+  DCHECK(NPVARIANT_IS_OBJECT(data) == false);
+
+  ScopedNpObject<NPObject> ev;
+  CreateEvent(event_type, false, false, ev.Receive());
+  if (ev) {
+    // Add the 'data' member to the event.
+    bool set = npapi::SetProperty(instance_, ev,
+        npapi::GetStringIdentifier("data"), const_cast<NPVariant*>(&data));
+    DCHECK(set);
+    DispatchEvent(ev);
+  }
+}
+
+NpProxyService* ChromeFrameNPAPI::CreatePrefService() {
+  return new NpProxyService;
+}
+
+NPObject* ChromeFrameNPAPI::GetWindowObject() const {
+  if (!window_object_.get()) {
+    NPError ret = npapi::GetValue(instance_, NPNVWindowNPObject,
+        window_object_.Receive());
+    DLOG_IF(ERROR, ret != NPERR_NO_ERROR) << "NPNVWindowNPObject failed";
+  }
+  return window_object_;
+}
+
+bool ChromeFrameNPAPI::GetBrowserIncognitoMode() {
+  bool incognito_mode = false;
+
+  // Check disabled for Opera due to bug: http://b/issue?id=1815494
+  if (GetBrowserType() != BROWSER_OPERA) {
+    // Check whether host browser is in private mode;
+    NPBool private_mode = FALSE;
+    NPError err = npapi::GetValue(instance_,
+                                  NPNVprivateModeBool,
+                                  &private_mode);
+    if (err == NPERR_NO_ERROR && private_mode) {
+      incognito_mode = true;
+    }
+  } else {
+    DLOG(WARNING) << "Not checking for private mode in Opera";
+  }
+
+  return incognito_mode;
+}
+
+NPAPIUrlRequest* ChromeFrameNPAPI::ValidateRequest(
+    NPP instance, void* notify_data) {
+  ChromeFrameNPAPI* plugin_instance =
+      ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance);
+  if (plugin_instance) {
+    return plugin_instance->RequestFromNotifyData(notify_data);
+  }
+
+  return NULL;
+}
+
+NPAPIUrlRequest* ChromeFrameNPAPI::RequestFromNotifyData(
+    void* notify_data) const {
+  NPAPIUrlRequest* request = reinterpret_cast<NPAPIUrlRequest*>(notify_data);
+  DCHECK(request ? automation_client_->IsValidRequest(request) : 1);
+  return request;
+}
+
+bool ChromeFrameNPAPI::HandleContextMenuCommand(UINT cmd) {
+  if (cmd == IDC_ABOUT_CHROME_FRAME) {
+    // TODO: implement "About Chrome Frame"
+  }
+  return false;
+}
diff --git a/chrome_frame/chrome_frame_npapi.h b/chrome_frame/chrome_frame_npapi.h
new file mode 100644
index 0000000..005d72f
--- /dev/null
+++ b/chrome_frame/chrome_frame_npapi.h
@@ -0,0 +1,333 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_CHROME_FRAME_NPAPI_H_
+#define CHROME_FRAME_CHROME_FRAME_NPAPI_H_
+
+#include <atlbase.h>
+#include <atlwin.h>
+#include <string>
+
+#include "chrome_frame/chrome_frame_automation.h"
+#include "chrome_frame/chrome_frame_plugin.h"
+#include "chrome_frame/np_browser_functions.h"
+#include "chrome_frame/np_event_listener.h"
+#include "chrome_frame/np_proxy_service.h"
+#include "chrome_frame/npapi_url_request.h"
+
+class MessageLoop;
+
+// ChromeFrameNPAPI: Implementation of the NPAPI plugin, which is responsible
+// for hosting a chrome frame, i.e. an iframe like widget which hosts the the
+// chrome window. This object delegates to Chrome.exe  (via the Chrome
+// IPC-based automation mechanism) for the actual rendering.
+class ChromeFrameNPAPI
+    : public CWindowImpl<ChromeFrameNPAPI>,
+      public ChromeFramePlugin<ChromeFrameNPAPI>,
+      public NpEventDelegate {
+ public:
+  typedef ChromeFramePlugin<ChromeFrameNPAPI> Base;
+
+  // NPObject structure which is exposed by us.
+  struct ChromeFrameNPObject : public NPObject {
+    NPP npp;
+    ChromeFrameNPAPI* chrome_frame_plugin_instance;
+  };
+
+  typedef enum {
+    PLUGIN_PROPERTY_VERSION,
+    PLUGIN_PROPERTY_SRC,
+    PLUGIN_PROPERTY_ONLOAD,
+    PLUGIN_PROPERTY_ONERROR,
+    PLUGIN_PROPERTY_ONMESSAGE,
+    PLUGIN_PROPERTY_READYSTATE,
+    PLUGIN_PROPERTY_ONPRIVATEMESSAGE,
+    PLUGIN_PROPERTY_USECHROMENETWORK,
+    PLUGIN_PROPERTY_COUNT  // must be last
+  } PluginPropertyId;
+
+  static const int kWmSwitchFocusToChromeFrame = WM_APP + 0x100;
+
+  static NPClass plugin_class_;
+  static NPClass* PluginClass() {
+    return &plugin_class_;
+  }
+
+  ChromeFrameNPAPI();
+  ~ChromeFrameNPAPI();
+
+  bool Initialize(NPMIMEType mime_type, NPP instance, uint16 mode,
+                  int16 argc, char* argn[], char* argv[]);
+  void Uninitialize();
+
+  bool SetWindow(NPWindow* window_info);
+  void UrlNotify(const char* url, NPReason reason, void* notify_data);
+  bool NewStream(NPMIMEType type, NPStream* stream, NPBool seekable,
+                 uint16* stream_type);
+
+  void Print(NPPrint* print_info);
+
+  // NPObject functions, which ensure that the plugin object is scriptable.
+  static bool HasMethod(NPObject* obj, NPIdentifier name);
+  static bool Invoke(NPObject* header, NPIdentifier name,
+                     const NPVariant* args, uint32_t arg_count,
+                     NPVariant* result);
+  static NPObject* AllocateObject(NPP instance, NPClass* class_name);
+  static void DeallocateObject(NPObject* header);
+
+  // Called by the scripting environment when the native code is shutdown.
+  // Any attempt to message a NPObject instance after the invalidate callback
+  // has been called will result in undefined behavior, even if the native code
+  // is still retaining those NPObject instances.
+  static void Invalidate(NPObject* header);
+
+  // The following functions need to be implemented to ensure that FF3
+  // invokes methods on the plugin. If these methods are not implemented
+  // then invokes on the plugin NPObject from the script fail with a
+  // bad NPObject error.
+  static bool HasProperty(NPObject* obj, NPIdentifier name);
+  static bool GetProperty(NPObject* obj, NPIdentifier name, NPVariant *variant);
+  static bool SetProperty(NPObject* obj, NPIdentifier name,
+                          const NPVariant *variant);
+
+  // Returns the ChromeFrameNPAPI object pointer from the NPP instance structure
+  // passed in by the browser.
+  static ChromeFrameNPAPI* ChromeFrameInstanceFromPluginInstance(NPP instance);
+
+  // Returns the ChromeFrameNPAPI object pointer from the NPObject structure
+  // which represents our plugin class.
+  static ChromeFrameNPAPI* ChromeFrameInstanceFromNPObject(void* object);
+
+  // Return a UrlRequest instance associated with the given instance and
+  // stream combination.
+  static NPAPIUrlRequest* ValidateRequest(NPP instance, void* notify_data);
+
+BEGIN_MSG_MAP(ChromeFrameNPAPI)
+  MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
+  CHAIN_MSG_MAP(Base)
+END_MSG_MAP()
+
+  LRESULT OnSetFocus(UINT message, WPARAM wparam, LPARAM lparam,
+                     BOOL& handled);  // NO_LINT
+
+  // Implementation of NpEventDelegate
+  virtual void OnEvent(const char* event_name);
+
+  void OnFocus();
+  void OnBlur();
+
+  // Implementation of SetProperty, public to allow unittesting.
+  bool SetProperty(NPIdentifier name, const NPVariant *variant);
+  // Implementation of GetProperty, public to allow unittesting.
+  bool GetProperty(NPIdentifier name, NPVariant *variant);
+
+  // Initialize string->identifier mapping, public to allow unittesting.
+  static void InitializeIdentifiers();
+
+  bool HandleContextMenuCommand(UINT cmd);
+ protected:
+  // Handler for accelerator messages passed on from the hosted chrome
+  // instance.
+  virtual void OnAcceleratorPressed(int tab_handle, const MSG& accel_message);
+  virtual void OnTabbedOut(int tab_handle, bool reverse);
+  virtual void OnOpenURL(int tab_handle, const GURL& url, int open_disposition);
+  virtual void OnLoad(int tab_handle, const GURL& url);
+  virtual void OnMessageFromChromeFrame(int tab_handle,
+                                        const std::string& message,
+                                        const std::string& origin,
+                                        const std::string& target);
+  virtual void OnRequestStart(int tab_handle, int request_id,
+                              const IPC::AutomationURLRequest& request);
+  virtual void OnRequestRead(int tab_handle, int request_id,
+                             int bytes_to_read);
+  virtual void OnRequestEnd(int tab_handle, int request_id,
+                            const URLRequestStatus& status);
+  virtual void OnSetCookieAsync(int tab_handle, const GURL& url,
+                                const std::string& cookie);
+
+  // ChromeFrameDelegate overrides
+  virtual void OnLoadFailed(int error_code, const std::string& url);
+  virtual void OnAutomationServerReady();
+  virtual void OnAutomationServerLaunchFailed(
+      AutomationLaunchResult reason, const std::string& server_version);
+
+ private:
+  void SubscribeToFocusEvents();
+  void UnsubscribeFromFocusEvents();
+
+  // Equivalent of:
+  // event = window.document.createEvent("Event");
+  // event.initEvent(type, bubbles, cancelable);
+  // and then returns the event object.
+  bool CreateEvent(const std::string& type, bool bubbles, bool cancelable,
+                   NPObject** basic_event);
+
+  // Creates and initializes an event object of type "message".
+  // Used for postMessage.
+  bool CreateMessageEvent(bool bubbles, bool cancelable,
+      const std::string& data, const std::string& origin,
+      NPObject** message_event);
+
+  // Calls chrome_frame.dispatchEvent to fire events to event listeners.
+  void DispatchEvent(NPObject* event);
+
+  // Returns a pointer to the <object> element in the page that
+  // hosts the plugin.  Note that this is the parent element of the <embed>
+  // element.  The <embed> element doesn't support some of the events that
+  // we require, so we use the object element for receiving events.
+  bool GetObjectElement(nsIDOMElement** element);
+
+  // Prototype for all methods that can be invoked from script.
+  typedef bool (ChromeFrameNPAPI::*PluginMethod)(NPObject* npobject,
+                                                 const NPVariant* args,
+                                                 uint32_t arg_count,
+                                                 NPVariant* result);
+
+  // Implementations of scriptable methods.
+
+  bool NavigateToURL(const NPVariant* args, uint32_t arg_count,
+                     NPVariant* result);
+
+  bool postMessage(NPObject* npobject, const NPVariant* args,
+                   uint32_t arg_count, NPVariant* result);
+
+  // This method is only available when the control is in privileged mode.
+  bool postPrivateMessage(NPObject* npobject, const NPVariant* args,
+                          uint32_t arg_count, NPVariant* result);
+
+  // Pointers to method implementations.
+  static PluginMethod plugin_methods_[];
+
+  // NPObject method ids exposed by the plugin.
+  static NPIdentifier plugin_method_identifiers_[];
+
+  // NPObject method names exposed by the plugin.
+  static const NPUTF8* plugin_method_identifier_names_[];
+
+  // NPObject property ids exposed by the plugin.
+  static NPIdentifier plugin_property_identifiers_[];
+
+  // NPObject property names exposed by the plugin.
+  static const NPUTF8*
+      plugin_property_identifier_names_[];
+
+  virtual void OnFinalMessage(HWND window);
+
+  // Helper function to invoke a function on a NPObject.
+  bool InvokeDefault(NPObject* object, const std::string& param,
+                     NPVariant* result);
+
+  bool InvokeDefault(NPObject* object, const NPVariant& param,
+                     NPVariant* result);
+
+  bool InvokeDefault(NPObject* object, unsigned param_count,
+                     const NPVariant* params, NPVariant* result);
+
+  // Helper function to convert javascript code to a NPObject we can
+  // invoke on.
+  virtual NPObject* JavascriptToNPObject(const std::string& function_name);
+
+  // Helper function to execute a script.
+  // Returns S_OK on success.
+  bool ExecuteScript(const std::string& script, NPVariant* result);
+
+  // Returns true if the script passed in is a valid function in the DOM.
+  bool IsValidJavascriptFunction(const std::string& script);
+
+  // Converts the data parameter to an NPVariant and forwards the call to the
+  // other FireEvent method.
+  void FireEvent(const std::string& event_type, const std::string& data);
+
+  // Creates an event object, assigns the data parameter to a |data| property
+  // on the event object and then calls DispatchEvent to fire the event to
+  // listeners.  event_type is the name of the event being fired.
+  void FireEvent(const std::string& event_type, const NPVariant& data);
+
+  // Returns a new prefs service. Virtual to allow overriding in unittests.
+  virtual NpProxyService* CreatePrefService();
+
+  // Returns our associated windows' location.
+  virtual std::string GetLocation();
+
+  // Returns true iff we're successfully able to query for the browser's
+  // incognito mode, and the browser returns true.
+  virtual bool GetBrowserIncognitoMode();
+
+  // Returns the window script object for the page.
+  // This function will cache the window object to avoid calling
+  // npapi::GetValue which can cause problems in Opera.
+  NPObject* GetWindowObject() const;
+
+  virtual void SetReadyState(READYSTATE new_state) {
+    ready_state_ = new_state;
+    NPVariant var;
+    INT32_TO_NPVARIANT(ready_state_, var);
+    FireEvent("readystatechanged", var);
+  }
+
+  // Host function to compile-time asserts over members of this class.
+  static void CompileAsserts();
+
+  // Get request from the stream notify data
+  NPAPIUrlRequest* RequestFromNotifyData(void* notify_data) const;
+
+  static LRESULT CALLBACK DropKillFocusHook(int code, WPARAM wparam,
+                                            LPARAM lparam);  // NO_LINT
+
+  // The plugins opaque instance handle
+  NPP instance_;
+
+  // The plugin instantiation mode (NP_FULL or NP_EMBED)
+  int16 mode_;
+  // The plugins mime type.
+  std::string mime_type_;
+
+  // Set to true if we need a full page plugin.
+  bool force_full_page_plugin_;
+
+  scoped_refptr<NpProxyService> pref_service_;
+
+  // Used to receive focus and blur events from the object element
+  // that hosts the plugin.
+  scoped_refptr<NpEventListener> focus_listener_;
+
+  // In some cases the IPC channel proxy object is instantiated on the UI
+  // thread in FF. It then tries to use the IPC logger, which relies on
+  // the message loop being around. Declaring a dummy message loop
+  // is a hack to get around this. Eventually the automation code needs to
+  // be fixed to ensure that the channel proxy always gets created on a thread
+  // with a message loop.
+  static MessageLoop* message_loop_;
+  static int instance_count_;
+
+  // The following members contain the NPObject pointers representing the
+  // onload/onerror/onmessage handlers on the page.
+  ScopedNpObject<NPObject> onerror_handler_;
+  ScopedNpObject<NPObject> onmessage_handler_;
+  ScopedNpObject<NPObject> onprivatemessage_handler_;
+
+  // As a workaround for a problem in Opera we cache the window object.
+  // The problem stems from two things: window messages aren't always removed
+  // from the message queue and messages can be pumped inside GetValue.
+  // This can cause an infinite recursion of processing the same message
+  // repeatedly.
+  mutable ScopedNpObject<NPObject> window_object_;
+
+  // Note since 'onload' is a registered event name, the browser will
+  // automagically create a code block for the handling code and hook it
+  // up to the CF object via addEventListener.
+  // See this list of known event types:
+  // http://www.w3.org/TR/DOM-Level-3-Events/events.html#Event-types
+
+  READYSTATE ready_state_;
+
+
+  // Popups are enabled
+  bool enabled_popups_;
+
+  // The value of src property keeping the current URL.
+  std::string src_;
+};
+
+#endif  // CHROME_FRAME_CHROME_FRAME_NPAPI_H_
diff --git a/chrome_frame/chrome_frame_npapi.rgs b/chrome_frame/chrome_frame_npapi.rgs
new file mode 100644
index 0000000..70e6ca1f
--- /dev/null
+++ b/chrome_frame/chrome_frame_npapi.rgs
@@ -0,0 +1,13 @@
+HKLM {

+  NoRemove Software {

+    NoRemove MozillaPlugins {

+      ForceRemove '@google.com/ChromeFrame,version=1.0' {

+        val Path = s '%MODULE%'

+        val Description = s 'Google ChromeFrame'

+        val ProductName = s 'Google ChromeFrame'

+        val Vendor = s 'Google'

+        val Version = s '%VERSION%'

+      }

+    }

+  }

+}

diff --git a/chrome_frame/chrome_frame_npapi_entrypoints.cc b/chrome_frame/chrome_frame_npapi_entrypoints.cc
new file mode 100644
index 0000000..f1eec1c
--- /dev/null
+++ b/chrome_frame/chrome_frame_npapi_entrypoints.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/chrome_frame_npapi.h"
+
+#define NPAPI WINAPI
+
+// Plugin entry points.
+extern "C" {
+  NPError NPAPI NP_Initialize(NPNetscapeFuncs* browser_funcs);
+  NPError NPAPI NP_GetEntryPoints(NPPluginFuncs* plugin_funcs);
+  void NPAPI NP_Shutdown();
+}
+
+NPError NPAPI NP_Initialize(NPNetscapeFuncs* browser_funcs) {
+  DLOG(INFO) << __FUNCTION__;
+  _pAtlModule->Lock();
+  npapi::InitializeBrowserFunctions(browser_funcs);
+  return NPERR_NO_ERROR;
+}
+
+NPError NPAPI NP_GetEntryPoints(NPPluginFuncs* plugin_funcs) {
+  plugin_funcs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
+  plugin_funcs->size = sizeof(plugin_funcs);
+  plugin_funcs->newp = NPP_New;
+  plugin_funcs->destroy = NPP_Destroy;
+  plugin_funcs->setwindow = NPP_SetWindow;
+  plugin_funcs->newstream = NPP_NewStream;
+  plugin_funcs->destroystream = NPP_DestroyStream;
+  plugin_funcs->asfile = NULL;
+  plugin_funcs->writeready = NPP_WriteReady;
+  plugin_funcs->write = NPP_Write;
+  plugin_funcs->print = NPP_Print;
+  plugin_funcs->event = NULL;
+  plugin_funcs->urlnotify = NPP_URLNotify;
+  plugin_funcs->getvalue = NPP_GetValue;
+  plugin_funcs->setvalue = NPP_SetValue;
+  return NPERR_NO_ERROR;
+}
+
+void NPAPI NP_Shutdown() {
+  DLOG(INFO) << __FUNCTION__;
+
+  npapi::UninitializeBrowserFunctions();
+
+  _pAtlModule->Unlock();  // matches Lock() inside NP_Initialize
+
+  DLOG_IF(ERROR, _pAtlModule->GetLockCount() != 0)
+        << "Being shut down but still have " << _pAtlModule->GetLockCount()
+        << " living objects";
+}
+
diff --git a/chrome_frame/chrome_frame_npapi_unittest.cc b/chrome_frame/chrome_frame_npapi_unittest.cc
new file mode 100644
index 0000000..d2c9b4e
--- /dev/null
+++ b/chrome_frame/chrome_frame_npapi_unittest.cc
@@ -0,0 +1,551 @@
+// Copyright (c) 2009 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.
+
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "chrome_frame/chrome_frame_automation.h"
+#include "chrome_frame/chrome_frame_npapi.h"
+#include "chrome_frame/ff_privilege_check.h"
+
+
+TEST(ChromeFrameNPAPI, DoesNotCrashOnConstruction) {
+  ChromeFrameNPAPI* api = new ChromeFrameNPAPI();
+  delete api;
+}
+
+
+// All mocks in the anonymous namespace.
+namespace {
+
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+
+// Make mocking privilege test easy.
+class MockPrivilegeTest {
+ public:
+  MockPrivilegeTest() {
+    CHECK(current_ == NULL);
+    current_ = this;
+  }
+  ~MockPrivilegeTest() {
+    CHECK(current_ == this);
+    current_ = NULL;
+  }
+
+  MOCK_METHOD1(IsFireFoxPrivilegedInvocation, bool(NPP));
+
+  static MockPrivilegeTest* current() { return current_; }
+
+ private:
+  static MockPrivilegeTest* current_;
+};
+
+MockPrivilegeTest* MockPrivilegeTest::current_ = NULL;
+
+const char* kMimeType = "application/chromeframe";
+// The default profile name is by default derived from the currently
+// running executable's name.
+const wchar_t* kDefaultProfileName = L"chrome_frame_unittests";
+
+
+class MockNPAPI: public ChromeFrameNPAPI {
+ public:
+  MockNPAPI() : mock_automation_client_(NULL) {}
+
+  MOCK_METHOD0(CreatePrefService, NpProxyService*());
+
+  MOCK_METHOD0(GetLocation, std::string());
+  MOCK_METHOD0(GetBrowserIncognitoMode, bool());
+
+  MOCK_METHOD1(JavascriptToNPObject, virtual NPObject*(const std::string&));
+
+  // Make public for test purposes
+  void OnAutomationServerReady() {
+    ChromeFrameNPAPI::OnAutomationServerReady();
+  }
+
+  // Neuter this (or it dchecks during testing).
+  void SetReadyState(READYSTATE new_state) {}
+
+  ChromeFrameAutomationClient* CreateAutomationClient() {
+    return mock_automation_client_;
+  }
+
+  ChromeFrameAutomationClient* mock_automation_client_;
+};
+
+class MockAutomationClient: public ChromeFrameAutomationClient {
+ public:
+  MOCK_METHOD6(Initialize, bool(ChromeFrameDelegate*, int, bool,
+                                const std::wstring&, const std::wstring&,
+                                bool));
+  MOCK_METHOD1(SetEnableExtensionAutomation, void(bool));  // NOLINT
+};
+
+class MockProxyService: public NpProxyService {
+ public:
+  MOCK_METHOD2(Initialize, bool(NPP instance, ChromeFrameAutomationClient*));
+};
+
+
+// Test fixture to allow testing the privileged NPAPI APIs
+class TestNPAPIPrivilegedApi: public ::testing::Test {
+ public:
+  virtual void SetUp() {
+    memset(&instance, 0, sizeof(instance));
+
+    // Gets owned & destroyed by mock_api (in the
+    // ChromeFramePlugin<T>::Uninitialize() function).
+    mock_automation = new MockAutomationClient;
+
+    mock_api.mock_automation_client_ = mock_automation;
+    mock_proxy = new MockProxyService;
+    mock_proxy->AddRef();
+    mock_proxy_holder.Attach(mock_proxy);
+  }
+
+  virtual void TearDown() {
+  }
+
+  void SetupPrivilegeTest(bool is_incognito,
+                          bool expect_privilege_check,
+                          bool is_privileged,
+                          const std::wstring& profile_name,
+                          const std::wstring& extra_args) {
+    EXPECT_CALL(mock_api, GetLocation())
+        .WillOnce(Return(std::string("http://www.google.com")));
+    EXPECT_CALL(mock_api, CreatePrefService())
+        .WillOnce(Return(mock_proxy));
+    EXPECT_CALL(mock_api, GetBrowserIncognitoMode())
+        .WillOnce(Return(is_incognito));
+
+    EXPECT_CALL(*mock_proxy, Initialize(_, _)).WillRepeatedly(Return(false));
+
+    EXPECT_CALL(*mock_automation,
+        Initialize(_, _, true, StrEq(profile_name), StrEq(extra_args), false))
+        .WillOnce(Return(true));
+
+    if (expect_privilege_check) {
+      EXPECT_CALL(mock_priv, IsFireFoxPrivilegedInvocation(_))
+          .WillOnce(Return(is_privileged));
+    } else {
+      EXPECT_CALL(mock_priv, IsFireFoxPrivilegedInvocation(_))
+          .Times(0);  // Fail if privilege check invoked.
+    }
+  }
+
+ public:
+  MockNPAPI mock_api;
+  MockAutomationClient* mock_automation;
+  MockProxyService* mock_proxy;
+  ScopedNsPtr<nsISupports> mock_proxy_holder;
+  MockPrivilegeTest mock_priv;
+  NPP_t instance;
+};
+
+}  // namespace
+
+// Stub for unittesting.
+bool IsFireFoxPrivilegedInvocation(NPP npp) {
+  MockPrivilegeTest* mock = MockPrivilegeTest::current();
+  if (!mock)
+    return false;
+
+  return mock->IsFireFoxPrivilegedInvocation(npp);
+}
+
+TEST_F(TestNPAPIPrivilegedApi, NoPrivilegeCheckWhenNoArguments) {
+  SetupPrivilegeTest(false,  // Not incognito
+                     false,  // Fail if privilege check is invoked.
+                     false,
+                     kDefaultProfileName,
+                     L"");   // No extra args to initialize.
+
+  // No arguments, no privilege requested.
+  EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType),
+                                  &instance,
+                                  NP_EMBED,
+                                  0, 0, 0));
+}
+
+TEST_F(TestNPAPIPrivilegedApi, NoPrivilegeCheckWhenZeroArgument) {
+  SetupPrivilegeTest(false,  // Not incognito
+                     false,  // Fail if privilege check is invoked.
+                     false,
+                     kDefaultProfileName,
+                     L"");   // No extra args to initialize.
+
+  // Privileged mode explicitly zero.
+  char* argn = "is_privileged";
+  char* argv = "0";
+  EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType),
+                                  &instance,
+                                  NP_EMBED,
+                                  1, &argn, &argv));
+}
+
+TEST_F(TestNPAPIPrivilegedApi, NotPrivilegedDoesNotAllowArgsOrProfile) {
+  SetupPrivilegeTest(false,  // Not incognito.
+                     true,   // Fail unless privilege check is invoked.
+                     false,  // Not privileged.
+                     kDefaultProfileName,
+                     L"");   // No extra arguments allowed.
+
+  char* argn[] = {
+    "privileged_mode",
+    "chrome_extra_arguments",
+    "chrome_profile_name",
+  };
+  char *argv[] = {
+    "1",
+    "foo",
+    "bar",
+  };
+  EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType),
+                                  &instance,
+                                  NP_EMBED,
+                                  arraysize(argn), argn, argv));
+}
+
+TEST_F(TestNPAPIPrivilegedApi, PrivilegedAllowsArgsAndProfile) {
+  SetupPrivilegeTest(false,  // Not incognito.
+                     true,  // Fail unless privilege check is invoked.
+                     true,  // Privileged mode.
+                     L"custom_profile_name",  // Custom profile expected.
+                     L"-bar=far");  // Extra arguments expected
+
+  // With privileged mode we expect automation to be enabled.
+  EXPECT_CALL(*mock_automation, SetEnableExtensionAutomation(true))
+      .Times(1);
+
+  char* argn[] = {
+    "privileged_mode",
+    "chrome_extra_arguments",
+    "chrome_profile_name",
+  };
+  char *argv[] = {
+    "1",
+    "-bar=far",
+    "custom_profile_name",
+  };
+  EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType),
+                                  &instance,
+                                  NP_EMBED,
+                                  arraysize(argn), argn, argv));
+
+  // Since we're mocking out ChromeFrameAutomationClient::Initialize, we need
+  // to tickle this explicitly.
+  mock_api.OnAutomationServerReady();
+}
+
+
+namespace {
+
+static const NPIdentifier kOnPrivateMessageId =
+    reinterpret_cast<NPIdentifier>(0x100);
+static const NPIdentifier kPostPrivateMessageId =
+    reinterpret_cast<NPIdentifier>(0x100);
+
+
+class MockNetscapeFuncs {
+ public:
+  MockNetscapeFuncs() {
+    CHECK(NULL == current_);
+    current_ = this;
+  }
+
+  ~MockNetscapeFuncs() {
+    CHECK(this == current_);
+    current_ = NULL;
+  }
+
+  MOCK_METHOD3(GetValue, NPError(NPP, NPNVariable, void *));
+  MOCK_METHOD3(GetStringIdentifiers, void(const NPUTF8 **,
+                                          int32_t,
+                                          NPIdentifier *));  // NOLINT
+  MOCK_METHOD1(RetainObject, NPObject*(NPObject*));  // NOLINT
+  MOCK_METHOD1(ReleaseObject, void(NPObject*));  // NOLINT
+
+
+  void GetPrivilegedStringIdentifiers(const NPUTF8 **names,
+                                      int32_t name_count,
+                                      NPIdentifier *identifiers) {
+    for (int32_t i = 0; i < name_count; ++i) {
+      if (0 == strcmp(names[i], "onprivatemessage")) {
+        identifiers[i] = kOnPrivateMessageId;
+      } else if (0 == strcmp(names[i], "postPrivateMessage")) {
+        identifiers[i] = kPostPrivateMessageId;
+      } else {
+        identifiers[i] = 0;
+      }
+    }
+  }
+
+  static const NPNetscapeFuncs* netscape_funcs() {
+    return &netscape_funcs_;
+  }
+
+ private:
+  static NPError MockGetValue(NPP instance,
+                              NPNVariable variable,
+                              void *ret_value) {
+    DCHECK(current_);
+    return current_->GetValue(instance, variable, ret_value);
+  }
+
+  static void MockGetStringIdentifiers(const NPUTF8 **names,
+                                       int32_t name_count,
+                                       NPIdentifier *identifiers) {
+    DCHECK(current_);
+    return current_->GetStringIdentifiers(names, name_count, identifiers);
+  }
+
+  static NPObject* MockRetainObject(NPObject* obj) {
+    DCHECK(current_);
+    return current_->RetainObject(obj);
+  }
+
+  static void MockReleaseObject(NPObject* obj) {
+    DCHECK(current_);
+    current_->ReleaseObject(obj);
+  }
+
+  static MockNetscapeFuncs* current_;
+  static NPNetscapeFuncs netscape_funcs_;
+};
+
+MockNetscapeFuncs* MockNetscapeFuncs::current_ = NULL;
+NPNetscapeFuncs MockNetscapeFuncs::netscape_funcs_ = {
+  0,   // size
+  0,   // version
+  NULL,   // geturl
+  NULL,   // posturl
+  NULL,   // requestread
+  NULL,   // newstream
+  NULL,   // write
+  NULL,   // destroystream
+  NULL,   // status
+  NULL,   // uagent
+  NULL,   // memalloc
+  NULL,   // memfree
+  NULL,   // memflush
+  NULL,   // reloadplugins
+  NULL,   // getJavaEnv
+  NULL,   // getJavaPeer
+  NULL,   // geturlnotify
+  NULL,   // posturlnotify
+  MockGetValue,   // getvalue
+  NULL,   // setvalue
+  NULL,   // invalidaterect
+  NULL,   // invalidateregion
+  NULL,   // forceredraw
+  NULL,   // getstringidentifier
+  MockGetStringIdentifiers,   // getstringidentifiers
+  NULL,   // getintidentifier
+  NULL,   // identifierisstring
+  NULL,   // utf8fromidentifier
+  NULL,   // intfromidentifier
+  NULL,   // createobject
+  MockRetainObject,   // retainobject
+  MockReleaseObject,   // releaseobject
+  NULL,   // invoke
+  NULL,   // invokeDefault
+  NULL,   // evaluate
+  NULL,   // getproperty
+  NULL,   // setproperty
+  NULL,   // removeproperty
+  NULL,   // hasproperty
+  NULL,   // hasmethod
+  NULL,   // releasevariantvalue
+  NULL,   // setexception
+  NULL,   // pushpopupsenabledstate
+  NULL,   // poppopupsenabledstate
+  NULL,   // enumerate
+  NULL,   // pluginthreadasynccall
+  NULL,   // construct
+};
+
+NPObject* const kMockNPObject = reinterpret_cast<NPObject*>(0xCafeBabe);
+
+class TestNPAPIPrivilegedProperty: public TestNPAPIPrivilegedApi {
+ public:
+  virtual void SetUp() {
+    TestNPAPIPrivilegedApi::SetUp();
+    npapi::InitializeBrowserFunctions(
+        const_cast<NPNetscapeFuncs*>(mock_funcs.netscape_funcs()));
+
+    // Expect calls to release and retain objects.
+    EXPECT_CALL(mock_funcs, RetainObject(kMockNPObject))
+        .WillRepeatedly(Return(kMockNPObject));
+    EXPECT_CALL(mock_funcs, ReleaseObject(kMockNPObject))
+        .WillRepeatedly(Return());
+
+    // And we should expect SetEnableExtensionAutomation to be called
+    // for privileged tests.
+    EXPECT_CALL(*mock_automation, SetEnableExtensionAutomation(true))
+       .WillRepeatedly(Return());
+
+    // Initializes identifiers.
+    EXPECT_CALL(mock_funcs, GetStringIdentifiers(_, _, _))
+        .WillRepeatedly(
+            Invoke(&mock_funcs,
+                   &MockNetscapeFuncs::GetPrivilegedStringIdentifiers));
+    MockNPAPI::InitializeIdentifiers();
+  }
+
+  virtual void TearDown() {
+    npapi::UninitializeBrowserFunctions();
+    TestNPAPIPrivilegedApi::TearDown();
+  }
+
+ public:
+  MockNetscapeFuncs mock_funcs;
+};
+
+
+}  // namespace
+
+TEST_F(TestNPAPIPrivilegedProperty,
+       NonPrivilegedOnPrivateMessageInitializationFails) {
+  // Attempt setting onprivatemessage when not privileged.
+  SetupPrivilegeTest(false,  // not incognito.
+                     true,   // expect privilege check.
+                     false,   // not privileged.
+                     kDefaultProfileName,
+                     L"");
+
+  char* on_private_message_str = "onprivatemessage()";
+  EXPECT_CALL(mock_api, JavascriptToNPObject(StrEq(on_private_message_str)))
+      .Times(0);  // this should not be called.
+
+  char* argn[] = {
+    "privileged_mode",
+    "onprivatemessage",
+  };
+  char* argv[] = {
+    "1",
+    on_private_message_str,
+  };
+  EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType),
+                                  &instance,
+                                  NP_EMBED,
+                                  arraysize(argn), argn, argv));
+  // Shouldn't be able to retrieve it.
+  NPVariant var;
+  VOID_TO_NPVARIANT(var);
+  EXPECT_FALSE(mock_api.GetProperty(kOnPrivateMessageId, &var));
+  EXPECT_TRUE(NPVARIANT_IS_VOID(var));
+
+  mock_api.Uninitialize();
+}
+
+TEST_F(TestNPAPIPrivilegedProperty,
+       PrivilegedOnPrivateMessageInitializationSucceeds) {
+  // Set onprivatemessage argument when privileged.
+  SetupPrivilegeTest(false,  // not incognito.
+                     true,   // expect privilege check.
+                     true,   // privileged.
+                     kDefaultProfileName,
+                     L"");
+
+  char* on_private_message_str = "onprivatemessage()";
+  NPObject* on_private_object = kMockNPObject;
+  EXPECT_CALL(mock_api, JavascriptToNPObject(StrEq(on_private_message_str)))
+      .WillOnce(Return(on_private_object));
+
+  char* argn[] = {
+    "privileged_mode",
+    "onprivatemessage",
+  };
+  char* argv[] = {
+    "1",
+    on_private_message_str,
+  };
+  EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType),
+                                  &instance,
+                                  NP_EMBED,
+                                  arraysize(argn), argn, argv));
+  // The property should have been set, verify that
+  // we can retrieve it and test it for correct value.
+  NPVariant var;
+  VOID_TO_NPVARIANT(var);
+  EXPECT_TRUE(mock_api.GetProperty(kOnPrivateMessageId, &var));
+  EXPECT_TRUE(NPVARIANT_IS_OBJECT(var));
+  EXPECT_EQ(kMockNPObject, NPVARIANT_TO_OBJECT(var));
+
+  mock_api.Uninitialize();
+}
+
+TEST_F(TestNPAPIPrivilegedProperty,
+       NonPrivilegedOnPrivateMessageAssignmentFails) {
+  // Assigning to onprivatemessage when not privileged should fail.
+  SetupPrivilegeTest(false,  // not incognito.
+                     true,   // expect privilege check.
+                     false,   // not privileged.
+                     kDefaultProfileName,
+                     L"");
+
+  char* argn = "privileged_mode";
+  char* argv = "1";
+  EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType),
+                                  &instance,
+                                  NP_EMBED,
+                                  1, &argn, &argv));
+
+  NPVariant var = {};
+  OBJECT_TO_NPVARIANT(kMockNPObject, var);
+  // Setting should fail.
+  EXPECT_FALSE(mock_api.SetProperty(kOnPrivateMessageId, &var));
+
+  // And so should getting.
+  NULL_TO_NPVARIANT(var);
+  EXPECT_FALSE(mock_api.GetProperty(kOnPrivateMessageId, &var));
+
+  mock_api.Uninitialize();
+}
+
+TEST_F(TestNPAPIPrivilegedProperty,
+       PrivilegedOnPrivateMessageAssignmentSucceeds) {
+  // Assigning to onprivatemessage when privileged should succeed.
+  SetupPrivilegeTest(false,  // not incognito.
+                     true,   // expect privilege check.
+                     true,   // privileged.
+                     kDefaultProfileName,
+                     L"");
+
+  char* argn = "privileged_mode";
+  char* argv = "1";
+  EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType),
+                                  &instance,
+                                  NP_EMBED,
+                                  1, &argn, &argv));
+
+  NPVariant var = {};
+  VOID_TO_NPVARIANT(var);
+  // Getting the property when NULL fails under current implementation.
+  // I shouldn't have thought this is correct behavior, e.g. I should
+  // have thought retrieving the NULL should succeed, but this is consistent
+  // with how other properties behave.
+  // TODO(robertshield): investigate and/or fix.
+  EXPECT_FALSE(mock_api.GetProperty(kOnPrivateMessageId, &var));
+  // EXPECT_TRUE(NPVARIANT_IS_OBJECT(var));
+  // EXPECT_EQ(NULL, NPVARIANT_TO_OBJECT(var));
+
+  // Setting the property should succeed.
+  OBJECT_TO_NPVARIANT(kMockNPObject, var);
+  EXPECT_TRUE(mock_api.SetProperty(kOnPrivateMessageId, &var));
+
+  // And fething it should return the value we just set.
+  VOID_TO_NPVARIANT(var);
+  EXPECT_TRUE(mock_api.GetProperty(kOnPrivateMessageId, &var));
+  EXPECT_TRUE(NPVARIANT_IS_OBJECT(var));
+  EXPECT_EQ(kMockNPObject, NPVARIANT_TO_OBJECT(var));
+
+  mock_api.Uninitialize();
+}
+
+// TODO(siggi): test invoking postPrivateMessage.
diff --git a/chrome_frame/chrome_frame_plugin.h b/chrome_frame/chrome_frame_plugin.h
new file mode 100644
index 0000000..b0814bb0
--- /dev/null
+++ b/chrome_frame/chrome_frame_plugin.h
@@ -0,0 +1,214 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_CHROME_FRAME_PLUGIN_H_
+#define CHROME_FRAME_CHROME_FRAME_PLUGIN_H_
+
+#include "base/win_util.h"
+#include "chrome_frame/chrome_frame_automation.h"
+#include "chrome_frame/utils.h"
+
+#define IDC_ABOUT_CHROME_FRAME 40018
+
+// A class to implement common functionality for all types of
+// plugins: NPAPI. ActiveX and ActiveDoc
+template <typename T>
+class ChromeFramePlugin : public ChromeFrameDelegateImpl {
+ public:
+  ChromeFramePlugin()
+      : ignore_setfocus_(false),
+        is_privileged_(false) {
+  }
+  ~ChromeFramePlugin() {
+    Uninitialize();
+  }
+
+BEGIN_MSG_MAP(ChromeFrameActivex)
+  MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
+  MESSAGE_HANDLER(WM_SIZE, OnSize)
+  MESSAGE_HANDLER(WM_PARENTNOTIFY, OnParentNotify)
+END_MSG_MAP()
+
+  bool Initialize() {
+    DCHECK(!automation_client_.get());
+    automation_client_.reset(CreateAutomationClient());
+    if (!automation_client_.get()) {
+      NOTREACHED() << "new ChromeFrameAutomationClient";
+      return false;
+    }
+
+    return true;
+  }
+
+  void Uninitialize() {
+    if (automation_client_.get()) {
+      automation_client_->Uninitialize();
+      automation_client_.reset();
+    }
+  }
+
+  bool InitializeAutomation(const std::wstring& profile_name,
+                            const std::wstring& extra_chrome_arguments,
+                            bool incognito) {
+    // We don't want to do incognito when privileged, since we're
+    // running in browser chrome or some other privileged context.
+    bool incognito_mode = !is_privileged_ && incognito;
+    return automation_client_->Initialize(this, kCommandExecutionTimeout, true,
+                                          profile_name, extra_chrome_arguments,
+                                          incognito_mode);
+  }
+
+  // ChromeFrameDelegate implementation
+  virtual WindowType GetWindow() const {
+    return (static_cast<const T*>(this))->m_hWnd;
+  }
+
+  virtual void GetBounds(RECT* bounds) {
+    if (bounds) {
+      if (::IsWindow(GetWindow())) {
+        (static_cast<T*>(this))->GetClientRect(bounds);
+      }
+    }
+  }
+  virtual std::string GetDocumentUrl() {
+    return document_url_;
+  }
+  virtual void OnAutomationServerReady() {
+    // Issue the extension automation request if we're privileged to
+    // allow this control to handle extension requests from Chrome.
+    if (is_privileged_)
+      automation_client_->SetEnableExtensionAutomation(true);
+  }
+
+  virtual bool IsValid() const {
+    return automation_client_.get() != NULL;
+  }
+
+ protected:
+  virtual void OnNavigationFailed(int tab_handle, int error_code,
+                                  const GURL& gurl) {
+    OnLoadFailed(error_code, gurl.spec());
+  }
+
+  virtual void OnHandleContextMenu(int tab_handle, HANDLE menu_handle,
+                                   int x_pos, int y_pos, int align_flags) {
+    if (!menu_handle || !automation_client_.get()) {
+      NOTREACHED();
+      return;
+    }
+
+    // TrackPopupMenuEx call will fail on IE on Vista running
+    // in low integrity mode. We DO seem to be able to enumerate the menu
+    // though, so just clone it and show the copy:
+    HMENU copy = UtilCloneContextMenu(static_cast<HMENU>(menu_handle));
+    if (!copy)
+      return;
+
+    T* pThis = static_cast<T*>(this);
+    if (pThis->PreProcessContextMenu(copy)) {
+      UINT flags = align_flags | TPM_LEFTBUTTON | TPM_RETURNCMD | TPM_RECURSE;
+      UINT selected = TrackPopupMenuEx(copy, flags, x_pos, y_pos, GetWindow(),
+                                       NULL);
+      if (selected != 0 && !pThis->HandleContextMenuCommand(selected)) {
+        automation_client_->SendContextMenuCommandToChromeFrame(selected);
+      }
+    }
+
+    DestroyMenu(copy);
+  }
+
+  LRESULT OnSetFocus(UINT message, WPARAM wparam, LPARAM lparam,
+                     BOOL& handled) {  // NO_LINT
+    if (!ignore_setfocus_ && automation_client_ != NULL) {
+      TabProxy* tab = automation_client_->tab();
+      HWND chrome_window = automation_client_->tab_window();
+      if (tab && ::IsWindow(chrome_window)) {
+        DLOG(INFO) << "Setting initial focus";
+        tab->SetInitialFocus(win_util::IsShiftPressed());
+      }
+    }
+
+    return 0;
+  }
+
+  LRESULT OnSize(UINT message, WPARAM wparam, LPARAM lparam,
+                 BOOL& handled) {  // NO_LINT
+    handled = FALSE;
+    // When we get resized, we need to resize the external tab window too.
+    if (automation_client_.get())
+      automation_client_->Resize(LOWORD(lparam), HIWORD(lparam),
+                                 SWP_NOACTIVATE | SWP_NOZORDER);
+    return 0;
+  }
+
+  LRESULT OnParentNotify(UINT message, WPARAM wparam, LPARAM lparam,
+                         BOOL& handled) {  // NO_LINT
+    switch (LOWORD(wparam)) {
+      case WM_LBUTTONDOWN:
+      case WM_MBUTTONDOWN:
+      case WM_RBUTTONDOWN:
+      case WM_XBUTTONDOWN: {
+        // If we got activated via mouse click on the external tab,
+        // we need to update the state of this thread and tell the
+        // browser that we now have the focus.
+        HWND focus = ::GetFocus();
+        HWND plugin_window = GetWindow();
+        if (focus != plugin_window && !IsChild(plugin_window, focus)) {
+          ignore_setfocus_ = true;
+          SetFocus(plugin_window);
+          ignore_setfocus_ = false;
+        }
+        break;
+      }
+    }
+
+    return 0;
+  }
+
+  // Return true if context menu should be displayed. The menu could be
+  // modified as well (enable/disable commands, add/remove items).
+  // Override in most-derived class if needed.
+  bool PreProcessContextMenu(HMENU menu) {
+    // Add an "About" item. 
+    // TODO: The string should be localized and menu should
+    // be modified in ExternalTabContainer:: once we go public.
+    AppendMenu(menu, MF_STRING, IDC_ABOUT_CHROME_FRAME,
+        L"About Chrome Frame...");
+    return true;
+  }
+
+  // Return true if menu command is processed, otherwise the command will be
+  // passed to Chrome for execution. Override in most-derived class if needed.
+  bool HandleContextMenuCommand(UINT cmd) {
+    return false;
+  }
+
+  // Allow overriding the type of automation client used, for unit tests.
+  virtual ChromeFrameAutomationClient* CreateAutomationClient() {
+    return new ChromeFrameAutomationClient;
+  }
+
+ protected:
+  // Our gateway to chrome land
+  scoped_ptr<ChromeFrameAutomationClient> automation_client_;
+
+  // Url of the containing document.
+  std::string document_url_;
+
+  // We set this flag when we're taking the focus ourselves
+  // and notifying the host browser that we're doing so.
+  // When the flag is not set, we transfer the focus to chrome.
+  bool ignore_setfocus_;
+
+  // The plugin is privileged if it is:
+  // * Invoked by a window running under the system principal in FireFox.
+  // * Being hosted by a custom host exposing the SID_ChromeFramePrivileged
+  //   service.
+  //
+  // When privileged, additional interfaces are made available to the user.
+  bool is_privileged_;
+};
+
+#endif  // CHROME_FRAME_CHROME_FRAME_PLUGIN_H_
+
diff --git a/chrome_frame/chrome_frame_unittest_main.cc b/chrome_frame/chrome_frame_unittest_main.cc
new file mode 100644
index 0000000..70157a40
--- /dev/null
+++ b/chrome_frame/chrome_frame_unittest_main.cc
@@ -0,0 +1,48 @@
+// Copyright 2009, Google Inc.
+// 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.
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "gtest/gtest.h"
+
+class ObligatoryModule: public CAtlExeModuleT<ObligatoryModule> {
+};
+
+ObligatoryModule g_obligatory_atl_module;
+
+int main(int argc, char** argv) {
+  testing::InitGoogleTest(&argc, argv);
+
+  base::AtExitManager at_exit_manager;
+  CommandLine::Init(argc, argv);
+
+  RUN_ALL_TESTS();
+}
diff --git a/chrome_frame/chrome_launcher.cc b/chrome_frame/chrome_launcher.cc
new file mode 100644
index 0000000..7670aec
--- /dev/null
+++ b/chrome_frame/chrome_launcher.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 2009 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.

+

+#include "chrome_frame/chrome_launcher.h"

+

+#include "base/base_switches.h"

+#include "base/command_line.h"

+#include "base/file_util.h"

+#include "base/logging.h"

+#include "base/path_service.h"

+#include "chrome/common/chrome_constants.h"

+#include "chrome/common/chrome_switches.h"

+#include "chrome_frame/chrome_frame_automation.h"

+#include "chrome_frame/crash_report.h"

+

+namespace chrome_launcher {

+

+const wchar_t kLauncherExeBaseName[] = L"chrome_launcher.exe";

+

+// These are the switches we will allow (along with their values) in the

+// safe-for-Low-Integrity version of the Chrome command line.

+const wchar_t* kAllowedSwitches[] = {

+  switches::kAutomationClientChannelID,

+  switches::kDisableMetrics,

+  switches::kNoFirstRun,

+  switches::kUserDataDir,

+  switches::kLoadExtension,

+};

+

+CommandLine* CreateLaunchCommandLine() {

+  // TODO(joi) As optimization, could launch Chrome directly when running at

+  // medium integrity.  (Requires bringing in code to read SIDs, etc.)

+

+  // The launcher EXE will be in the same directory as the npchrome_tab DLL,

+  // so create a full path to it based on this assumption.  Since our unit

+  // tests also use this function, and live in the directory above, we test

+  // existence of the file and try the path that includes the /servers/

+  // directory if needed.

+  FilePath module_path;

+  if (PathService::Get(base::FILE_MODULE, &module_path)) {

+    FilePath current_dir = module_path.DirName();

+    FilePath same_dir_path = current_dir.Append(kLauncherExeBaseName);

+    if (file_util::PathExists(same_dir_path)) {

+      return new CommandLine(same_dir_path.ToWStringHack());

+    } else {

+      FilePath servers_path = 

+          current_dir.Append(L"servers").Append(kLauncherExeBaseName);

+      DCHECK(file_util::PathExists(servers_path)) <<

+          "What module is this? It's not in 'servers' or main output dir.";

+      return new CommandLine(servers_path.ToWStringHack());

+    }

+  } else {

+    NOTREACHED();

+    return NULL;

+  }

+}

+

+void SanitizeCommandLine(const CommandLine& original, CommandLine* sanitized) {

+  int num_sanitized_switches = 0;

+  for (int i = 0; i < arraysize(kAllowedSwitches); ++i) {

+    const wchar_t* current_switch = kAllowedSwitches[i];

+    if (original.HasSwitch(current_switch)) {

+      ++num_sanitized_switches;

+      std::wstring switch_value = original.GetSwitchValue(current_switch);

+      if (0 == switch_value.length()) {

+        sanitized->AppendSwitch(current_switch);

+      } else {

+        sanitized->AppendSwitchWithValue(current_switch, switch_value);

+      }

+    }

+  }

+  if (num_sanitized_switches != original.GetSwitchCount()) {

+    NOTREACHED();

+    LOG(ERROR) << "Original command line from Low Integrity had switches "

+        << "that are not on our whitelist.";

+  }

+}

+

+bool SanitizeAndLaunchChrome(const wchar_t* command_line) {

+  std::wstring command_line_with_program(L"dummy.exe ");

+  command_line_with_program += command_line;

+  CommandLine original(L"");

+  original.ParseFromString(command_line_with_program);

+  CommandLine sanitized(GetChromeExecutablePath());

+  SanitizeCommandLine(original, &sanitized);

+

+  return base::LaunchApp(sanitized.command_line_string(), false, false, NULL);

+}

+

+std::wstring GetChromeExecutablePath() {

+  std::wstring cur_path;

+  PathService::Get(base::DIR_MODULE, &cur_path);

+  file_util::AppendToPath(&cur_path, chrome::kBrowserProcessExecutableName);

+

+  // The installation model for Chrome places the DLLs in a versioned

+  // sub-folder one down from the Chrome executable. If we fail to find

+  // chrome.exe in the current path, try looking one up and launching that

+  // instead.

+  if (!file_util::PathExists(cur_path)) {

+    PathService::Get(base::DIR_MODULE, &cur_path);

+    file_util::UpOneDirectory(&cur_path);

+    file_util::AppendToPath(&cur_path, chrome::kBrowserProcessExecutableName);

+  }

+

+  return cur_path;

+}

+

+}  // namespace chrome_launcher

+

+// Entrypoint that implements the logic of chrome_launcher.exe.

+int CALLBACK CfLaunchChrome() {

+  if (chrome_launcher::SanitizeAndLaunchChrome(::GetCommandLine())) {

+    return ERROR_SUCCESS;

+  } else {

+    return ERROR_OPEN_FAILED;

+  }

+}

+

+// Compile-time check to see that the type CfLaunchChromeProc is correct.

+#ifndef NODEBUG

+namespace {

+chrome_launcher::CfLaunchChromeProc cf_launch_chrome = CfLaunchChrome;

+}  // namespace

+#endif  // NODEBUG

diff --git a/chrome_frame/chrome_launcher.h b/chrome_frame/chrome_launcher.h
new file mode 100644
index 0000000..72b198a
--- /dev/null
+++ b/chrome_frame/chrome_launcher.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2009 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.

+

+#ifndef CHROME_FRAME_CHROME_LAUNCHER_H_

+#define CHROME_FRAME_CHROME_LAUNCHER_H_

+

+#include <string>

+

+class CommandLine;

+

+namespace chrome_launcher {

+

+// The base name of the chrome_launcher.exe file.

+extern const wchar_t kLauncherExeBaseName[];

+

+// Creates a command line suitable for launching Chrome.  You can add any

+// flags needed before launching.

+//

+// The command-line may use the Chrome executable directly, or use an in-between

+// process if needed for security/elevation purposes.  You must delete the

+// returned command line.

+CommandLine* CreateLaunchCommandLine();

+

+// Fills in a new command line from the flags on this process's command line

+// that we are allowing Low Integrity to invoke.

+//

+// Logs a warning for any flags that were passed that are not allowed to be

+// invoked by Low Integrity.

+void SanitizeCommandLine(const CommandLine& original, CommandLine* sanitized);

+

+// Given a command-line without an initial program part, launch our associated

+// chrome.exe with a sanitized version of that command line. Returns true iff 

+// successful.

+bool SanitizeAndLaunchChrome(const wchar_t* command_line);

+

+// Returns the full path to the Chrome executable.

+std::wstring GetChromeExecutablePath();

+

+// The type of the CfLaunchChrome entrypoint exported from this DLL.

+typedef int (__stdcall *CfLaunchChromeProc)();

+

+}  // namespace chrome_launcher

+

+#endif  // CHROME_FRAME_CHROME_LAUNCHER_H_

diff --git a/chrome_frame/chrome_launcher_main.cc b/chrome_frame/chrome_launcher_main.cc
new file mode 100644
index 0000000..5347f5a4
--- /dev/null
+++ b/chrome_frame/chrome_launcher_main.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2009 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.

+

+#include <windows.h>

+

+#include "chrome_frame/chrome_launcher.h"

+

+// We want to keep this EXE tiny, so we avoid all dependencies and link to no

+// libraries, and we do not use the C runtime.

+//

+// To catch errors in debug builds, we define an extremely simple assert macro.

+#ifndef NDEBUG

+#define CLM_ASSERT(x) do { if (!(x)) { ::DebugBreak(); } } while (false)

+#else

+#define CLM_ASSERT(x)

+#endif  // NDEBUG

+

+// In release builds, we skip the standard library completely to minimize

+// size.  This is more work in debug builds, and unnecessary, hence the

+// different signatures.

+#ifndef NDEBUG

+int APIENTRY wWinMain(HINSTANCE, HINSTANCE, wchar_t*, int) {

+#else

+extern "C" void __cdecl WinMainCRTStartup() {

+#endif  // NDEBUG

+  // This relies on the chrome_launcher.exe residing in the same directory

+  // as our DLL.  We build a full path to avoid loading it from any other

+  // directory in the DLL search path.

+  //

+  // The code is a bit verbose because we can't use the standard library.

+  const wchar_t kBaseName[] = L"npchrome_tab.dll";

+  wchar_t file_path[MAX_PATH + (sizeof(kBaseName) / sizeof(kBaseName[0])) + 1];

+  file_path[0] = L'\0';

+  ::GetModuleFileName(::GetModuleHandle(NULL), file_path, MAX_PATH);

+

+  // Find index of last slash, and null-terminate the string after it.

+  //

+  // Proof for security purposes, since we can't use the safe string

+  // manipulation functions from the runtime:

+  // - File_path is always null-terminated, by us initially and by 

+  //   ::GetModuleFileName if it puts anything into the buffer.

+  // - If there is no slash in the path then it's a relative path, not an

+  //   absolute one, and the code ends up creating a relative path to

+  //   npchrome_tab.dll.

+  // - It's safe to use lstrcatW since we know the maximum length of both

+  //   parts we are concatenating, and we know the buffer will fit them in

+  //   the worst case.

+  int slash_index = lstrlenW(file_path);

+  // Invariant: 0 <= slash_index < MAX_PATH

+  CLM_ASSERT(slash_index > 0);

+  while (slash_index > 0 && file_path[slash_index] != L'\\')

+    --slash_index;

+  // Invariant: 0 <= slash_index < MAX_PATH and it is either the index of 

+  // the last \ in the path, or 0.

+  if (slash_index != 0)

+    ++slash_index;  // don't remove the last '\'

+  file_path[slash_index] = L'\0';

+

+  lstrcatW(file_path, kBaseName);

+

+  UINT exit_code = ERROR_FILE_NOT_FOUND;

+  HMODULE chrome_tab = ::LoadLibrary(file_path);

+  CLM_ASSERT(chrome_tab);

+  if (chrome_tab) {

+    chrome_launcher::CfLaunchChromeProc proc = 

+        reinterpret_cast<chrome_launcher::CfLaunchChromeProc>(

+            ::GetProcAddress(chrome_tab, "CfLaunchChrome"));

+    CLM_ASSERT(proc);

+    if (proc) {

+      exit_code = proc();

+    } else {

+      exit_code = ERROR_INVALID_FUNCTION;

+    }

+

+    ::FreeLibrary(chrome_tab);

+  }

+  ::ExitProcess(exit_code);

+}

diff --git a/chrome_frame/chrome_launcher_unittest.cc b/chrome_frame/chrome_launcher_unittest.cc
new file mode 100644
index 0000000..1181f096
--- /dev/null
+++ b/chrome_frame/chrome_launcher_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2009 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.

+

+#include "base/command_line.h"

+#include "base/logging.h"

+#include "chrome/common/chrome_switches.h"

+#include "chrome_frame/chrome_launcher.h"

+#include "testing/gtest/include/gtest/gtest.h"

+

+namespace {

+

+// Utility class to disable logging.  Required to disable DCHECKs that some

+// of our tests would otherwise trigger.

+class LogDisabler {

+ public:

+  LogDisabler() {

+    initial_log_level_ = logging::GetMinLogLevel();

+    logging::SetMinLogLevel(logging::LOG_FATAL + 1);

+  }

+

+  ~LogDisabler() {

+    logging::SetMinLogLevel(initial_log_level_);

+  }

+

+ private:

+  int initial_log_level_;

+};

+

+}  // namespace

+

+TEST(ChromeLauncher, SanitizeCommandLine) {

+  CommandLine bad(L"dummy.exe");

+  bad.AppendSwitch(switches::kDisableMetrics);  // in whitelist

+  bad.AppendSwitchWithValue(switches::kLoadExtension, L"foo");  // in whitelist

+  bad.AppendSwitch(L"no-such-switch");  // does not exist

+  bad.AppendSwitch(switches::kHomePage);  // exists but not in whitelist

+

+  LogDisabler no_dchecks;

+

+  CommandLine sanitized(L"dumbo.exe");

+  chrome_launcher::SanitizeCommandLine(bad, &sanitized);

+  EXPECT_TRUE(sanitized.HasSwitch(switches::kDisableMetrics));

+  EXPECT_TRUE(sanitized.HasSwitch(switches::kLoadExtension));

+  EXPECT_FALSE(sanitized.HasSwitch(L"no-such-switch"));

+  EXPECT_FALSE(sanitized.HasSwitch(switches::kHomePage));

+

+  EXPECT_EQ(sanitized.GetSwitchValue(switches::kLoadExtension), L"foo");

+}

diff --git a/chrome_frame/chrome_protocol.cc b/chrome_frame/chrome_protocol.cc
new file mode 100644
index 0000000..87ca34e
--- /dev/null
+++ b/chrome_frame/chrome_protocol.cc
@@ -0,0 +1,85 @@
+// Copyright (c) 2009 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.
+
+// Implementation of ChromeProtocol
+#include "chrome_frame/chrome_protocol.h"
+
+#include "base/logging.h"
+
+static const wchar_t* kChromeMimeType = L"application/chromepage";
+
+// ChromeProtocol
+
+// Starts the class associated with the asynchronous pluggable protocol.
+STDMETHODIMP ChromeProtocol::Start(LPCWSTR url,
+                                   IInternetProtocolSink* prot_sink,
+                                   IInternetBindInfo* bind_info,
+                                   DWORD flags,
+                                   DWORD reserved) {
+  DLOG(INFO) << __FUNCTION__ << ": URL = " << url;
+  prot_sink->ReportProgress(BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE,
+                            kChromeMimeType);
+  prot_sink->ReportData(
+      BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION |
+      BSCF_DATAFULLYAVAILABLE,
+      0,
+      0);
+  return S_OK;
+}
+
+// Allows the pluggable protocol handler to continue processing data on the
+// apartment (or user interface) thread. This method is called in response
+// to a call to IInternetProtocolSink::Switch.
+STDMETHODIMP ChromeProtocol::Continue(PROTOCOLDATA* protocol_data) {
+  DLOG(INFO) << __FUNCTION__;
+  return S_OK;
+}
+
+// Aborts an operation in progress.
+STDMETHODIMP ChromeProtocol::Abort(HRESULT reason, DWORD options) {
+  DLOG(INFO) << __FUNCTION__;
+  return S_OK;
+}
+
+STDMETHODIMP ChromeProtocol::Terminate(DWORD options) {
+  DLOG(INFO) << __FUNCTION__;
+  return S_OK;
+}
+
+STDMETHODIMP ChromeProtocol::Suspend() {
+  return E_NOTIMPL;
+}
+STDMETHODIMP ChromeProtocol::Resume() {
+  return E_NOTIMPL;
+}
+
+// Reads data retrieved by the pluggable protocol handler.
+STDMETHODIMP ChromeProtocol::Read(void* buffer,
+                                  ULONG buffer_size_in_bytes,
+                                  ULONG* bytes_read) {
+  DLOG(INFO) << __FUNCTION__;
+  return S_FALSE;
+}
+
+// Moves the current seek offset.
+STDMETHODIMP ChromeProtocol::Seek(LARGE_INTEGER move_by,
+                              DWORD origin,
+                              ULARGE_INTEGER* new_position) {
+  DLOG(INFO) << __FUNCTION__;
+  return E_NOTIMPL;
+}
+
+// Locks the request so that IInternetProtocolRoot::Terminate ()
+// can be called and the remaining data can be read.
+STDMETHODIMP ChromeProtocol::LockRequest(DWORD options) {
+  DLOG(INFO) << __FUNCTION__;
+  return S_OK;
+}
+
+// Frees any resources associated with a lock. Called only if
+// IInternetProtocol::LockRequest () was called.
+STDMETHODIMP ChromeProtocol::UnlockRequest() {
+  DLOG(INFO) << __FUNCTION__;
+  return S_OK;
+}
diff --git a/chrome_frame/chrome_protocol.h b/chrome_frame/chrome_protocol.h
new file mode 100644
index 0000000..751162b
--- /dev/null
+++ b/chrome_frame/chrome_protocol.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_CHROME_PROTOCOL_H_
+#define CHROME_FRAME_CHROME_PROTOCOL_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include "chrome_frame/resource.h"
+#include "grit/chrome_frame_resources.h"
+
+// Include without path to make GYP build see it.
+#include "chrome_tab.h"  // NOLINT
+
+// ChromeProtocol
+class ATL_NO_VTABLE ChromeProtocol
+    : public CComObjectRootEx<CComSingleThreadModel>,
+      public CComCoClass<ChromeProtocol, &CLSID_ChromeProtocol>,
+      public IInternetProtocol {
+ public:
+  ChromeProtocol() {
+  }
+  DECLARE_REGISTRY_RESOURCEID(IDR_CHROMEPROTOCOL)
+
+  BEGIN_COM_MAP(ChromeProtocol)
+    COM_INTERFACE_ENTRY(IInternetProtocol)
+    COM_INTERFACE_ENTRY(IInternetProtocolRoot)
+  END_COM_MAP()
+
+  DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+  HRESULT FinalConstruct() {
+    return S_OK;
+  }
+  void FinalRelease() {
+  }
+
+ public:
+  // IInternetProtocolRoot
+  STDMETHOD(Start)(LPCWSTR url,
+                   IInternetProtocolSink* prot_sink,
+                   IInternetBindInfo* bind_info,
+                   DWORD flags,
+                   DWORD reserved);
+  STDMETHOD(Continue)(PROTOCOLDATA* protocol_data);
+  STDMETHOD(Abort)(HRESULT reason, DWORD options);
+  STDMETHOD(Terminate)(DWORD options);
+  STDMETHOD(Suspend)();
+  STDMETHOD(Resume)();
+
+  // IInternetProtocol based on IInternetProtocolRoot
+  STDMETHOD(Read)(void* buffer,
+                  ULONG buffer_size_in_bytes,
+                  ULONG* bytes_read);
+  STDMETHOD(Seek)(LARGE_INTEGER move_by,
+                  DWORD origin,
+                  ULARGE_INTEGER* new_position);
+  STDMETHOD(LockRequest)(DWORD options);
+  STDMETHOD(UnlockRequest)(void);
+};
+
+OBJECT_ENTRY_AUTO(__uuidof(ChromeProtocol), ChromeProtocol)
+
+#endif  // CHROME_FRAME_CHROME_PROTOCOL_H_
diff --git a/chrome_frame/chrome_protocol.rgs b/chrome_frame/chrome_protocol.rgs
new file mode 100644
index 0000000..747d7b50
--- /dev/null
+++ b/chrome_frame/chrome_protocol.rgs
@@ -0,0 +1,39 @@
+HKLM {

+  NoRemove Software {

+    NoRemove Classes {

+      ChromeTab.ChromeProtocol.1 = s 'ChromeProtocol Class' {

+        CLSID = s '{9875BFAF-B04D-445E-8A69-BE36838CDE3E}'

+      }

+      ChromeTab.ChromeProtocol = s 'ChromeProtocol Class' {

+        CLSID = s '{9875BFAF-B04D-445E-8A69-BE36838CDE3E}'

+        CurVer = s 'ChromeTab.ChromeProtocol.1'

+      }

+      NoRemove CLSID {

+        ForceRemove {9875BFAF-B04D-445E-8A69-BE36838CDE3E} = s 'ChromeProtocol Class' {

+          ProgID = s 'ChromeTab.ChromeProtocol.1'

+          VersionIndependentProgID = s 'ChromeTab.ChromeProtocol'

+          ForceRemove 'Programmable'

+          InprocServer32 = s '%MODULE%' {

+            val ThreadingModel = s 'Apartment'

+          }

+          val AppID = s '%APPID%'

+          'TypeLib' = s '{6F2664E1-FF6E-488A-BCD1-F4CA6001DFCC}'

+        }

+      }

+    }

+  }

+}

+

+HKLM {

+  NoRemove Software {

+    NoRemove Classes {

+      NoRemove Protocols {

+        NoRemove Handler {

+          NoRemove 'cf' {

+            val CLSID = s '{9875BFAF-B04D-445E-8A69-BE36838CDE3E}'

+          }

+        }

+      }

+    }

+  }

+}

diff --git a/chrome_frame/chrome_tab.cc b/chrome_frame/chrome_tab.cc
new file mode 100644
index 0000000..1b10ff6
--- /dev/null
+++ b/chrome_frame/chrome_tab.cc
@@ -0,0 +1,308 @@
+// Copyright (c) 2009 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.
+
+// chrome_tab.cc : Implementation of DLL Exports.
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/file_version_info.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/registry.h"
+#include "base/string_piece.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#include "chrome/common/chrome_constants.h"
+#include "grit/chrome_frame_resources.h"
+#include "chrome_frame/bho.h"
+#include "chrome_frame/chrome_frame_automation.h"
+#include "chrome_frame/chrome_launcher.h"
+#include "chrome_frame/crash_report.h"
+#include "chrome_frame/resource.h"
+#include "chrome_frame/utils.h"
+
+// Include without path to make GYP build see it.
+#include "chrome_tab.h"  // NOLINT
+
+static const wchar_t kBhoRegistryPath[] =
+    L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"
+    L"\\Browser Helper Objects";
+
+const wchar_t kBhoNoLoadExplorerValue[] = L"NoExplorer";
+
+class ChromeTabModule
+    : public AtlPerUserModule<CAtlDllModuleT<ChromeTabModule> > {
+ public:
+  typedef AtlPerUserModule<CAtlDllModuleT<ChromeTabModule> > ParentClass;
+
+  DECLARE_LIBID(LIBID_ChromeTabLib)
+  DECLARE_REGISTRY_APPID_RESOURCEID(IDR_CHROMETAB,
+                                    "{FD9B1B31-F4D8-436A-8F4F-D3C2E36733D3}")
+
+  // Override to add our SYSTIME binary value to registry scripts.
+  // See chrome_frame_activex.rgs for usage.
+  virtual HRESULT AddCommonRGSReplacements(IRegistrarBase* registrar) throw() {
+    HRESULT hr = ParentClass::AddCommonRGSReplacements(registrar);
+
+    if (SUCCEEDED(hr)) {
+      SYSTEMTIME local_time;
+      ::GetSystemTime(&local_time);
+      std::string hex(HexEncode(&local_time, sizeof(local_time)));
+      base::StringPiece sp_hex(hex);
+      hr = registrar->AddReplacement(L"SYSTIME",
+                                     base::SysNativeMBToWide(sp_hex).c_str());
+      DCHECK(SUCCEEDED(hr));
+    }
+
+    if (SUCCEEDED(hr)) {
+      std::wstring app_path(
+          chrome_launcher::GetChromeExecutablePath());
+      app_path = file_util::GetDirectoryFromPath(app_path);
+      hr = registrar->AddReplacement(L"CHROME_APPPATH", app_path.c_str());
+      DCHECK(SUCCEEDED(hr));
+    }
+
+    if (SUCCEEDED(hr)) {
+      hr = registrar->AddReplacement(L"CHROME_APPNAME",
+                                     chrome::kBrowserProcessExecutableName);
+      DCHECK(SUCCEEDED(hr));
+
+      // Fill in VERSION from the VERSIONINFO stored in the DLL's resources.
+      scoped_ptr<FileVersionInfo> module_version_info(
+          FileVersionInfo::CreateFileVersionInfoForCurrentModule());
+      DCHECK(module_version_info != NULL);
+      std::wstring file_version(module_version_info->file_version());
+      hr = registrar->AddReplacement(L"VERSION", file_version.c_str());
+      DCHECK(SUCCEEDED(hr));
+    }
+
+    if (SUCCEEDED(hr)) {
+      // Add the directory of chrome_launcher.exe.  This will be the same
+      // as the directory for the current DLL.
+      std::wstring module_dir;
+      FilePath module_path;
+      if (PathService::Get(base::FILE_MODULE, &module_path)) {
+        module_dir = module_path.DirName().ToWStringHack();
+      } else {
+        NOTREACHED();
+      }
+      hr = registrar->AddReplacement(L"CHROME_LAUNCHER_APPPATH",
+                                     module_dir.c_str());
+      DCHECK(SUCCEEDED(hr));
+    }
+
+    if (SUCCEEDED(hr)) {
+      // Add the filename of chrome_launcher.exe
+      hr = registrar->AddReplacement(L"CHROME_LAUNCHER_APPNAME",
+                                     chrome_launcher::kLauncherExeBaseName);
+      DCHECK(SUCCEEDED(hr));
+    }
+
+    return hr;
+  }
+};
+
+ChromeTabModule _AtlModule;
+
+base::AtExitManager* g_exit_manager = NULL;
+
+// DLL Entry Point
+extern "C" BOOL WINAPI DllMain(HINSTANCE instance,
+                               DWORD reason,
+                               LPVOID reserved) {
+  UNREFERENCED_PARAMETER(instance);
+  if (reason == DLL_PROCESS_ATTACH) {
+#ifndef NDEBUG
+    // Silence traces from the ATL registrar to reduce the log noise.
+    ATL::CTrace::s_trace.ChangeCategory(atlTraceRegistrar, 0,
+                                        ATLTRACESTATUS_DISABLED);
+#endif
+    InitializeCrashReporting(false, false);
+    g_exit_manager = new base::AtExitManager();
+    CommandLine::Init(0, NULL);
+    logging::InitLogging(NULL, logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG,
+                        logging::LOCK_LOG_FILE, logging::DELETE_OLD_LOG_FILE);
+  } else if (reason == DLL_PROCESS_DETACH) {
+    g_patch_helper.UnpatchIfNeeded();
+    delete g_exit_manager;
+    g_exit_manager = NULL;
+    ShutdownCrashReporting();
+  }
+  return _AtlModule.DllMain(reason, reserved);
+}
+
+#ifdef _MANAGED
+#pragma managed(pop)
+#endif
+
+const wchar_t kPostPlatformUAKey[] =
+    L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\"
+    L"User Agent\\Post Platform";
+const wchar_t kClockUserAgent[] = L"chromeframe";
+
+// To delete the clock user agent, set value to NULL.
+HRESULT SetClockUserAgent(const wchar_t* value) {
+  HRESULT hr;
+  RegKey ua_key;
+  if (ua_key.Create(HKEY_CURRENT_USER, kPostPlatformUAKey, KEY_WRITE)) {
+    if (value) {
+      ua_key.WriteValue(kClockUserAgent, value);
+    } else {
+      ua_key.DeleteValue(kClockUserAgent);
+    }
+    hr = S_OK;
+  } else {
+    DLOG(ERROR) << __FUNCTION__ << ": " << kPostPlatformUAKey;
+    hr = E_UNEXPECTED;
+  }
+
+  return hr;
+}
+
+HRESULT RefreshElevationPolicy() {
+  const wchar_t kIEFrameDll[] = L"ieframe.dll";
+  const char kIERefreshPolicy[] = "IERefreshElevationPolicy";
+  HRESULT hr = E_NOTIMPL;
+  HMODULE ieframe_module = LoadLibrary(kIEFrameDll);
+  if (ieframe_module) {
+    typedef HRESULT (__stdcall *IERefreshPolicy)();
+    IERefreshPolicy ie_refresh_policy = reinterpret_cast<IERefreshPolicy>(
+        GetProcAddress(ieframe_module, kIERefreshPolicy));
+
+    if (ie_refresh_policy) {
+      hr = ie_refresh_policy();
+    } else {
+      hr = HRESULT_FROM_WIN32(GetLastError());
+    }
+    
+    FreeLibrary(ieframe_module);
+  } else {
+    hr = HRESULT_FROM_WIN32(GetLastError());
+  }
+
+  return hr;
+}
+
+HRESULT RegisterChromeTabBHO() {
+  RegKey ie_bho_key;
+  if (!ie_bho_key.Create(HKEY_LOCAL_MACHINE, kBhoRegistryPath,
+                       KEY_CREATE_SUB_KEY)) {
+    DLOG(WARNING) << "Failed to open registry key "
+                  << kBhoRegistryPath
+                  << " for write";
+    return E_FAIL;
+  }
+
+  wchar_t bho_class_id_as_string[MAX_PATH] = {0};
+  StringFromGUID2(CLSID_ChromeFrameBHO, bho_class_id_as_string,
+                  arraysize(bho_class_id_as_string));
+
+  if (!ie_bho_key.CreateKey(bho_class_id_as_string, KEY_READ | KEY_WRITE)) {
+    DLOG(WARNING) << "Failed to create bho registry key under "
+                  << kBhoRegistryPath
+                  << " for write";
+    return E_FAIL;
+  }
+
+  ie_bho_key.WriteValue(kBhoNoLoadExplorerValue, 1);
+  DLOG(INFO) << "Registered ChromeTab BHO";
+
+  SetClockUserAgent(L"1");
+  RefreshElevationPolicy();
+  return S_OK;
+}
+
+HRESULT UnregisterChromeTabBHO() {
+  SetClockUserAgent(NULL);
+
+  RegKey ie_bho_key;
+  if (!ie_bho_key.Open(HKEY_LOCAL_MACHINE, kBhoRegistryPath,
+                       KEY_READ | KEY_WRITE)) {
+    DLOG(WARNING) << "Failed to open registry key "
+                  << kBhoRegistryPath
+                  << " for write.";
+    return E_FAIL;
+  }
+
+  wchar_t bho_class_id_as_string[MAX_PATH] = {0};
+  StringFromGUID2(CLSID_ChromeFrameBHO, bho_class_id_as_string,
+                  arraysize(bho_class_id_as_string));
+
+  if (!ie_bho_key.DeleteKey(bho_class_id_as_string)) {
+    DLOG(WARNING) << "Failed to delete bho registry key "
+                  << bho_class_id_as_string
+                  << " under "
+                  << kBhoRegistryPath;
+    return E_FAIL;
+  }
+
+  DLOG(INFO) << "Unregistered ChromeTab BHO";
+  return S_OK;
+}
+
+// Used to determine whether the DLL can be unloaded by OLE
+STDAPI DllCanUnloadNow() {
+  return _AtlModule.DllCanUnloadNow();
+}
+
+// Returns a class factory to create an object of the requested type
+STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) {
+  if (g_patch_helper.state() == PatchHelper::UNKNOWN) {
+    g_patch_helper.InitializeAndPatchProtocolsIfNeeded();
+    UrlMkSetSessionOption(URLMON_OPTION_USERAGENT_REFRESH, NULL, 0, 0);
+  }
+
+  return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
+}
+
+// DllRegisterServer - Adds entries to the system registry
+STDAPI DllRegisterServer() {
+  // registers object, typelib and all interfaces in typelib
+  HRESULT hr = _AtlModule.DllRegisterServer(TRUE);
+
+#ifdef GOOGLE_CHROME_BUILD
+  // Muck with the Omaha configuration so that we don't get updated by non-CF
+  // Google Chrome builds.
+  UtilUpdateOmahaConfig(true);
+#endif
+
+  if (SUCCEEDED(hr)) {
+    // Best effort attempt to register the BHO. At this point we silently
+    // ignore any errors during registration. There are some traces emitted
+    // to the debug log.
+    RegisterChromeTabBHO();
+  }
+
+  return hr;
+}
+
+// DllUnregisterServer - Removes entries from the system registry
+STDAPI DllUnregisterServer() {
+  HRESULT hr = _AtlModule.DllUnregisterServer(TRUE);
+
+#ifdef GOOGLE_CHROME_BUILD
+  // Undo any prior mucking with the Omaha config.
+  UtilUpdateOmahaConfig(false);
+#endif
+
+  if (SUCCEEDED(hr)) {
+    // Best effort attempt to unregister the BHO. At this point we silently
+    // ignore any errors during unregistration. There are some traces emitted
+    // to the debug log.
+    UnregisterChromeTabBHO();
+  }
+  return hr;
+}
+
+STDAPI RegisterNPAPIPlugin() {
+  HRESULT hr = _AtlModule.UpdateRegistryFromResourceS(IDR_CHROMEFRAME_NPAPI,
+                                                      TRUE);
+  return hr;
+}
+
+STDAPI UnregisterNPAPIPlugin() {
+  HRESULT hr = _AtlModule.UpdateRegistryFromResourceS(IDR_CHROMEFRAME_NPAPI,
+                                                      FALSE);
+  return hr;
+}
diff --git a/chrome_frame/chrome_tab.def b/chrome_frame/chrome_tab.def
new file mode 100644
index 0000000..ae2ddd2
--- /dev/null
+++ b/chrome_frame/chrome_tab.def
@@ -0,0 +1,15 @@
+; ChromeTab.def : Declares the module parameters.
+
+LIBRARY      "npchrome_tab.dll"
+
+EXPORTS
+	DllCanUnloadNow       PRIVATE
+	DllGetClassObject     PRIVATE
+	DllRegisterServer     PRIVATE
+	DllUnregisterServer   PRIVATE
+	NP_Initialize         PRIVATE
+	NP_GetEntryPoints     PRIVATE
+	NP_Shutdown           PRIVATE
+	CfLaunchChrome        PRIVATE
+	RegisterNPAPIPlugin   PRIVATE
+	UnregisterNPAPIPlugin PRIVATE
diff --git a/chrome_frame/chrome_tab.idl b/chrome_frame/chrome_tab.idl
new file mode 100644
index 0000000..6f8a2acd
--- /dev/null
+++ b/chrome_frame/chrome_tab.idl
@@ -0,0 +1,153 @@
+// Copyright (c) 2009 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.
+
+// This file will be processed by the MIDL tool to
+// produce the type library (chrome_tab.tlb) and marshalling code.
+
+#include "olectl.h"
+import "oaidl.idl";
+import "ocidl.idl";
+
+[
+  object,
+  uuid(B9F5EA20-C450-4f46-B70F-BFD3CA9A20C5),
+  dual,
+  nonextensible,
+  helpstring("IChromeFrame Interface"),
+  pointer_default(unique)
+]
+interface IChromeFrame : IDispatch {
+  [propget, id(1)]
+  HRESULT src([out, retval] BSTR* src);
+  [propput, id(1)]
+  HRESULT src([in] BSTR src);
+
+  [id(3)]
+  HRESULT postMessage([in] BSTR message, [in, optional] VARIANT target);
+
+  [id(4), propget]
+  HRESULT onload([out, retval] VARIANT* onload_handler);
+  [id(4), propput]
+  HRESULT onload([in] VARIANT onload_handler);
+
+  [propget, id(5)]
+  HRESULT onloaderror([out, retval] VARIANT* onerror_handler);
+  [propput, id(5)]
+  HRESULT onloaderror([in] VARIANT onerror_handler);
+
+  [propget, id(6)]
+  HRESULT onmessage([out, retval] VARIANT* onmessage_handler);
+  [propput, id(6)]
+  HRESULT onmessage([in] VARIANT onmessage_handler);
+
+  [propget, id(DISPID_READYSTATE)]
+  HRESULT readyState([out, retval] long* ready_state);
+
+  [id(7)]
+  HRESULT addEventListener([in] BSTR event_type, [in] IDispatch* listener,
+                           [in, optional] VARIANT use_capture);
+
+  [id(8)]
+  HRESULT removeEventListener([in] BSTR event_type, [in] IDispatch* listener,
+                              [in, optional] VARIANT use_capture);
+
+  [propget, id(9)]
+  HRESULT version([out, retval] BSTR* version);
+
+  [id(10), hidden]
+  // This method is available only when the control is in privileged mode.
+  HRESULT postPrivateMessage([in] BSTR message,
+                             [in] BSTR origin,
+                             [in] BSTR target);
+
+  [propget, id(11)]
+  HRESULT useChromeNetwork([out, retval] VARIANT_BOOL* pVal);
+  [propput, id(11)]
+  HRESULT useChromeNetwork([in] VARIANT_BOOL newVal);
+};
+
+[
+  object,
+  uuid(679E292F-DBAB-46b8-8693-03084CEF61BE),
+  oleautomation,
+  nonextensible,
+  hidden,
+]
+interface IChromeFramePrivileged: IUnknown {
+  // If the host returns false for wants_privileged, the control
+  // won't enable privileged mode.
+  HRESULT GetWantsPrivileged([out] boolean *wants_privileged);
+  // Extra arguments to supply to the Chrome instance.  Returns S_FALSE when
+  // no extra arguments are needed.  Always sets the output string to non-NULL.
+  HRESULT GetChromeExtraArguments([out] BSTR *args);
+  // The profile name we want to use.
+  HRESULT GetChromeProfileName([out] BSTR *profile_name);
+};
+
+// Expose this service to the ChromeFrame control to trigger privileged
+// mode. If the control is in privileged mode, it will forward messages
+// to the onmessage handler irrespective of origin.
+cpp_quote("#define SID_ChromeFramePrivileged __uuidof(IChromeFramePrivileged)")
+
+typedef enum {
+  CF_EVENT_DISPID_ONLOAD = 1,
+  CF_EVENT_DISPID_ONLOADERROR,
+  CF_EVENT_DISPID_ONMESSAGE,
+  CF_EVENT_DISPID_ONPRIVATEMESSAGE,
+  CF_EVENT_DISPID_ONREADYSTATECHANGED = DISPID_READYSTATECHANGE,
+} ChromeFrameEventDispId;
+
+[
+  uuid(6F2664E1-FF6E-488A-BCD1-F4CA6001DFCC),
+  version(1.0),
+  helpstring("ChromeTab 1.0 Type Library")
+]
+library ChromeTabLib {
+  importlib("stdole2.tlb");
+
+  [uuid(A96B8A02-DD11-4936-8C0F-B2520289FABB)]
+  dispinterface DIChromeFrameEvents {
+   properties:
+    // None.
+
+   methods:
+    [id(CF_EVENT_DISPID_ONLOAD)]
+    void onload();
+    [id(CF_EVENT_DISPID_ONLOADERROR)]
+    void onloaderror();
+    [id(CF_EVENT_DISPID_ONMESSAGE)]
+    void onmessage([in] IDispatch* event);
+    [id(CF_EVENT_DISPID_ONREADYSTATECHANGED)]
+    void onreadystatechanged();
+    [id(CF_EVENT_DISPID_ONPRIVATEMESSAGE)]
+    // This event is only fired when the control is in privileged mode.
+    void onprivatemessage([in] IDispatch* event, [in] BSTR target);
+  };
+
+  [uuid(BB1176EE-20DD-41DC-9D1E-AC1335C7BBB0)]
+  coclass HtmlFilter {
+    [default] interface IUnknown;
+  };
+
+  [uuid(9875BFAF-B04D-445E-8A69-BE36838CDE3E)]
+  coclass ChromeProtocol {
+    [default] interface IUnknown;
+  };
+
+  [uuid(3E1D0E7F-F5E3-44CC-AA6A-C0A637619AB8), control]
+  coclass ChromeActiveDocument {
+    [default] interface IChromeFrame;
+  };
+
+  [uuid(E0A900DF-9611-4446-86BD-4B1D47E7DB2A), control]
+  coclass ChromeFrame {
+    [default] interface IChromeFrame;
+    [default, source] dispinterface DIChromeFrameEvents;
+  };
+
+  [uuid(ECB3C477-1A0A-44bd-BB57-78F9EFE34FA7)]
+  coclass ChromeFrameBHO {
+    [default] interface IUnknown;
+  };
+};
diff --git a/chrome_frame/chrome_tab.rgs b/chrome_frame/chrome_tab.rgs
new file mode 100644
index 0000000..ab4008b
--- /dev/null
+++ b/chrome_frame/chrome_tab.rgs
@@ -0,0 +1,12 @@
+HKLM {

+  NoRemove Software {

+    NoRemove Classes {

+      NoRemove AppID {

+        '%APPID%' = s 'ChromeTab'

+        'ChromeTab.DLL' {

+          val AppID = s '%APPID%'

+        }

+      }

+    }

+  }

+}

diff --git a/chrome_frame/chrome_tab_version.rc.version b/chrome_frame/chrome_tab_version.rc.version
new file mode 100644
index 0000000..84c02f4
--- /dev/null
+++ b/chrome_frame/chrome_tab_version.rc.version
@@ -0,0 +1,45 @@
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION @MAJOR@,@MINOR@,@BUILD@,@PATCH@
+ PRODUCTVERSION @MAJOR@,@MINOR@,@BUILD@,@PATCH@
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        // Note that Firefox 3.0 requires the charset to be 04e4 (multi-lingual).
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "@COMPANY_FULLNAME@"
+            VALUE "CompanyShortName", "@COMPANY_SHORTNAME@"
+            VALUE "ProductName", "Google Chrome Frame"
+            VALUE "ProductVersion", "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
+            VALUE "FileDescription", "Chrome Frame renders the Web of the future in the browsers of the past. It's like strapping a rocket engine to a minivan."
+            VALUE "FileVersion", "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
+            VALUE "InternalName", "Google Chrome Frame"
+            VALUE "LegalCopyright", "@COPYRIGHT@"
+            VALUE "MIMEType", "application/chromeframe"
+            VALUE "FileExtents", "chromeframe"
+            VALUE "FileOpenName", "chromeframe"
+            VALUE "OriginalFilename", "npchrome_tab.dll"
+            VALUE "LastChange", "@LASTCHANGE@"
+            VALUE "Official Build", "@OFFICIAL_BUILD@"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        // Note that Firefox 3.0 requires the charset to be 1252 (multi-lingual).
+        VALUE "Translation", 0x409, 1252
+    END
+END
diff --git a/chrome_frame/com_message_event.cc b/chrome_frame/com_message_event.cc
new file mode 100644
index 0000000..1e25b87
--- /dev/null
+++ b/chrome_frame/com_message_event.cc
@@ -0,0 +1,157 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/com_message_event.h"
+
+#include "base/logging.h"
+#include "base/string_util.h"
+
+ComMessageEvent::ComMessageEvent() {
+}
+
+ComMessageEvent::~ComMessageEvent() {
+}
+
+bool ComMessageEvent::Initialize(IOleContainer* container,
+                                 const std::string& message,
+                                 const std::string& origin,
+                                 const std::string& event_type) {
+  DCHECK(container);
+  message_ = message;
+  origin_ = origin;
+  type_ = event_type;
+
+  // May remain NULL if container not IE
+  ScopedComPtr<IHTMLEventObj> basic_event;
+  ScopedComPtr<IHTMLDocument2> doc;
+
+  // Fetching doc may fail in non-IE containers.
+  container->QueryInterface(doc.Receive());
+  if (doc) {
+    ScopedComPtr<IHTMLDocument4> doc4;
+    doc4.QueryFrom(doc);
+    DCHECK(doc4);  // supported by IE5.5 and higher
+    if (doc4) {
+      // IHTMLEventObj5 is only supported in IE8 and later, so we provide our
+      // own (minimalistic) implementation of it.
+      doc4->createEventObject(NULL, basic_event.Receive());
+      DCHECK(basic_event);
+    }
+  }
+
+  basic_event_ = basic_event;  
+  return true;
+}
+
+STDMETHODIMP ComMessageEvent::GetTypeInfoCount(UINT* info) {
+  // Don't DCHECK as python scripts might still call this function
+  // inadvertently.
+  DLOG(WARNING) << "Not implemented: " << __FUNCTION__;
+  return E_NOTIMPL;  
+}
+
+STDMETHODIMP ComMessageEvent::GetTypeInfo(UINT which_info, LCID lcid,
+                                          ITypeInfo** type_info) {
+  NOTREACHED();
+  return E_NOTIMPL;  
+}
+
+STDMETHODIMP ComMessageEvent::GetIDsOfNames(REFIID iid, LPOLESTR* names,
+                                            UINT count_names, LCID lcid,
+                                            DISPID* dispids) {
+  HRESULT hr = S_OK;
+
+  // Note that since we're using LowerCaseEqualsASCII for string comparison,
+  // the second argument _must_ be all lower case.  I.e. we cannot compare
+  // against L"messagePort" since it has a capital 'P'.
+  for (UINT i = 0; SUCCEEDED(hr) && i < count_names; ++i) {
+    const wchar_t* name_begin = names[i];
+    const wchar_t* name_end = name_begin + wcslen(name_begin);
+    if (LowerCaseEqualsASCII(name_begin, name_end, "data")) {
+      dispids[i] = DISPID_MESSAGE_EVENT_DATA;
+    } else if (LowerCaseEqualsASCII(name_begin, name_end, "origin")) {
+      dispids[i] = DISPID_MESSAGE_EVENT_ORIGIN;
+    } else if (LowerCaseEqualsASCII(name_begin, name_end, "lasteventid")) {
+      dispids[i] = DISPID_MESSAGE_EVENT_LAST_EVENT_ID;
+    } else if (LowerCaseEqualsASCII(name_begin, name_end, "source")) {
+      dispids[i] = DISPID_MESSAGE_EVENT_SOURCE;
+    } else if (LowerCaseEqualsASCII(name_begin, name_end, "messageport")) {
+      dispids[i] = DISPID_MESSAGE_EVENT_MESSAGE_PORT;
+    } else if (LowerCaseEqualsASCII(name_begin, name_end, "type")) {
+      dispids[i] = DISPID_MESSAGE_EVENT_TYPE;
+    } else {
+      if (basic_event_) {
+        hr = basic_event_->GetIDsOfNames(IID_IDispatch, &names[i], 1, lcid,
+                                         &dispids[i]);
+      } else {
+        hr = DISP_E_MEMBERNOTFOUND;
+      }
+
+      if (FAILED(hr)) {
+        DLOG(WARNING) << "member not found: " << names[i]
+                      << StringPrintf(L"0x%08X", hr);
+      }
+    }
+  }
+  return hr;                           
+}
+
+STDMETHODIMP ComMessageEvent::Invoke(DISPID dispid, REFIID iid, LCID lcid,
+                                     WORD flags, DISPPARAMS* params,
+                                     VARIANT* result, EXCEPINFO* excepinfo,
+                                     UINT* arg_err) {
+  HRESULT hr = DISP_E_MEMBERNOTFOUND;
+  switch (dispid) {
+    case DISPID_MESSAGE_EVENT_DATA:
+      hr = GetStringProperty(flags, UTF8ToWide(message_).c_str(), result);
+      break;
+
+    case DISPID_MESSAGE_EVENT_ORIGIN:
+      hr = GetStringProperty(flags, UTF8ToWide(origin_).c_str(), result);
+      break;
+
+    case DISPID_MESSAGE_EVENT_TYPE:
+      hr = GetStringProperty(flags, UTF8ToWide(type_).c_str(), result);
+      break;
+
+    case DISPID_MESSAGE_EVENT_LAST_EVENT_ID:
+      hr = GetStringProperty(flags, L"", result);
+      break;
+
+    case DISPID_MESSAGE_EVENT_SOURCE:
+    case DISPID_MESSAGE_EVENT_MESSAGE_PORT:
+      if (flags & DISPATCH_PROPERTYGET) {
+        result->vt = VT_NULL;
+        hr = S_OK;
+      } else {
+        hr = DISP_E_TYPEMISMATCH;
+      }
+      break;
+
+    default:
+      if (basic_event_) {
+        hr = basic_event_->Invoke(dispid, iid, lcid, flags, params, result,
+                                  excepinfo, arg_err);
+      }
+      break;
+  }
+
+  return hr;
+}
+
+HRESULT ComMessageEvent::GetStringProperty(WORD flags, const wchar_t* value,
+                                           VARIANT* result) {
+  if (!result)
+    return E_INVALIDARG;
+
+  HRESULT hr;
+  if (flags & DISPATCH_PROPERTYGET) {
+    result->vt = VT_BSTR;
+    result->bstrVal = ::SysAllocString(value);
+    hr = S_OK;
+  } else {
+    hr = DISP_E_TYPEMISMATCH;
+  }
+  return hr;
+}
diff --git a/chrome_frame/com_message_event.h b/chrome_frame/com_message_event.h
new file mode 100644
index 0000000..86fec2d
--- /dev/null
+++ b/chrome_frame/com_message_event.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_COM_MESSAGE_EVENT_H_
+#define CHROME_FRAME_COM_MESSAGE_EVENT_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <mshtml.h>  // IHTMLEventObj
+#include "base/basictypes.h"
+#include "base/scoped_comptr_win.h"
+
+// Implements a MessageEvent compliant event object by providing MessageEvent
+// specific properties itself and inherited properties from a browser provided
+// event implementation.
+// NOTE: The messagePort and source properties will always be NULL.
+// See the HTML 5 spec for further details on a MessageEvent object:
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#messageevent
+class ComMessageEvent
+    : public CComObjectRootEx<CComSingleThreadModel>,
+      public IDispatch {
+ public:
+  ComMessageEvent();
+  ~ComMessageEvent();
+
+BEGIN_COM_MAP(ComMessageEvent)
+  COM_INTERFACE_ENTRY(IDispatch)
+END_COM_MAP()
+
+  // The dispids we support.  These are based on HTML5 and not IHTMLEventObj5
+  // (there are a couple of differences).
+  // http://dev.w3.org/html5/spec/Overview.html#messageevent
+  // vs http://msdn.microsoft.com/en-us/library/cc288548(VS.85).aspx
+  typedef enum {
+    DISPID_MESSAGE_EVENT_DATA = 201,
+    DISPID_MESSAGE_EVENT_ORIGIN,
+    DISPID_MESSAGE_EVENT_LAST_EVENT_ID,
+    DISPID_MESSAGE_EVENT_SOURCE,
+    DISPID_MESSAGE_EVENT_MESSAGE_PORT,
+    DISPID_MESSAGE_EVENT_TYPE
+  } MessageEventDispIds;
+
+  // Utility function for checking Invoke flags and assigning a BSTR to the
+  // result variable.
+  HRESULT GetStringProperty(WORD flags, const wchar_t* value, VARIANT* result);
+
+  STDMETHOD(GetTypeInfoCount)(UINT* info);
+  STDMETHOD(GetTypeInfo)(UINT which_info, LCID lcid, ITypeInfo** type_info);
+  STDMETHOD(GetIDsOfNames)(REFIID iid, LPOLESTR* names, UINT count_names,
+                           LCID lcid, DISPID* dispids);
+  STDMETHOD(Invoke)(DISPID dispid, REFIID iid, LCID lcid, WORD flags,
+                    DISPPARAMS* params, VARIANT* result, EXCEPINFO* excepinfo,
+                    UINT* arg_err);
+
+  // Initializes this object.  The container pointer is used to find the
+  // container's IHTMLEventObj implementation if available.
+  bool Initialize(IOleContainer* container, const std::string& message,
+                  const std::string& origin, const std::string& event_type);
+
+ protected:
+  // HTML Event object to which we delegate property and method
+  // calls that we do not directly support.  This may not be initialized
+  // if our container does not require the properties/methods exposed by
+  // the basic event object.
+  ScopedComPtr<IHTMLEventObj> basic_event_;
+  std::string message_;
+  std::string origin_;
+  std::string type_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ComMessageEvent);
+};
+
+#endif  // CHROME_FRAME_COM_MESSAGE_EVENT_H_
diff --git a/chrome_frame/com_type_info_holder.cc b/chrome_frame/com_type_info_holder.cc
new file mode 100644
index 0000000..a018f81e
--- /dev/null
+++ b/chrome_frame/com_type_info_holder.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/com_type_info_holder.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+
+namespace com_util {
+
+base::LazyInstance<TypeInfoCache> type_info_cache(base::LINKER_INITIALIZED);
+
+// TypeInfoCache
+
+TypeInfoCache::~TypeInfoCache() {
+  CacheMap::iterator it = cache_.begin();
+  while (it != cache_.end()) {
+    delete it->second;
+    it++;
+  }
+}
+
+TypeInfoNameCache* TypeInfoCache::Lookup(const IID* iid) {
+  DCHECK(Singleton() == this);
+
+  TypeInfoNameCache* tih = NULL;
+
+  AutoLock lock(lock_);
+  CacheMap::iterator it = cache_.find(iid);
+  if (it == cache_.end()) {
+    tih = new TypeInfoNameCache();
+    HRESULT hr = tih ? tih->Initialize(*iid) : E_OUTOFMEMORY;
+    if (SUCCEEDED(hr)) {
+      cache_[iid] = tih;
+    } else {
+      NOTREACHED();
+      delete tih;
+    }
+  } else {
+    tih = it->second;
+  }
+
+  return tih;
+}
+
+TypeInfoCache* TypeInfoCache::Singleton() {
+  return type_info_cache.Pointer();
+}
+
+HRESULT TypeInfoNameCache::Initialize(const IID& iid) {
+  DCHECK(type_info_ == NULL);
+
+  wchar_t file_path[MAX_PATH];
+  DWORD path_len = ::GetModuleFileNameW(reinterpret_cast<HMODULE>(&__ImageBase),
+                                        file_path, arraysize(file_path));
+  if (path_len == 0 || path_len == MAX_PATH) {
+    NOTREACHED();
+    return E_UNEXPECTED;
+  }
+
+  ScopedComPtr<ITypeLib> type_lib;
+  HRESULT hr = LoadTypeLib(file_path, type_lib.Receive());
+  if (SUCCEEDED(hr)) {
+    hr = type_lib->GetTypeInfoOfGuid(iid, type_info_.Receive());
+  }
+
+  return hr;
+}
+
+// TypeInfoNameCache
+
+HRESULT TypeInfoNameCache::GetIDsOfNames(OLECHAR** names, uint32 count,
+                                         DISPID* dispids) {
+  DCHECK(type_info_ != NULL);
+
+  HRESULT hr = S_OK;
+  for (uint32 i = 0; i < count && SUCCEEDED(hr); ++i) {
+    NameToDispIdCache::HashType hash = NameToDispIdCache::Hash(names[i]);
+    if (!cache_.Lookup(hash, &dispids[i])) {
+      hr = type_info_->GetIDsOfNames(&names[i], 1, &dispids[i]);
+      if (SUCCEEDED(hr)) {
+        cache_.Add(hash, dispids[i]);
+      }
+    }
+  }
+
+  return hr;
+}
+
+HRESULT TypeInfoNameCache::Invoke(IDispatch* p, DISPID dispid, WORD flags,
+                                  DISPPARAMS* params, VARIANT* result,
+                                  EXCEPINFO* excepinfo, UINT* arg_err) {
+  DCHECK(type_info_);
+  HRESULT hr = type_info_->Invoke(p, dispid, flags, params, result, excepinfo,
+                                  arg_err);
+  DCHECK(hr != RPC_E_WRONG_THREAD);
+  return hr;
+}
+
+// NameToDispIdCache
+
+bool NameToDispIdCache::Lookup(HashType hash, DISPID* dispid) const {
+  AutoLock lock(lock_);
+  const DispidMap::const_iterator it = map_.find(hash);
+  bool found = (it != map_.end());
+  if (found)
+    *dispid = it->second;
+  return found;
+}
+
+void NameToDispIdCache::Add(HashType hash, DISPID dispid) {
+  AutoLock lock(lock_);
+  map_[hash] = dispid;
+}
+
+NameToDispIdCache::HashType NameToDispIdCache::Hash(const wchar_t* name) {
+  return LHashValOfName(LANG_NEUTRAL, name);
+}
+
+}  // namespace com_util
diff --git a/chrome_frame/com_type_info_holder.h b/chrome_frame/com_type_info_holder.h
new file mode 100644
index 0000000..9b0e6cf
--- /dev/null
+++ b/chrome_frame/com_type_info_holder.h
@@ -0,0 +1,191 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_COM_TYPE_INFO_HOLDER_H_
+#define CHROME_FRAME_COM_TYPE_INFO_HOLDER_H_
+
+#include <map>
+#include <ocidl.h>  // IProvideClassInfo2
+
+#include "base/lock.h"
+#include "base/scoped_comptr_win.h"
+
+#define NO_VTABLE __declspec(novtable)
+
+namespace com_util {
+
+// A map from a name hash (32 bit value) to a DISPID.
+// Used as a caching layer before looking the name up in a type lib.
+class NameToDispIdCache {
+ public:
+  typedef uint32 HashType;
+
+  bool Lookup(HashType hash, DISPID* dispid) const;
+  void Add(HashType hash, DISPID dispid);
+
+  // Hashes the name by calling LHashValOfName.
+  // The returned hash value is independent of the case of the characters
+  // in |name|.
+  static HashType Hash(const wchar_t* name);
+
+ protected:
+  typedef std::map<HashType, DISPID> DispidMap;
+  DispidMap map_;
+  mutable Lock lock_;
+};
+
+// Wraps an instance of ITypeInfo and builds+maintains a cache of names
+// to dispids.  Also offers an Invoke method that simply forwards the call
+// to ITypeInfo::Invoke.
+class TypeInfoNameCache {
+ public:
+  // Loads the module's type library and fetches the ITypeInfo object for
+  // the specified interface ID.
+  HRESULT Initialize(const IID& iid);
+
+  // Fetches the id's of the given names.  If there's a cache miss, the results
+  // are fetched from the underlying ITypeInfo and then cached.
+  HRESULT GetIDsOfNames(OLECHAR** names, uint32 count, DISPID* dispids);
+
+  // Calls ITypeInfo::Invoke.
+  HRESULT Invoke(IDispatch* p, DISPID dispid, WORD flags, DISPPARAMS* params,
+                 VARIANT* result, EXCEPINFO* excepinfo, UINT* arg_err);
+
+  inline ITypeInfo* CopyTypeInfo() {
+    ITypeInfo* ti = type_info_.get();
+    if (ti)
+      ti->AddRef();
+    return ti;
+  }
+
+ protected:
+  ScopedComPtr<ITypeInfo> type_info_;
+  NameToDispIdCache cache_;
+};
+
+// The root class for type lib access.
+// This class has only one instance that should be accessed via the
+// Singleton method.
+class TypeInfoCache {
+ public:
+  TypeInfoCache() {
+  }
+
+  ~TypeInfoCache();
+
+  // Looks up a previously cached TypeInfoNameCache instance or creates and
+  // caches a new one.
+  TypeInfoNameCache* Lookup(const IID* iid);
+
+  // Call to get access to the singleton instance of TypeInfoCache.
+  static TypeInfoCache* Singleton();
+
+ protected:
+  typedef std::map<const IID*, TypeInfoNameCache*> CacheMap;
+  Lock lock_;
+  CacheMap cache_;
+};
+
+// Holds a pointer to the type info of a given COM interface.
+// The type info is loaded once on demand and after that cached.
+// NOTE: This class only supports loading the first typelib from the
+// current module.
+template <const IID& iid>
+class TypeInfoHolder {
+ public:
+  TypeInfoHolder() : type_info_(NULL) {
+  }
+
+  bool EnsureTI() {
+    if (!type_info_)
+      type_info_ = TypeInfoCache::Singleton()->Lookup(&iid);
+    return type_info_ != NULL;
+  }
+
+  HRESULT GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** info) {
+    if (EnsureTI()) {
+      *info = type_info_->CopyTypeInfo();
+      return S_OK;
+    }
+
+    return E_UNEXPECTED;
+  }
+
+  HRESULT GetIDsOfNames(REFIID riid, OLECHAR** names, UINT count, LCID lcid,
+                        DISPID* dispids) {
+    if (!EnsureTI())
+      return E_UNEXPECTED;
+    return type_info_->GetIDsOfNames(names, count, dispids);
+  }
+
+  HRESULT Invoke(IDispatch* p, DISPID dispid, REFIID riid, LCID lcid,
+                 WORD flags, DISPPARAMS* params, VARIANT* result,
+                 EXCEPINFO* excepinfo, UINT* arg_err) {
+    if (!EnsureTI())
+      return E_UNEXPECTED;
+
+    return type_info_->Invoke(p, dispid, flags, params, result, excepinfo,
+                              arg_err);
+  }
+
+ protected:
+  TypeInfoNameCache* type_info_;
+};
+
+// Implements IDispatch part of T (where T is an IDispatch derived interface).
+// The class assumes that the type info of T is available in a typelib of the
+// current module.
+template <class T, const IID& iid = __uuidof(T)>
+class NO_VTABLE IDispatchImpl : public T {
+ public:
+  STDMETHOD(GetTypeInfoCount)(UINT* count) {
+    if (count == NULL) 
+      return E_POINTER; 
+    *count = 1;
+    return S_OK;
+  }
+
+  STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo** pptinfo) {
+    return type_info_.GetTypeInfo(itinfo, lcid, pptinfo);
+  }
+
+  STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* names, UINT count,
+                           LCID lcid, DISPID* dispids) {
+    return type_info_.GetIDsOfNames(riid, names, count, lcid, dispids);
+  }
+  STDMETHOD(Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD flags,
+                    DISPPARAMS* params, VARIANT* result, EXCEPINFO* excepinfo,
+                    UINT* arg_err) {
+    return type_info_.Invoke(static_cast<IDispatch*>(this), dispid, riid, lcid,
+                             flags, params, result, excepinfo, arg_err);
+  }
+
+ protected:
+  TypeInfoHolder<iid> type_info_;
+};
+
+// Simple implementation of IProvideClassInfo[2].
+template <const CLSID& class_id, const IID& source_iid>
+class NO_VTABLE IProvideClassInfo2Impl : public IProvideClassInfo2 {
+ public:
+  STDMETHOD(GetClassInfo)(ITypeInfo** pptinfo) {
+    return type_info_.GetTypeInfo(0, LANG_NEUTRAL, pptinfo);
+  }
+
+  STDMETHOD(GetGUID)(DWORD guid_kind, GUID* guid) {
+    if(guid == NULL || guid_kind != GUIDKIND_DEFAULT_SOURCE_DISP_IID)
+      return E_INVALIDARG;
+
+    *guid = source_iid;
+
+    return S_OK;
+  }
+
+ protected:
+  TypeInfoHolder<class_id> type_info_;
+};
+
+}  // namespace com_util
+
+#endif  // CHROME_FRAME_COM_TYPE_INFO_HOLDER_H_
diff --git a/chrome_frame/combine_libs.py b/chrome_frame/combine_libs.py
new file mode 100644
index 0000000..97d3de72
--- /dev/null
+++ b/chrome_frame/combine_libs.py
@@ -0,0 +1,114 @@
+# Copyright (c) 2009 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.
+
+# TODO(slightlyoff): move to using shared version of this script.
+
+'''This script makes it easy to combine libs and object files to a new lib,
+optionally removing some of the object files in the input libs by regular
+expression matching.
+For usage information, run the script with a --help argument.
+'''
+import optparse
+import os
+import re
+import subprocess
+import sys
+
+
+def Shell(*args):
+  '''Runs the program and args in args, returns the output from the program.'''
+  process = subprocess.Popen(args,
+                             stdin = None,
+                             stdout = subprocess.PIPE,
+                             stderr = subprocess.STDOUT)
+  output = process.stdout.readlines()
+  process.wait()
+  retcode = process.returncode
+  if retcode != 0:
+    raise RuntimeError('%s exited with status %d' % (args[0], retcode))
+  return output
+
+
+def CollectRemovals(remove_re, inputs):
+  '''Returns a list of all object files in inputs that match remove_re.'''
+  removals = []
+  for input in inputs:
+    output = Shell('lib.exe', '/list', input)
+
+    for line in output:
+      line = line.rstrip()
+      if remove_re.search(line):
+        removals.append(line)
+
+  return removals
+
+
+def CombineLibraries(output, remove_re, inputs):
+  '''Combines all the libraries and objects in inputs, while removing any
+  object files that match remove_re.
+  '''
+  removals = []
+  if remove_re:
+    removals = CollectRemovals(remove_re, inputs)
+  
+  print removals
+
+  args = ['lib.exe', '/out:%s' % output]
+  args += ['/remove:%s' % obj for obj in removals]
+  args += inputs
+  Shell(*args)
+
+
+USAGE = '''usage: %prog [options] <lib or obj>+
+
+Combines input libraries or objects into an output library, while removing
+any object file (in the input libraries) that matches a given regular
+expression.
+'''
+
+def GetOptionParser():
+  parser = optparse.OptionParser(USAGE)
+  parser.add_option('-o', '--output', dest = 'output',
+                    help = 'write to this output library')
+  parser.add_option('-r', '--remove', dest = 'remove',
+                    help = 'object files matching this regexp will be removed '
+                            'from the output library')
+  return parser
+
+
+def Main():
+  '''Main function for this script'''
+  parser = GetOptionParser()
+  (opt, args) = parser.parse_args()
+  output = opt.output
+  remove = opt.remove
+  if not output:
+    parser.error('You must specify an output file')
+
+  if not args:
+    parser.error('You must specify at least one object or library')
+
+  output = output.strip()
+  remove = remove.strip()
+
+  if remove:
+    try:
+      remove_re = re.compile(opt.remove)
+    except:
+      parser.error('%s is not a valid regular expression' % opt.remove)
+  else:
+    remove_re = None
+
+  if sys.platform != 'win32':
+    parser.error('this script only works on Windows for now')
+
+  # If this is set, we can't capture lib.exe's output.
+  if 'VS_UNICODE_OUTPUT' in os.environ:
+    del os.environ['VS_UNICODE_OUTPUT']
+
+  CombineLibraries(output, remove_re, args)
+
+
+if __name__ == '__main__':
+  Main()
diff --git a/chrome_frame/common/extra_defines.vsprops b/chrome_frame/common/extra_defines.vsprops
new file mode 100644
index 0000000..ca29c05
--- /dev/null
+++ b/chrome_frame/common/extra_defines.vsprops
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="Windows-1252"?>

+<VisualStudioPropertySheet

+  ProjectType="Visual C++"

+  Version="8.00"

+  Name="extra"

+  >

+  <Tool

+    Name="VCCLCompilerTool"	

+    PreprocessorDefinitions="EXCLUDE_SKIA_DEPENDENCIES"

+    AdditionalIncludeDirectories=""

+  />

+</VisualStudioPropertySheet>

diff --git a/chrome_frame/crash_report.cc b/chrome_frame/crash_report.cc
new file mode 100644
index 0000000..5a67316a
--- /dev/null
+++ b/chrome_frame/crash_report.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 2009 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.

+

+// crash_report.cc : Implementation crash reporting.

+#include "chrome_frame/crash_report.h"

+

+#include "base/file_util.h"

+#include "base/logging.h"

+#include "base/win_util.h"

+#include "breakpad/src/client/windows/handler/exception_handler.h"

+#include "chrome/installer/util/google_update_settings.h"

+#include "chrome/installer/util/install_util.h"

+#include "chrome_frame/vectored_handler.h"

+#include "chrome_frame/vectored_handler-impl.h"

+

+namespace {

+// TODO(joshia): factor out common code with chrome used for crash reporting

+const wchar_t kGoogleUpdatePipeName[] = L"\\\\.\\pipe\\GoogleCrashServices\\";

+const wchar_t kChromePipeName[] = L"\\\\.\\pipe\\ChromeCrashServices";

+// Well known SID for the system principal.

+const wchar_t kSystemPrincipalSid[] = L"S-1-5-18";

+google_breakpad::ExceptionHandler* g_breakpad = NULL;

+

+// Returns the custom info structure based on the dll in parameter and the

+// process type.

+google_breakpad::CustomClientInfo* GetCustomInfo() {

+  // TODO(joshia): Grab these based on build.

+  static google_breakpad::CustomInfoEntry ver_entry(L"ver", L"0.1.0.0");

+  static google_breakpad::CustomInfoEntry prod_entry(L"prod", L"ChromeFrame");

+  static google_breakpad::CustomInfoEntry plat_entry(L"plat", L"Win32");

+  static google_breakpad::CustomInfoEntry type_entry(L"ptype", L"chrome_frame");

+  static google_breakpad::CustomInfoEntry entries[] = {

+      ver_entry, prod_entry, plat_entry, type_entry };

+  static google_breakpad::CustomClientInfo custom_info = {

+      entries, arraysize(entries) };

+  return &custom_info;

+}

+

+__declspec(naked)

+static EXCEPTION_REGISTRATION_RECORD* InternalRtlpGetExceptionList() {

+  __asm {

+    mov eax, fs:0

+    ret

+  }

+}

+}  // end of namespace

+

+// Class which methods simply forwards to Win32 API and uses breakpad to write

+// a minidump. Used as template (external interface) of VectoredHandlerT<E>.

+class Win32VEHTraits : public VEHTraitsBase {

+ public:

+  static inline void* Register(PVECTORED_EXCEPTION_HANDLER func,

+      const void* module_start, const void* module_end) {

+    VEHTraitsBase::SetModule(module_start, module_end);

+    return ::AddVectoredExceptionHandler(1, func);

+  }

+

+  static inline ULONG Unregister(void* handle) {

+    return ::RemoveVectoredExceptionHandler(handle);

+  }

+

+  static inline bool WriteDump(EXCEPTION_POINTERS* p) {

+    return g_breakpad->WriteMinidumpForException(p);

+  }

+

+  static inline EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() {

+    return InternalRtlpGetExceptionList();

+  }

+

+  static inline WORD RtlCaptureStackBackTrace(DWORD FramesToSkip,

+      DWORD FramesToCapture, void** BackTrace, DWORD* BackTraceHash) {

+    return ::RtlCaptureStackBackTrace(FramesToSkip, FramesToCapture,

+                                      BackTrace, BackTraceHash);

+  }

+};

+

+extern "C" IMAGE_DOS_HEADER __ImageBase;

+bool InitializeCrashReporting(bool use_crash_service, bool full_dump) {

+  if (g_breakpad)

+    return true;

+

+  std::wstring pipe_name;

+  if (use_crash_service) {

+    // Crash reporting is done by crash_service.exe.

+    pipe_name = kChromePipeName;

+  } else {

+    // We want to use the Google Update crash reporting. We need to check if the

+    // user allows it first.

+    if (!GoogleUpdateSettings::GetCollectStatsConsent())

+      return true;

+

+    // Build the pipe name. It can be either:

+    // System-wide install: "NamedPipe\GoogleCrashServices\S-1-5-18"

+    // Per-user install: "NamedPipe\GoogleCrashServices\<user SID>"

+    wchar_t dll_path[MAX_PATH * 2] = {0};

+    GetModuleFileName(reinterpret_cast<HMODULE>(&__ImageBase), dll_path, 

+                      arraysize(dll_path));

+

+    std::wstring user_sid;

+    if (InstallUtil::IsPerUserInstall(dll_path)) {

+      if (!win_util::GetUserSidString(&user_sid)) {

+        return false;

+      }

+    } else {

+      user_sid = kSystemPrincipalSid;

+    }

+

+    pipe_name = kGoogleUpdatePipeName;

+    pipe_name += user_sid;

+  }

+

+  // Get the alternate dump directory. We use the temp path.

+  FilePath temp_directory;

+  if (!file_util::GetTempDir(&temp_directory) || temp_directory.empty()) {

+    return false;

+  }

+

+  MINIDUMP_TYPE dump_type = full_dump ? MiniDumpWithFullMemory : MiniDumpNormal;

+  g_breakpad = new google_breakpad::ExceptionHandler(

+      temp_directory.value(), NULL, NULL, NULL,

+      google_breakpad::ExceptionHandler::HANDLER_INVALID_PARAMETER |

+      google_breakpad::ExceptionHandler::HANDLER_PURECALL, dump_type,

+      pipe_name.c_str(), GetCustomInfo());

+

+  if (g_breakpad) {

+    // Find current module boundaries.

+    const void* start = &__ImageBase;

+    const char* s = reinterpret_cast<const char*>(start);

+    const IMAGE_NT_HEADERS32* nt = reinterpret_cast<const IMAGE_NT_HEADERS32*>

+        (s + __ImageBase.e_lfanew);

+    const void* end = s + nt->OptionalHeader.SizeOfImage;

+    VectoredHandler::Register(start, end);

+  }

+

+  return g_breakpad != NULL;

+}

+

+bool ShutdownCrashReporting() {

+  VectoredHandler::Unregister();

+  delete g_breakpad;

+  g_breakpad = NULL;

+  return true;

+}

diff --git a/chrome_frame/crash_report.h b/chrome_frame/crash_report.h
new file mode 100644
index 0000000..3758e33
--- /dev/null
+++ b/chrome_frame/crash_report.h
@@ -0,0 +1,13 @@
+// Copyright (c) 2009 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.

+

+// crash_report.h : Declarations for crash reporting.

+

+#ifndef CHROME_FRAME_CRASH_REPORT_H_

+#define CHROME_FRAME_CRASH_REPORT_H_

+

+bool InitializeCrashReporting(bool use_crash_service, bool full_dump);

+bool ShutdownCrashReporting();

+

+#endif  // CHROME_FRAME_CRASH_REPORT_H_

diff --git a/chrome_frame/extra_system_apis.h b/chrome_frame/extra_system_apis.h
new file mode 100644
index 0000000..0fbe0ea
--- /dev/null
+++ b/chrome_frame/extra_system_apis.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2009 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.
+
+// This header file contains declarations for system APIs and interfaces
+// that are either undocumented or are documented but not included in any
+// Platform SDK header files.
+#ifndef CHROME_FRAME_EXTRA_SYSTEM_APIS_H_
+#define CHROME_FRAME_EXTRA_SYSTEM_APIS_H_
+
+// This is an interface provided by the WebBrowser object. It allows us to
+// notify the browser of navigation events. MSDN documents this interface
+// (see http://msdn2.microsoft.com/en-us/library/aa752109(VS.85).aspx)
+// but this is not included in any Platform SDK header file.
+class __declspec(uuid("54A8F188-9EBD-4795-AD16-9B4945119636"))
+IWebBrowserEventsService : public IUnknown {
+ public:
+  STDMETHOD(FireBeforeNavigate2Event)(VARIANT_BOOL *cancel) = 0;
+  STDMETHOD(FireNavigateComplete2Event)(VOID) = 0;
+  STDMETHOD(FireDownloadBeginEvent)(VOID) = 0;
+  STDMETHOD(FireDownloadCompleteEvent)(VOID) = 0;
+  STDMETHOD(FireDocumentCompleteEvent)(VOID) = 0;
+};
+
+// This interface is used in conjunction with the IWebBrowserEventsService
+// interface. The web browser queries us for this interface when we invoke
+// one of the IWebBrowserEventsService methods. This interface supplies the
+// WebBrowser with a URL to use for the events. MSDN documents this interface
+// (see http://msdn2.microsoft.com/en-us/library/aa752103(VS.85).aspx)
+// but this is not included in any Platform SDK header file.
+class __declspec(uuid("{87CC5D04-EAFA-4833-9820-8F986530CC00}"))
+IWebBrowserEventsUrlService : public IUnknown {
+ public:
+  STDMETHOD(GetUrlForEvents)(BSTR *url) = 0;
+};
+
+#endif  // CHROME_FRAME_EXTRA_SYSTEM_APIS_H_
diff --git a/chrome_frame/ff_30_privilege_check.cc b/chrome_frame/ff_30_privilege_check.cc
new file mode 100644
index 0000000..e2b6369c
--- /dev/null
+++ b/chrome_frame/ff_30_privilege_check.cc
@@ -0,0 +1,99 @@
+// Copyright (c) 2009 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.
+
+// This file relies on the 1.9 version of the unfrozen interfaces
+// "nsIScriptSecurityManager" and "nsIScriptObjectPrincipal"
+// from gecko 1.9, which means that this implementation is specific to
+// FireFox 3.0 and any other browsers built from the same gecko version.
+// See [http://en.wikipedia.org/wiki/Gecko_(layout_engine]
+// It's a good bet that nsIScriptSecurityManager will change for gecko
+// 1.9.1 and FireFox 3.5, in which case we'll need another instance of this
+// code for the 3.5 version of FireFox.
+
+// Gecko headers need this on Windows.
+#define XP_WIN
+#include "chrome_frame/script_security_manager.h"
+#include "third_party/xulrunner-sdk/win/include/dom/nsIScriptObjectPrincipal.h"
+#include "third_party/xulrunner-sdk/win/include/xpcom/nsIServiceManager.h"
+
+// These are needed to work around typedef conflicts in chrome headers.
+#define _UINT32
+#define _INT32
+
+#include "chrome_frame/np_browser_functions.h"
+#include "chrome_frame/scoped_ns_ptr_win.h"
+#include "chrome_frame/ns_associate_iid_win.h"
+#include "base/logging.h"
+
+ASSOCIATE_IID(NS_ISERVICEMANAGER_IID_STR, nsIServiceManager);
+
+namespace {
+// Unfortunately no NS_ISCRIPTOBJECTPRINCIPAL_IID_STR
+// defined for this interface
+nsIID IID_nsIScriptObjectPrincipal = NS_ISCRIPTOBJECTPRINCIPAL_IID;
+}  // namespace
+
+// Returns true iff we're being instantiated into a document
+// that has the system principal's privileges
+bool IsFireFoxPrivilegedInvocation(NPP instance) {
+  ScopedNsPtr<nsIServiceManager> service_manager;
+  NPError nperr = npapi::GetValue(instance, NPNVserviceManager,
+                                  service_manager.Receive());
+  if (nperr != NPERR_NO_ERROR || !service_manager.get())
+    return false;
+  DCHECK(service_manager);
+
+  // Get the document.
+  ScopedNsPtr<nsISupports> window;
+  nperr = npapi::GetValue(instance, NPNVDOMWindow, window.Receive());
+  if (nperr != NPERR_NO_ERROR || !window.get())
+    return false;
+  DCHECK(window);
+
+  // This interface allows us access to the window's principal.
+  ScopedNsPtr<nsIScriptObjectPrincipal, &IID_nsIScriptObjectPrincipal>
+      script_object_principal;
+  nsresult err = script_object_principal.QueryFrom(window);
+  if (NS_FAILED(err) || !script_object_principal.get())
+    return false;
+  DCHECK(script_object_principal);
+
+  // For regular HTML windows, this will be a principal encoding the
+  // document's origin. For browser XUL, this will be the all-powerful
+  // system principal.
+  nsIPrincipal* window_principal = script_object_principal->GetPrincipal();
+  DCHECK(window_principal);
+  if (!window_principal)
+    return false;
+
+  // Get the script security manager.
+  ScopedNsPtr<nsIScriptSecurityManager_FF35> security_manager_ff35;
+  PRBool is_system = PR_FALSE;
+
+  err = service_manager->GetServiceByContractID(
+      NS_SCRIPTSECURITYMANAGER_CONTRACTID,
+      nsIScriptSecurityManager_FF35::GetIID(),
+      reinterpret_cast<void**>(security_manager_ff35.Receive()));
+  if (NS_SUCCEEDED(err) && security_manager_ff35.get()) {
+    err = security_manager_ff35->IsSystemPrincipal(window_principal,
+                                                   &is_system);
+    if (NS_FAILED(err))
+      is_system = PR_FALSE;
+  } else {
+    ScopedNsPtr<nsIScriptSecurityManager_FF30> security_manager_ff30;
+    err = service_manager->GetServiceByContractID(
+        NS_SCRIPTSECURITYMANAGER_CONTRACTID,
+        nsIScriptSecurityManager_FF30::GetIID(),
+        reinterpret_cast<void**>(security_manager_ff30.Receive()));
+    if (NS_SUCCEEDED(err) && security_manager_ff30.get()) {
+      err = security_manager_ff30->IsSystemPrincipal(window_principal,
+                                                     &is_system);
+    }
+
+    if (NS_FAILED(err))
+      is_system = PR_FALSE;
+  }
+
+  return is_system == PR_TRUE;
+}
diff --git a/chrome_frame/ff_privilege_check.h b/chrome_frame/ff_privilege_check.h
new file mode 100644
index 0000000..a7b5c006
--- /dev/null
+++ b/chrome_frame/ff_privilege_check.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_FF_PRIVILEGE_CHECK_H_
+#define CHROME_FRAME_FF_PRIVILEGE_CHECK_H_
+
+// Returns true iff we're being invoked in a privileged document
+// in FireFox 3.x.
+// An example privileged document is when the plugin is instantiated
+// in browser chrome by XUL.
+bool IsFireFoxPrivilegedInvocation(NPP instance);
+
+#endif  // CHROME_FRAME_FF_PRIVILEGE_CHECK_H_
diff --git a/chrome_frame/find_dialog.cc b/chrome_frame/find_dialog.cc
new file mode 100644
index 0000000..15aaff7
--- /dev/null
+++ b/chrome_frame/find_dialog.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/find_dialog.h"
+
+#include <Richedit.h>
+
+#include "chrome_frame/chrome_frame_automation.h"
+
+const int kMaxFindChars = 1024;
+
+HHOOK CFFindDialog::msg_hook_ = NULL;
+
+CFFindDialog::CFFindDialog() {}
+
+void CFFindDialog::Init(ChromeFrameAutomationClient* automation_client) {
+  automation_client_ = automation_client;
+}
+
+LRESULT CFFindDialog::OnDestroy(UINT msg, WPARAM wparam, LPARAM lparam,
+                                BOOL& handled) {
+  UninstallMessageHook();
+  return 0;
+}
+
+LRESULT CFFindDialog::OnFind(WORD wNotifyCode, WORD wID, HWND hWndCtl,
+                             BOOL& bHandled) {
+  wchar_t buffer[kMaxFindChars + 1];
+  GetDlgItemText(IDC_FIND_TEXT, buffer, kMaxFindChars);
+  std::wstring find_text(buffer);
+
+  bool match_case = IsDlgButtonChecked(IDC_MATCH_CASE) == BST_CHECKED;
+  bool search_down = IsDlgButtonChecked(IDC_DIRECTION_DOWN) == BST_CHECKED;
+
+  automation_client_->FindInPage(find_text,
+                                 search_down ? FWD : BACK,
+                                 match_case ? CASE_SENSITIVE : IGNORE_CASE,
+                                 false);
+
+  return 0;
+}
+
+LRESULT CFFindDialog::OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl,
+                               BOOL& bHandled) {
+  DestroyWindow();
+  return 0;
+}
+
+LRESULT CFFindDialog::OnInitDialog(UINT msg, WPARAM wparam, LPARAM lparam,
+                                   BOOL& handled) {
+  // Init() must be called before Create() or DoModal()!
+  DCHECK(automation_client_);
+
+  InstallMessageHook();
+  SendDlgItemMessage(IDC_FIND_TEXT, EM_EXLIMITTEXT, 0, kMaxFindChars);
+  BOOL result = CheckRadioButton(IDC_DIRECTION_DOWN, IDC_DIRECTION_UP,
+                                 IDC_DIRECTION_DOWN);
+
+  HWND text_field = GetDlgItem(IDC_FIND_TEXT);
+  ::SetFocus(text_field);
+
+  return FALSE;  // we set the focus ourselves.
+}
+
+LRESULT CALLBACK CFFindDialog::GetMsgProc(int code, WPARAM wparam,
+                                          LPARAM lparam) {
+  // Mostly borrowed from http://support.microsoft.com/kb/q187988/
+  // and http://www.codeproject.com/KB/atl/cdialogmessagehook.aspx.
+  LPMSG msg = reinterpret_cast<LPMSG>(lparam);

+  if (code >= 0 && wparam == PM_REMOVE &&

+      msg->message >= WM_KEYFIRST && msg->message <= WM_KEYLAST) {

+    HWND hwnd = GetActiveWindow();

+    if (::IsWindow(hwnd) && ::IsDialogMessage(hwnd, msg)) {

+      // The value returned from this hookproc is ignored, and it cannot

+      // be used to tell Windows the message has been handled. To avoid

+      // further processing, convert the message to WM_NULL before

+      // returning.

+      msg->hwnd = NULL;

+      msg->message = WM_NULL;

+      msg->lParam = 0L;

+      msg->wParam = 0;

+    }

+  }

+

+  // Passes the hook information to the next hook procedure in

+  // the current hook chain.

+  return ::CallNextHookEx(msg_hook_, code, wparam, lparam);
+}
+
+bool CFFindDialog::InstallMessageHook() {
+  // Make sure we only call this once.
+  DCHECK(msg_hook_ == NULL);
+  msg_hook_ = ::SetWindowsHookEx(WH_GETMESSAGE, &CFFindDialog::GetMsgProc,

+                                 _AtlBaseModule.m_hInst, GetCurrentThreadId());

+  DCHECK(msg_hook_ != NULL);
+  return msg_hook_ != NULL;
+}
+
+bool CFFindDialog::UninstallMessageHook() {
+  DCHECK(msg_hook_ != NULL);
+  BOOL result = ::UnhookWindowsHookEx(msg_hook_);
+  DCHECK(result);
+  msg_hook_ = NULL;
+
+  return result != FALSE;
+}
diff --git a/chrome_frame/find_dialog.h b/chrome_frame/find_dialog.h
new file mode 100644
index 0000000..9e7cafe
--- /dev/null
+++ b/chrome_frame/find_dialog.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_FIND_DIALOG_H_
+#define CHROME_FRAME_FIND_DIALOG_H_
+
+#include <atlbase.h>
+#include <atlwin.h>
+
+#include "base/ref_counted.h"
+#include "resource.h"
+#include "grit/chrome_frame_resources.h"
+
+class ChromeFrameAutomationClient;
+
+class CFFindDialog : public CDialogImpl<CFFindDialog> {
+ public:
+  enum { IDD = IDD_FIND_DIALOG };
+
+  BEGIN_MSG_MAP(CFFindDialog)
+    MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
+    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
+    COMMAND_ID_HANDLER(IDOK, OnFind)
+    COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
+  END_MSG_MAP()
+
+  CFFindDialog();
+  void Init(ChromeFrameAutomationClient* automation_client);
+
+  LRESULT OnDestroy(UINT msg, WPARAM wparam,
+                    LPARAM lparam, BOOL& handled);  // NOLINT
+  LRESULT OnFind(WORD wNotifyCode, WORD wID,
+                 HWND hWndCtl, BOOL& bHandled);  // NOLINT
+  LRESULT OnCancel(WORD wNotifyCode, WORD wID,
+                   HWND hWndCtl, BOOL& bHandled);  // NOLINT
+  LRESULT OnInitDialog(UINT msg, WPARAM wparam,
+                       LPARAM lparam, BOOL& handled);  // NOLINT
+
+ private:
+
+  // Since the message loop we expect to run in isn't going to be nicely
+  // calling IsDialogMessage(), we need to hook the wnd proc and call it
+  // ourselves. See http://support.microsoft.com/kb/q187988/
+  bool InstallMessageHook();
+  bool UninstallMessageHook();
+  static LRESULT CALLBACK GetMsgProc(int code, WPARAM wparam, LPARAM lparam);
+  static HHOOK msg_hook_;
+
+  // We don't own these, and they must exist at least as long as we do.
+  ChromeFrameAutomationClient* automation_client_;
+};
+
+#endif  // CHROME_FRAME_FIND_DIALOG_H_
diff --git a/chrome_frame/frame.html b/chrome_frame/frame.html
new file mode 100644
index 0000000..d63d3ac
--- /dev/null
+++ b/chrome_frame/frame.html
@@ -0,0 +1,20 @@
+<html>
+<!-- TODO(slightlyoff): Move to tests directory? -->
+<head>
+<title>Script test</title>
+<script>
+function OnLoad() {
+  var host = window.externalHost;
+  host.onmessage = OnHostMessage;
+  host.ForwardMessageToExternalHost("Hello from ChromeFrame");
+}
+
+function OnHostMessage(text) {
+  window.alert("In ChromeFrame: \r\n Message from host: " + text);
+}
+</script>
+</head>
+<body onload="OnLoad();">
+Test script
+</body>
+</html> 
diff --git a/chrome_frame/frame_w_controls.html b/chrome_frame/frame_w_controls.html
new file mode 100644
index 0000000..d80642f
--- /dev/null
+++ b/chrome_frame/frame_w_controls.html
@@ -0,0 +1,29 @@
+<html>
+<!-- TODO(slightlyoff): Move to tests directory? -->
+<head>
+<title>Script test</title>
+<script>
+function msg(txt) {
+  window.document.getElementById("my_text").innerText = txt;
+}
+
+function OnLoad() {
+  var host = window.externalHost;
+  host.ForwardMessageToExternalHost("OnChromeFrameMessage",
+                                    "Hello from ChromeFrame");
+}
+
+function OnHostMessage(text) {
+  msg("In ChromeFrame: \r\n Message from host: " + text);
+}
+</script>
+</head>
+<body onload="OnLoad();">
+Here's an edit field: <input type="text"><br>
+Here's another: <input type="text"><br>
+<p>
+Message:<br>
+<pre><div id="my_text"></div></pre>
+</p>
+</body>
+</html> 
diff --git a/chrome_frame/function_stub.h b/chrome_frame/function_stub.h
new file mode 100644
index 0000000..55f0c5e
--- /dev/null
+++ b/chrome_frame/function_stub.h
@@ -0,0 +1,241 @@
+// Copyright (c) 2009 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.
+
+
+#ifndef CHROME_FRAME_FUNCTION_STUB_H_
+#define CHROME_FRAME_FUNCTION_STUB_H_
+
+#include <windows.h>
+#include "base/logging.h"
+
+// IMPORTANT: The struct below must be byte aligned.
+#pragma pack(push)
+#pragma pack(1)
+
+#ifndef _M_IX86
+#error Only x86 supported right now.
+#endif
+
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+
+// This struct is assembly code + signature.  The purpose of the struct is to be
+// able to hook an existing function with our own and store information such
+// as the original function pointer with the code stub.  Typically this is used
+// for patching entries of a vtable or e.g. a globally registered wndproc
+// for a class as opposed to a window.
+// When unhooking, you can just call the BypassStub() function and leave the
+// stub in memory.  This unhooks your function while leaving the (potential)
+// chain of patches intact.
+//
+// @note: This class is meant for __stdcall calling convention and
+//        it uses eax as a temporary variable.  The struct can
+//        be improved in the future to save eax before the 
+//        operation and then restore it.
+//
+// For instance if the function prototype is:
+//
+// @code
+//   LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
+// @endcode
+//
+// and we would like to add one static argument to make it, say:
+//
+// @code
+//   LRESULT MyNewWndProc(WNDPROC original, HWND hwnd, UINT msg, 
+//                        WPARAM wparam, LPARAM lparam);
+// @endcode
+//
+// That can be achieved by wrapping the function up with a FunctionStub:
+//
+// @code
+//   FunctionStub* stub = FunctionStub::Create(original_wndproc, MyNewWndProc);
+//   SetClassLongPtr(wnd, GCLP_WNDPROC, stub->code());
+// @endcode
+struct FunctionStub {
+ private:
+  typedef enum AsmConstants {
+    POP_EAX = 0x58,
+    PUSH = 0x68,
+    PUSH_EAX = 0x50,
+    JUMP_RELATIVE = 0xE9
+  };
+
+  FunctionStub(uintptr_t extra_argument, void* dest)
+      : signature_(reinterpret_cast<HMODULE>(&__ImageBase)) {
+    Opcodes::Hook& hook = code_.hook_;
+    hook.pop_return_addr_ = POP_EAX;
+    hook.push_ = PUSH;
+    hook.arg_ = extra_argument;
+    hook.push_return_addr_ = PUSH_EAX;
+    hook.jump_to_ = JUMP_RELATIVE;
+
+    // Calculate the target jump to the destination function.
+    hook.target_ = CalculateRelativeJump(dest, &hook.jump_to_);
+
+    // Allow the process to execute this struct as code.
+    DWORD old_protect = 0;
+    // Allow reads too since we want read-only member variable access at
+    // all times.
+    ::VirtualProtect(this, sizeof(FunctionStub), PAGE_EXECUTE_READ,
+                     &old_protect);
+  }
+
+  ~FunctionStub() {
+    // No more execution rights.
+    DWORD old_protect = 0;
+    ::VirtualProtect(this, sizeof(FunctionStub), PAGE_READWRITE, &old_protect);
+  }
+
+  // Calculates the target value for a relative jump.
+  // The function assumes that the size of the opcode is 1 byte.
+  inline uintptr_t CalculateRelativeJump(const void* absolute_to,
+                                         const void* absolute_from) const {
+    return (reinterpret_cast<uintptr_t>(absolute_to) -
+            reinterpret_cast<uintptr_t>(absolute_from)) - 
+           (sizeof(uint8) + sizeof(uintptr_t));
+  }
+
+  // Does the opposite of what CalculateRelativeJump does, which is to
+  // calculate back the absolute address that previously was relative to
+  // some other address.
+  inline uintptr_t CalculateAbsoluteAddress(const void* relative_to,
+                                            uintptr_t relative_address) const {
+    return relative_address + sizeof(uint8) + sizeof(uintptr_t) +
+           reinterpret_cast<uintptr_t>(relative_to);
+  }
+
+  // Used to identify function stubs that belong to this module.
+  HMODULE signature_;
+
+  // IMPORTANT: Do not change the order of member variables
+  union Opcodes {
+    // Use this struct when the stub forwards the call to our hook function
+    // providing an extra argument.
+    struct Hook {
+      uint8 pop_return_addr_;  // pop eax
+      uint8 push_;             // push arg  ; push...
+      uintptr_t arg_;          //           ; extra argument
+      uint8 push_return_addr_; // push eax  ; push the return address
+      uint8 jump_to_;          // jmp       ; jump...
+      uintptr_t target_;       //           ; to the hook function
+    } hook_;
+    // When the stub is bypassed, we jump directly to a given target,
+    // usually the originally hooked function.
+    struct Bypassed {
+      uint8 jump_to_;          // jmp to
+      uintptr_t target_;       // relative target.
+    } bypassed_;
+  };
+
+  Opcodes code_;
+
+ public:
+  // Neutralizes this stub and converts it to a direct jump to a new target.
+  void BypassStub(void* new_target) {
+    DWORD old_protect = 0;
+    // Temporarily allow us to write to member variables
+    ::VirtualProtect(this, sizeof(FunctionStub), PAGE_EXECUTE_READWRITE,
+                     &old_protect);
+
+    // Now, just change the first 5 bytes to jump directly to the new target.
+    Opcodes::Bypassed& bypassed = code_.bypassed_;
+    bypassed.jump_to_ = JUMP_RELATIVE;
+    bypassed.target_ = CalculateRelativeJump(new_target, &bypassed.jump_to_);
+
+    // Restore the previous protection flags.
+    ::VirtualProtect(this, sizeof(FunctionStub), old_protect, &old_protect);
+
+    // Flush the instruction cache just in case.
+    ::FlushInstructionCache(::GetCurrentProcess(), this, sizeof(FunctionStub));
+  }
+
+  // @returns the argument supplied in the call to @ref Create
+  inline uintptr_t argument() const {
+    DCHECK(code_.hook_.pop_return_addr_ == POP_EAX) << "stub has been disabled";
+    return code_.hook_.arg_;
+  }
+
+  inline bool is_bypassed() const {
+    return code_.bypassed_.jump_to_ == JUMP_RELATIVE;
+  }
+
+  inline uintptr_t absolute_target() const {
+    DCHECK(code_.hook_.pop_return_addr_ == POP_EAX) << "stub has been disabled";
+    return CalculateAbsoluteAddress(
+        reinterpret_cast<const void*>(&code_.hook_.jump_to_),
+        code_.hook_.target_);
+  }
+
+  // Returns true if the stub is valid and enabled.
+  // Don't call this method after bypassing the stub.
+  inline bool is_valid() const {
+    return signature_ == reinterpret_cast<HMODULE>(&__ImageBase) &&
+        code_.hook_.pop_return_addr_ == POP_EAX;
+  }
+
+  inline PROC code() const {
+    return reinterpret_cast<PROC>(const_cast<Opcodes*>(&code_));
+  }
+
+  // Use to create a new function stub as shown above.
+  //
+  // @param extra_argument The static argument to pass to the function.
+  // @param dest Target function to which the stub applies.
+  // @returns NULL if an error occurs, otherwise a pointer to the
+  // function stub.
+  //
+  // TODO(tommi): Change this so that VirtualAlloc isn't called for
+  // every stub.  Instead we should re-use each allocation for
+  // multiple stubs.  In practice I'm guessing that there would
+  // only be one allocation per process, since each allocation actually
+  // allocates at least one page of memory (4K).  Size of FunctionStub
+  // is 12 bytes, so one page could house 341 function stubs.
+  // When stubs are created frequently, VirtualAlloc could fail
+  // and last error is ERROR_NOT_ENOUGH_MEMORY (8).
+  static FunctionStub* Create(uintptr_t extra_argument, void* dest) {
+    DCHECK(dest);
+
+    // Use VirtualAlloc to get memory for the stub.  This gives us a
+    // whole page that we don't share with anyone else.
+    // Initially the memory must be READWRITE to allow for construction
+    // PAGE_EXECUTE is set in constructor.
+    FunctionStub* ret = reinterpret_cast<FunctionStub*>(VirtualAlloc(NULL, 
+        sizeof(FunctionStub), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
+
+    if (!ret) {
+      NOTREACHED();
+    } else {
+      // Construct
+      ret->FunctionStub::FunctionStub(extra_argument, dest);
+    }
+
+    return ret;
+  }
+
+  static FunctionStub* FromCode(void* address) {
+    Opcodes* code = reinterpret_cast<Opcodes*>(address);
+    if (code->hook_.pop_return_addr_ == POP_EAX) {
+      FunctionStub* stub = reinterpret_cast<FunctionStub*>(
+          reinterpret_cast<uint8*>(address) - sizeof(HMODULE));
+      if (stub->is_valid())
+        return stub;
+    }
+
+    return NULL;
+  }
+
+  // Deallocates a FunctionStub.  The stub must not be in use on any thread!
+  static bool Destroy(FunctionStub* stub) {
+    DCHECK(stub != NULL);
+    FunctionStub* to_free = reinterpret_cast<FunctionStub*>(stub);
+    to_free->FunctionStub::~FunctionStub();
+    BOOL success = VirtualFree(to_free, sizeof(FunctionStub), MEM_DECOMMIT);
+    DCHECK(success) << "VirtualFree";
+    return success != FALSE;
+  }
+};
+
+#pragma pack(pop)
+
+#endif  // CHROME_FRAME_FUNCTION_STUB_H_
diff --git a/chrome_frame/host.html b/chrome_frame/host.html
new file mode 100644
index 0000000..a3da73cf
--- /dev/null
+++ b/chrome_frame/host.html
@@ -0,0 +1,47 @@
+<HTML>
+<!-- TODO(slightlyoff): Move to tests directory? -->
+<HEAD>
+<TITLE> Chrome Frame Test </TITLE>
+<SCRIPT type="text/javascript">
+function GetChromeFrame() {
+  var chromeFrame = window.document.ChromeFrame
+  return chromeFrame;
+}
+
+function OnChromeFrameMessage(text) {
+  window.alert("In host:  \r\nMessage from ChromeFrame: " + text);
+  
+  var chromeFrame = GetChromeFrame();
+  chromeFrame.PostMessageToFrame("Hello from host");
+  return "OK";
+}
+
+function OnNavigate() {
+  var url = document.getElementById('inputurl');
+  var chromeFrame = GetChromeFrame();
+  chromeFrame.src = url.value;
+}
+
+function onLoad() {
+  var chromeFrame = GetChromeFrame();
+  chromeFrame.onmessage = OnChromeFrameMessage;
+}
+
+</SCRIPT>
+</HEAD>
+<BODY onload="onLoad();">
+Chrome Frame Test activex
+<br><br>
+<input id="inputurl" type="text" name="URL">
+<input type="submit" value="Navigate" onClick="OnNavigate();"> 
+<center>
+<OBJECT ID="ChromeFrame" WIDTH=500 HEIGHT=500 CODEBASE="http://www.google.com"
+        CLASSID="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+    <PARAM NAME="src" VALUE="http://www.google.com">
+    <embed ID="ChromeFramePlugin" WIDTH=500 HEIGHT=500 NAME="ChromeFrame"
+        SRC="http://www.google.com" TYPE="application/chromeframe">
+    </embed>
+</OBJECT>
+</center>
+</BODY>
+</HTML>
diff --git a/chrome_frame/host_w_controls.html b/chrome_frame/host_w_controls.html
new file mode 100644
index 0000000..0e44fc5
--- /dev/null
+++ b/chrome_frame/host_w_controls.html
@@ -0,0 +1,97 @@
+<HTML>
+<!-- TODO(slightlyoff): Move to tests directory? -->
+<HEAD>
+<TITLE> Chrome Frame Test </TITLE>
+<SCRIPT type="text/javascript">
+function msg(txt) {
+  try {
+    document.getElementById("my_text").innerHTML = txt;
+  } catch(e) {
+    alert("error");
+  }
+}
+
+function GetChromeFrame() {
+  var chromeFrame = window.document.ChromeFrame
+  return chromeFrame;
+}
+
+function OnChromeFrameMessage(text) {
+  msg("In host:  \r\nMessage from ChromeFrame: " + text);
+  
+  var chromeFrame = GetChromeFrame();
+  chromeFrame.PostMessageToFrame("OnHostMessage", "Hello from host");
+  return "OK";
+}
+
+function OnNavigate() {
+  var url = document.getElementById('inputurl');
+  GetChromeFrame().src = url.value;
+}
+
+function OnFocus() {
+  msg("OnFocus");
+}
+
+window.onload = function() {
+  var chromeFrame = GetChromeFrame();
+  var url = location.href;
+  url = url.substr(0, url.lastIndexOf('/') + 1) + "frame_w_controls.html";
+  chromeFrame.src = url;
+
+  try {
+    var cf = document.getElementById('ChromeFrame');
+    cf.addEventListener("focus", OnFocus, true);
+    cf.addEventListener("blur", function() { msg('blur'); }, true);
+    msg("ready");
+  } catch(e) {
+    alert("error");
+  }
+}
+
+function setFocusToCf() {
+  var cf = document.getElementById('ChromeFrame');
+  cf.focus();
+  // alert(cf.hasFocus());
+  return true;
+}
+
+</SCRIPT>
+<style>
+/* CSS magic to avoid the focus rect */
+object:focus {
+    outline: 0;
+}
+</style>
+<!--
+object:focus { outline: none; }
+:focus { outline: none }
+a:focus          { outline: 1px dotted invert }
+-->
+</HEAD>
+<BODY>
+Chrome Frame Test activex
+<br><br>
+<input id="inputurl" type="text" name="URL">
+<input type="submit" value="Navigate" onClick="OnNavigate();"> 
+<center>
+<OBJECT ID="ChromeFrame" tabindex="0"
+        WIDTH="500"
+        HEIGHT="300"
+        CODEBASE="http://www.google.com"
+        CLASSID="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+    <!-- <PARAM NAME="BackColor" VALUE="100"> -->
+    <!-- <PARAM NAME="src" VALUE="file:///z:/code/debug/test.html"> -->
+    <embed ID="ChromeFramePlugin" WIDTH=500 HEIGHT=300 NAME="ChromeFrame"
+        SRC="http://www.google.com" TYPE="application/chromeframe">
+    </embed>
+</OBJECT>
+<p>To test the focus: <input id="fake_edit" type="text" name="fake"></p>
+<p><button onclick="return setFocusToCf();">SetFocusToCF</button></p>
+<p>
+Message:<br>
+<pre><p id="my_text"></p></pre>
+</p>
+</center>
+</BODY>
+</HTML>
diff --git a/chrome_frame/html_utils.cc b/chrome_frame/html_utils.cc
new file mode 100644
index 0000000..60fdd7e
--- /dev/null
+++ b/chrome_frame/html_utils.cc
@@ -0,0 +1,281 @@
+// Copyright (c) 2009 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.

+//

+#include "chrome_frame/html_utils.h"

+

+#include "base/string_util.h"

+#include "base/string_tokenizer.h"

+

+const wchar_t* kQuotes = L"\"'";

+

+HTMLScanner::StringRange::StringRange() {

+}

+

+HTMLScanner::StringRange::StringRange(StrPos start, StrPos end)

+    : start_(start), end_(end) {

+}

+

+bool HTMLScanner::StringRange::LowerCaseEqualsASCII(const char* other) const {

+  return ::LowerCaseEqualsASCII(start_, end_, other);

+}

+

+bool HTMLScanner::StringRange::Equals(const wchar_t* other) const {

+  int ret = wcsncmp(&start_[0], other, end_ - start_);

+  if (ret == 0)

+    ret = (other[end_ - start_] == L'\0') ? 0 : -1;

+  return ret == 0;

+}

+

+std::wstring HTMLScanner::StringRange::Copy() const {

+  return std::wstring(start_, end_);

+}

+

+bool HTMLScanner::StringRange::GetTagName(std::wstring* tag_name) const {

+  if (*start_ != L'<') {

+    LOG(ERROR) << "Badly formatted tag found";

+    return false;

+  }

+

+  StrPos name_start = start_;

+  name_start++;

+  while (name_start < end_ && IsWhitespace(*name_start))

+    name_start++;

+

+  if (name_start >= end_) {

+    // We seem to have a degenerate tag (i.e. <   >). Return false here.

+    return false;

+  }

+

+  StrPos name_end = name_start + 1;

+  while (name_end < end_ && !IsWhitespace(*name_end))

+    name_end++;

+

+  if (name_end > end_) {

+    // This looks like an improperly formatted tab ('<foo'). Return false here.

+    return false;

+  }

+

+  tag_name->assign(name_start, name_end);

+  return true;

+}

+

+

+bool HTMLScanner::StringRange::GetTagAttribute(const wchar_t* attribute_name,

+    StringRange* attribute_value) const {

+  if (NULL == attribute_name || NULL == attribute_value) {

+    NOTREACHED();

+    return false;

+  }

+

+  // Use this so we can use the convenience method LowerCaseEqualsASCII()

+  // from string_util.h.

+  std::string search_name_ascii(WideToASCII(attribute_name));

+

+  WStringTokenizer tokenizer(start_, end_, L" =/");

+  tokenizer.set_options(WStringTokenizer::RETURN_DELIMS);

+

+  // Set up the quote chars so that we get quoted attribute values as single

+  // tokens.

+  tokenizer.set_quote_chars(L"\"'");

+

+  const bool PARSE_STATE_NAME = true;

+  const bool PARSE_STATE_VALUE = false;

+  bool parse_state = PARSE_STATE_NAME;

+

+  // Used to skip the first token, which is the tag name.

+  bool first_token_skipped = false;

+

+  // This is set during a loop iteration in which an '=' sign was spotted.

+  // It is used to filter out degenerate tags such as:

+  // <meta foo==bar>

+  bool last_token_was_delim = false;

+

+  // Set this if the attribute name has been found that we might then

+  // pick up the value in the next loop iteration.

+  bool attribute_name_found = false;

+

+  while (tokenizer.GetNext()) {

+    // If we have a whitespace delimiter, just keep going. Cases of this should

+    // be reduced by the CollapseWhitespace call. If we have an '=' character,

+    // we update our state and reiterate.

+    if (tokenizer.token_is_delim()) {

+      if (*tokenizer.token_begin() == L'=') {

+        if (last_token_was_delim) {

+          // Looks like we have a badly formed tag, just stop parsing now.

+          return false;

+        }

+        parse_state = !parse_state;

+        last_token_was_delim = true;

+      }

+      continue;

+    }

+

+    last_token_was_delim = false;

+

+    // The first non-delimiter token is the tag name, which we don't want.

+    if (!first_token_skipped) {

+      first_token_skipped = true;

+      continue;

+    }

+

+    if (PARSE_STATE_NAME == parse_state) {

+      // We have a tag name, check to see if it matches our target name:

+      if (::LowerCaseEqualsASCII(tokenizer.token_begin(), tokenizer.token_end(),

+                                 search_name_ascii.c_str())) {

+        attribute_name_found = true;

+        continue;

+      }

+    } else if (PARSE_STATE_VALUE == parse_state && attribute_name_found) {

+      attribute_value->start_ = tokenizer.token_begin();

+      attribute_value->end_ = tokenizer.token_end();

+

+      // Unquote the attribute value if need be.

+      attribute_value->UnQuote();

+

+      return true;

+    } else if (PARSE_STATE_VALUE == parse_state) {

+      // If we haven't found the attribute name we want yet, ignore this token

+      // and go back to looking for our name.

+      parse_state = PARSE_STATE_NAME;

+    }

+  }

+

+  return false;

+}

+

+bool HTMLScanner::StringRange::UnQuote() {

+  if (start_ + 2 > end_) {

+    // String's too short to be quoted, bail.

+    return false;

+  }

+

+  if ((*start_ == L'\'' && *(end_ - 1) == L'\'') ||

+      (*start_ == L'"' && *(end_ - 1) == L'"')) {

+    start_ = start_ + 1;

+    end_ = end_ - 1;

+    return true;

+  }

+

+  return false;

+}

+

+HTMLScanner::HTMLScanner(const wchar_t* html_string)

+    : html_string_(CollapseWhitespace(html_string, true)),

+      quotes_(kQuotes) {

+}

+

+void HTMLScanner::GetTagsByName(const wchar_t* name, StringRangeList* tag_list,

+                                const wchar_t* stop_tag) {

+  DCHECK(NULL != name);

+  DCHECK(NULL != tag_list);

+  DCHECK(NULL != stop_tag);

+

+  StringRange remaining_html(html_string_.begin(), html_string_.end());

+

+  std::wstring search_name(name);

+  TrimWhitespace(search_name, TRIM_ALL, &search_name);

+

+  // Use this so we can use the convenience method LowerCaseEqualsASCII()

+  // from string_util.h.

+  std::string search_name_ascii(WideToASCII(search_name));

+  std::string stop_tag_ascii(WideToASCII(stop_tag));

+

+  StringRange current_tag;

+  std::wstring current_name;

+  while (NextTag(&remaining_html, &current_tag)) {

+    if (current_tag.GetTagName(&current_name)) {

+      if (LowerCaseEqualsASCII(current_name, search_name_ascii.c_str())) {

+        tag_list->push_back(current_tag);

+      } else if (LowerCaseEqualsASCII(current_name, stop_tag_ascii.c_str())) {

+        // We hit the stop tag so it's time to go home.

+        break;

+      }

+    }

+  }

+}

+

+struct ScanState {

+  bool in_quote;

+  bool in_escape;

+  wchar_t quote_char;

+  ScanState() : in_quote(false), in_escape(false) {}

+};

+

+bool HTMLScanner::IsQuote(wchar_t c) {

+  return quotes_.find(c) != std::wstring::npos;

+}

+

+bool HTMLScanner::IsHTMLCommentClose(StringRange* html_string, StrPos pos) {

+  if (pos < html_string->end_ && pos > html_string->start_ + 2 &&

+      *pos == L'>') {

+    return *(pos-1) == L'-' && *(pos-2) == L'-';

+  }

+  return false;

+}

+

+bool HTMLScanner::NextTag(StringRange* html_string, StringRange* tag) {

+  DCHECK(NULL != html_string);

+  DCHECK(NULL != tag);

+

+  tag->start_ = html_string->start_;

+  while (tag->start_ < html_string->end_ && *tag->start_ != L'<') {

+    tag->start_++;

+  }

+

+  // we went past the end of the string.

+  if (tag->start_ >= html_string->end_) {

+    return false;

+  }

+

+  tag->end_ = tag->start_ + 1;

+

+  // Get the tag name to see if we are in an HTML comment. If we are, then

+  // don't consider quotes. This should work for example:

+  // <!-- foo ' --> <meta foo='bar'>

+  std::wstring tag_name;

+  StringRange start_range(tag->start_, html_string->end_);

+  start_range.GetTagName(&tag_name);

+  if (StartsWith(tag_name, L"!--", true)) {

+    // We're inside a comment tag, keep going until we get out of it.

+    while (tag->end_ < html_string->end_ &&

+           !IsHTMLCommentClose(html_string, tag->end_)) {

+      tag->end_++;

+    }

+  } else {

+    // Properly handle quoted strings within non-comment tags by maintaining

+    // some state while scanning. Specifically, we have to maintain state on

+    // whether we are inside a string, what the string terminating character

+    // will be and whether we are inside an escape sequence.

+    ScanState state;

+    while (tag->end_ < html_string->end_) {

+      if (state.in_quote) {

+        if (state.in_escape) {

+          state.in_escape = false;

+        } else if (*tag->end_ == '\\') {

+          state.in_escape = true;

+        } else if (*tag->end_ == state.quote_char) {

+          state.in_quote = false;

+        }

+      } else {

+        state.in_quote = IsQuote(state.quote_char = *tag->end_);

+      }

+

+      if (!state.in_quote && *tag->end_ == L'>') {

+        break;

+      }

+      tag->end_++;

+    }

+  }

+

+  // We hit the end_ but found no matching tag closure. Consider this an

+  // incomplete tag and do not report it.

+  if (tag->end_ >= html_string->end_)

+    return false;

+

+  // Modify html_string to point to just beyond the end_ of the current tag.

+  html_string->start_ = tag->end_ + 1;

+

+  return true;

+}

+

diff --git a/chrome_frame/html_utils.h b/chrome_frame/html_utils.h
new file mode 100644
index 0000000..c670b004
--- /dev/null
+++ b/chrome_frame/html_utils.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2009 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.

+

+// This file defines utility functions for working with html.

+

+#ifndef CHROME_FRAME_HTML_UTILS_H_

+#define CHROME_FRAME_HTML_UTILS_H_

+

+#include <string>

+#include <vector>

+

+#include "base/basictypes.h"

+#include "testing/gtest/include/gtest/gtest_prod.h"

+

+// Forward declarations

+class HtmlUtilUnittest;

+

+//

+// Class designed to take a string of HTML and extract from it named

+// attribute values from named tags.

+//

+// Caveat: this class currently doesn't handle multi-word UTF-16 encoded

+// characters. Doesn't handle implies that any data following such a

+// character could possibly be misinterpreted.

+//

+class HTMLScanner {

+ public:

+  typedef std::wstring::const_iterator StrPos;

+

+  // Structure maintaining const_iterators into html_string_.

+  class StringRange {

+    friend class HTMLScanner;

+   public:

+    StringRange();

+    StringRange(StrPos start, StrPos end);

+

+    bool LowerCaseEqualsASCII(const char* other) const;

+    bool Equals(const wchar_t* other) const;

+

+    // Copies the data described by StringRange into destination.

+    std::wstring Copy() const;

+

+    // If this StringRange represents a tag, this method extracts the name of

+    // the tag and sticks it in tag_name.

+    // Returns true if the tag name was successfully extracted.

+    // Returns false if this string doesn't look like a valid tag.

+    bool GetTagName(std::wstring* tag_name) const;

+

+    // From a given string range, uses a string tokenizer to extract the value

+    // of the named attribute if a simple scan finds that the attribute name is

+    // present.

+    //

+    // Returns true if the named attribute can be located and it has a value

+    // which has been placed in attribute_value.

+    //

+    // Note that the attribute value is unquoted here as well, so that

+    // GetTagAttribute(*<foo bar="baz">*, L"bar", *out_value*) will stick

+    // 'bar' in out_value and not '"bar"'.

+    //

+    // Returns false if the named attribute is not present in the tag or if it

+    // did not have a value.

+    //

+    bool GetTagAttribute(const wchar_t* attribute_name,

+                         StringRange* attribute_value) const;

+

+    // Unquotes a StringRange by removing a matching pair of either ' or "

+    // characters from the beginning and end of the string if present.

+    // Returns true if string was modified, false otherwise.

+    bool UnQuote();

+   private:

+     StrPos start_;

+     StrPos end_;

+  };

+

+  typedef std::vector<StringRange> StringRangeList;

+

+  // html_string must be a null-terminated string containing the HTML

+  // to be scanned.

+  explicit HTMLScanner(const wchar_t* html_string);

+

+  // Returns the set of ranges denoting HTML tags that match the given name.

+  // If stop_tag_name is given, then as soon as a tag with this name is

+  // encountered this method will return.

+  void GetTagsByName(const wchar_t* name, StringRangeList* tag_list,

+                     const wchar_t* stop_tag_name);

+

+ private:

+  friend class HtmlUtilUnittest;

+  FRIEND_TEST(HtmlUtilUnittest, BasicTest);

+

+  // Given html_string which represents the remaining html range, this method

+  // returns the next tag in tag and advances html_string to one character after

+  // the end of tag. This method is intended to be called repeatedly to extract

+  // all of the tags in sequence.

+  //

+  // Returns true if another tag was found and 'tag' was populated with a valid

+  // range.

+  // Returns false if we have reached the end of the html data.

+  bool NextTag(StringRange* html_string, StringRange* tag);

+

+  // Returns true if c can be found in quotes_, false otherwise

+  bool IsQuote(wchar_t c);

+

+  // Returns true if pos refers to the last character in an HTML comment in a

+  // string described by html_string, false otherwise.

+  // For example with html_string describing <!-- foo> -->, pos must refer to

+  // the last > for this method to return true.

+  bool IsHTMLCommentClose(StringRange* html_string, StrPos pos);

+

+  // We store a (CollapsedWhitespace'd) copy of the html data.

+  const std::wstring html_string_;

+

+  // Store the string of quote characters to avoid repeated construction.

+  const std::wstring quotes_;

+

+  DISALLOW_COPY_AND_ASSIGN(HTMLScanner);

+};

+

+#endif  // CHROME_FRAME_HTML_UTILS_H_

diff --git a/chrome_frame/icu_stubs.cc b/chrome_frame/icu_stubs.cc
new file mode 100644
index 0000000..2f3abcf
--- /dev/null
+++ b/chrome_frame/icu_stubs.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2009 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.
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "googleurl/src/url_canon.h"
+
+#include <windows.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// Avoid dependency on string_util_icu.cc (which pulls in icu).
+
+std::string WideToAnsiDirect(const wchar_t* wide, size_t wide_len) {
+  std::string ret;
+  char* write = WriteInto(&ret, wide_len + 1);
+  for (size_t i = 0; i < wide_len; ++i) {
+    // We can only convert characters below 0x80 directly from wide to ansi.
+    DCHECK(wide[i] <= 127) << "can't convert";
+    write[i] = static_cast<char>(wide[i]);
+  }
+
+  write[wide_len] = '\0';
+
+  return ret;
+}
+
+bool WideToUTF8(const wchar_t* wide, size_t wide_len, std::string* utf8) {
+  DCHECK(utf8);
+
+  // Add a cutoff. If it's all ASCII, convert it directly
+  size_t i;
+  for (i = 0; i < wide_len; ++i) {
+    if (wide[i] > 127)
+      break;
+  }
+
+  // If we made it to the end without breaking, then it's all ANSI, so do a
+  // quick convert
+  if (i == wide_len) {
+    *utf8 = WideToAnsiDirect(wide, wide_len);
+    return true;
+  }
+
+  // Figure out how long the string is
+  int size = WideCharToMultiByte(CP_UTF8, 0, wide, wide_len + 1, NULL, 0, NULL,
+                                 NULL);
+
+  if (size > 0) {
+    WideCharToMultiByte(CP_UTF8, 0, wide, wide_len + 1, WriteInto(utf8, size),
+                        size, NULL, NULL);
+  }
+
+  return (size > 0);
+}
+
+std::string WideToUTF8(const std::wstring& wide) {
+  std::string ret;
+  if (!wide.empty()) {
+    // Ignore the success flag of this call, it will do the best it can for
+    // invalid input, which is what we want here.
+    WideToUTF8(wide.data(), wide.length(), &ret);
+  }
+  return ret;
+}
+
+bool UTF8ToWide(const char* src, size_t src_len, std::wstring* output) {
+  DCHECK(output);
+
+  if (src_len == 0) {
+    output->clear();
+    return true;
+  }
+
+  int wide_chars = MultiByteToWideChar(CP_UTF8, 0, src, src_len, NULL, 0);
+  if (!wide_chars) {
+    NOTREACHED();
+    return false;
+  }
+
+  wide_chars++;  // make room for L'\0'
+  // Note that WriteInto will fill the string with '\0', so in the case
+  // where the input string is not \0 terminated, we will still be ensured
+  // that the output string will be.
+  if (!MultiByteToWideChar(CP_UTF8, 0, src, src_len,
+                           WriteInto(output, wide_chars), wide_chars)) {
+    NOTREACHED();
+    output->clear();
+    return false;
+  }
+
+  return true;
+}
+
+std::wstring UTF8ToWide(const base::StringPiece& utf8) {
+  std::wstring ret;
+  if (!utf8.empty())
+    UTF8ToWide(utf8.data(), utf8.length(), &ret);
+  return ret;
+}
+
+#ifdef WCHAR_T_IS_UTF16
+string16 UTF8ToUTF16(const std::string& utf8) {
+  std::wstring ret;
+  if (!utf8.empty())
+    UTF8ToWide(utf8.data(), utf8.length(), &ret);
+  return ret;
+}
+#else
+#error Need WCHAR_T_IS_UTF16
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Replace ICU dependent functions in googleurl.
+/*#define __UTF_H__
+#include "third_party/icu38/public/common/unicode/utf16.h"
+#define U_IS_SURROGATE(c) (((c)&0xfffff800)==0xd800)
+extern const char16 kUnicodeReplacementCharacter;*/
+
+namespace url_canon {
+
+bool IDNToASCII(const char16* src, int src_len, CanonOutputW* output) {
+  // We should only hit this when the user attempts to navigate
+  // CF to an invalid URL.
+  DLOG(WARNING) << __FUNCTION__ << " not implemented";
+  return false;
+}
+
+bool ReadUTFChar(const char* str, int* begin, int length,
+                 unsigned* code_point_out) {
+  // We should only hit this when the user attempts to navigate
+  // CF to an invalid URL.
+  DLOG(WARNING) << __FUNCTION__ << " not implemented";
+
+  // TODO(tommi): consider if we can use something like
+  // http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+  return false;
+}
+
+bool ReadUTFChar(const char16* str, int* begin, int length,
+                 unsigned* code_point) {
+/*
+  if (U16_IS_SURROGATE(str[*begin])) {
+    if (!U16_IS_SURROGATE_LEAD(str[*begin]) || *begin + 1 >= length ||
+        !U16_IS_TRAIL(str[*begin + 1])) {
+      // Invalid surrogate pair.
+      *code_point = kUnicodeReplacementCharacter;
+      return false;
+    } else {
+      // Valid surrogate pair.
+      *code_point = U16_GET_SUPPLEMENTARY(str[*begin], str[*begin + 1]);
+      (*begin)++;
+    }
+  } else {
+    // Not a surrogate, just one 16-bit word.
+    *code_point = str[*begin];
+  }
+
+  if (U_IS_UNICODE_CHAR(*code_point))
+    return true;
+
+  // Invalid code point.
+  *code_point = kUnicodeReplacementCharacter;
+  return false;*/
+  CHECK(false);
+  return false;
+}
+
+}  // namespace url_canon
diff --git a/chrome_frame/ie8_types.h b/chrome_frame/ie8_types.h
new file mode 100644
index 0000000..c683471
--- /dev/null
+++ b/chrome_frame/ie8_types.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_IE8_TYPES_H_
+#define CHROME_FRAME_IE8_TYPES_H_
+
+#include <urlmon.h>
+
+#ifndef __IInternetBindInfoEx_INTERFACE_DEFINED__
+#define __IInternetBindInfoEx_INTERFACE_DEFINED__
+
+#define IID_IInternetBindInfoEx (__uuidof(IInternetBindInfoEx))
+
+MIDL_INTERFACE("A3E015B7-A82C-4DCD-A150-569AEEED36AB")
+IInternetBindInfoEx : public IInternetBindInfo {
+ public:
+  virtual HRESULT STDMETHODCALLTYPE GetBindInfoEx(
+      DWORD *grfBINDF,
+      BINDINFO *pbindinfo,
+      DWORD *grfBINDF2,
+      DWORD *pdwReserved) = 0;
+};
+
+#endif __IInternetBindInfoEx_INTERFACE_DEFINED__
+
+#endif //  CHROME_FRAME_IE8_TYPES_H_
diff --git a/chrome_frame/iids.cc b/chrome_frame/iids.cc
new file mode 100644
index 0000000..681250a
--- /dev/null
+++ b/chrome_frame/iids.cc
@@ -0,0 +1,10 @@
+// Copyright (c) 2009 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.
+
+// Pull in IIDs, CLSIDs etc from our .idl file.
+#include "chrome_tab.h"
+
+extern "C" {
+#include "chrome_tab_i.c"
+}
diff --git a/chrome_frame/in_place_menu.h b/chrome_frame/in_place_menu.h
new file mode 100644
index 0000000..9742896
--- /dev/null
+++ b/chrome_frame/in_place_menu.h
@@ -0,0 +1,231 @@
+// Copyright 2008, Google Inc.
+// 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.
+
+#ifndef CHROME_FRAME_IN_PLACE_MENU_H_
+#define CHROME_FRAME_IN_PLACE_MENU_H_
+
+// in_place_menu.h : menu merging implementation
+//
+// This file is a modified version of the menu.h file, which is
+// part of the ActiveDoc MSDN sample. The modifications are largely
+// conversions to Google coding guidelines. Below is the original header
+// from the file.
+
+// This is a part of the Active Template Library.
+// Copyright (c) Microsoft Corporation.  All rights reserved.
+//
+// This source code is only intended as a supplement to the
+// Active Template Library Reference and related
+// electronic documentation provided with the library.
+// See these sources for detailed information regarding the
+// Active Template Library product.
+
+#include "base/logging.h"
+#include "base/scoped_comptr_win.h"
+
+template <class T>
+class InPlaceMenu {
+ public:
+  InPlaceMenu() : shared_menu_(NULL), ole_menu_(NULL), our_menu_(NULL) {
+  }
+
+  ~InPlaceMenu() {
+    InPlaceMenuDestroy();
+  }
+
+  HRESULT InPlaceMenuCreate(LPCWSTR menu_name) {
+    // We might already have an in-place menu set, because we set menus
+    // IOleDocumentView::UIActivate as well as in
+    // IOleInPlaceActiveObject::OnDocWindowActivate. If we have already
+    // done our work, just return silently
+    if (ole_menu_ || shared_menu_)
+      return S_OK;
+
+    ScopedComPtr<IOleInPlaceFrame> in_place_frame;
+    GetInPlaceFrame(in_place_frame.Receive());
+    // We have no IOleInPlaceFrame, no menu merging possible
+    if (!in_place_frame) {
+      NOTREACHED();
+      return E_FAIL;
+    }
+    // Create a blank menu and ask the container to add
+    // its menus into the OLEMENUGROUPWIDTHS structure
+    shared_menu_ = ::CreateMenu();
+    OLEMENUGROUPWIDTHS mgw = {0};
+    HRESULT hr = in_place_frame->InsertMenus(shared_menu_, &mgw);
+    if (FAILED(hr)) {
+      ::DestroyMenu(shared_menu_);
+      shared_menu_ = NULL;
+      return hr;
+    }
+    // Insert our menus
+    our_menu_ = LoadMenu(_AtlBaseModule.GetResourceInstance(),menu_name);
+    MergeMenus(shared_menu_, our_menu_, &mgw.width[0], 1);
+    // Send the menu to the client
+    ole_menu_ = (HMENU)OleCreateMenuDescriptor(shared_menu_, &mgw);
+    T* t = static_cast<T*>(this);
+    in_place_frame->SetMenu(shared_menu_, ole_menu_, t->m_hWnd);
+    return S_OK;
+  }
+
+  HRESULT InPlaceMenuDestroy() {
+    ScopedComPtr<IOleInPlaceFrame> in_place_frame;
+    GetInPlaceFrame(in_place_frame.Receive());
+    if (in_place_frame) {
+      in_place_frame->RemoveMenus(shared_menu_);
+      in_place_frame->SetMenu(NULL, NULL, NULL);
+    }
+    if (ole_menu_) {
+      OleDestroyMenuDescriptor(ole_menu_);
+      ole_menu_ = NULL;
+    }
+    if (shared_menu_) {
+      UnmergeMenus(shared_menu_, our_menu_);
+      DestroyMenu(shared_menu_);
+      shared_menu_ = NULL;
+    }
+    if (our_menu_) {
+      DestroyMenu(our_menu_);
+      our_menu_ = NULL;
+    }
+    return S_OK;
+  }
+
+  void MergeMenus(HMENU shared_menu, HMENU source_menu, LONG* menu_widths,
+                  unsigned int width_index) {
+    // Copy the popups from the source menu
+    // Insert at appropriate spot depending on width_index
+    DCHECK(width_index == 0 || width_index == 1);
+    int position = 0;
+    if (width_index == 1)
+      position = (int)menu_widths[0];
+    int group_width = 0;
+    int menu_items = GetMenuItemCount(source_menu);
+    for (int index = 0; index < menu_items; index++) {
+      // Get the HMENU of the popup
+      HMENU popup_menu = ::GetSubMenu(source_menu, index);
+      // Separators move us to next group
+      UINT state = GetMenuState(source_menu, index, MF_BYPOSITION);
+      if (!popup_menu && (state & MF_SEPARATOR)) {
+         // Servers should not touch past 5
+        DCHECK(width_index <= 5);
+        menu_widths[width_index] = group_width;
+        group_width = 0;
+        if (width_index < 5)
+          position += static_cast<int>(menu_widths[width_index+1]);
+        width_index += 2;
+      } else {
+        // Get the menu item text
+        TCHAR item_text[256] = {0};
+        int text_length = GetMenuString(source_menu, index, item_text,
+                                        ARRAYSIZE(item_text), MF_BYPOSITION);
+        // Popups are handled differently than normal menu items
+        if (popup_menu) {
+          if (::GetMenuItemCount(popup_menu) != 0) {
+            // Strip the HIBYTE because it contains a count of items
+            state = LOBYTE(state) | MF_POPUP;   // Must be popup
+            // Non-empty popup -- add it to the shared menu bar
+            InsertMenu(shared_menu, position, state|MF_BYPOSITION,
+                       reinterpret_cast<UINT_PTR>(popup_menu), item_text);
+            ++position;
+            ++group_width;
+          }
+        } else if (text_length > 0) {
+          // only non-empty items are added
+          DCHECK(item_text[0] != 0);
+          // here the state does not contain a count in the HIBYTE
+          InsertMenu(shared_menu, position, state|MF_BYPOSITION,
+                     GetMenuItemID(source_menu, index), item_text);
+          ++position;
+          ++group_width;
+        }
+      }
+    }
+  }
+
+  void UnmergeMenus(HMENU shared_menu, HMENU source_menu) {
+    int our_item_count = GetMenuItemCount(source_menu);
+    int shared_item_count = GetMenuItemCount(shared_menu);
+
+    for (int index = shared_item_count - 1; index >= 0; index--) {
+      // Check the popup menus
+      HMENU popup_menu = ::GetSubMenu(shared_menu, index);
+      if (popup_menu) {
+        // If it is one of ours, remove it from the shared menu
+        for (int sub_index = 0; sub_index < our_item_count; sub_index++) {
+          if (::GetSubMenu(source_menu, sub_index) == popup_menu) {
+            // Remove the menu from hMenuShared
+            RemoveMenu(shared_menu, index, MF_BYPOSITION);
+            break;
+          }
+        }
+      }
+    }
+  }
+
+ protected:
+  HRESULT GetInPlaceFrame(IOleInPlaceFrame** in_place_frame) {
+    if (!in_place_frame) {
+      NOTREACHED();
+      return E_POINTER;
+    }
+    T* t = static_cast<T*>(this);
+    HRESULT hr = E_FAIL;
+    if (!t->in_place_frame_) {
+      // We weren't given an IOleInPlaceFrame pointer, so
+      // we'll have to get it ourselves.
+      if (t->m_spInPlaceSite) {
+        t->frame_info_.cb = sizeof(OLEINPLACEFRAMEINFO);
+        ScopedComPtr<IOleInPlaceUIWindow> in_place_ui_window;
+        RECT position_rect = {0};
+        RECT clip_rect = {0};
+        hr = t->m_spInPlaceSite->GetWindowContext(in_place_frame,
+                                                  in_place_ui_window.Receive(),
+                                                  &position_rect, &clip_rect,
+                                                  &t->frame_info_);
+      }
+    } else {
+      *in_place_frame = t->in_place_frame_;
+      (*in_place_frame)->AddRef();
+      hr = S_OK;
+    }
+    return hr;
+  }
+
+ protected:
+  // The OLE menu descriptor created by the OleCreateMenuDescriptor
+  HMENU ole_menu_;
+  // The shared menu that we pass to IOleInPlaceFrame::SetMenu
+  HMENU shared_menu_;
+  // Our menu resource that we want to insert
+  HMENU our_menu_;
+};
+
+#endif  // CHROME_FRAME_IN_PLACE_MENU_H_
+
diff --git a/chrome_frame/installer_util/test.txt b/chrome_frame/installer_util/test.txt
new file mode 100644
index 0000000..30d74d2
--- /dev/null
+++ b/chrome_frame/installer_util/test.txt
@@ -0,0 +1 @@
+test
\ No newline at end of file
diff --git a/chrome_frame/np_browser_functions.cc b/chrome_frame/np_browser_functions.cc
new file mode 100644
index 0000000..9cc6f77
--- /dev/null
+++ b/chrome_frame/np_browser_functions.cc
@@ -0,0 +1,512 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/np_browser_functions.h"
+
+#include "base/logging.h"
+
+namespace npapi {
+
+// global function pointers (within this namespace) for the NPN functions.
+union NpVersion {
+  struct {
+    uint8 major;
+    uint8 minor;
+  } v;
+  uint16 version;
+};
+
+NpVersion g_version = {0};
+
+NPN_GetURLProcPtr g_geturl = NULL;
+NPN_PostURLProcPtr g_posturl = NULL;
+NPN_RequestReadProcPtr g_requestread = NULL;
+NPN_NewStreamProcPtr g_newstream = NULL;
+NPN_WriteProcPtr g_write = NULL;
+NPN_DestroyStreamProcPtr g_destroystream = NULL;
+NPN_StatusProcPtr g_status = NULL;
+NPN_UserAgentProcPtr g_useragent = NULL;
+NPN_MemAllocProcPtr g_memalloc = NULL;
+NPN_MemFreeProcPtr g_memfree = NULL;
+NPN_MemFlushProcPtr g_memflush = NULL;
+NPN_ReloadPluginsProcPtr g_reloadplugins = NULL;
+NPN_GetJavaEnvProcPtr g_getJavaEnv = NULL;
+NPN_GetJavaPeerProcPtr g_getJavaPeer = NULL;
+NPN_GetURLNotifyProcPtr g_geturlnotify = NULL;
+NPN_PostURLNotifyProcPtr g_posturlnotify = NULL;
+NPN_GetValueProcPtr g_getvalue = NULL;
+NPN_SetValueProcPtr g_setvalue = NULL;
+NPN_InvalidateRectProcPtr g_invalidaterect = NULL;
+NPN_InvalidateRegionProcPtr g_invalidateregion = NULL;
+NPN_ForceRedrawProcPtr g_forceredraw = NULL;
+NPN_GetStringIdentifierProcPtr g_getstringidentifier = NULL;
+NPN_GetStringIdentifiersProcPtr g_getstringidentifiers = NULL;
+NPN_GetIntIdentifierProcPtr g_getintidentifier = NULL;
+NPN_IdentifierIsStringProcPtr g_identifierisstring = NULL;
+NPN_UTF8FromIdentifierProcPtr g_utf8fromidentifier = NULL;
+NPN_IntFromIdentifierProcPtr g_intfromidentifier = NULL;
+NPN_CreateObjectProcPtr g_createobject = NULL;
+NPN_RetainObjectProcPtr g_retainobject = NULL;
+NPN_ReleaseObjectProcPtr g_releaseobject = NULL;
+NPN_InvokeProcPtr g_invoke = NULL;
+NPN_InvokeDefaultProcPtr g_invoke_default = NULL;
+NPN_EvaluateProcPtr g_evaluate = NULL;
+NPN_GetPropertyProcPtr g_getproperty = NULL;
+NPN_SetPropertyProcPtr g_setproperty = NULL;
+NPN_RemovePropertyProcPtr g_removeproperty = NULL;
+NPN_HasPropertyProcPtr g_hasproperty = NULL;
+NPN_HasMethodProcPtr g_hasmethod = NULL;
+NPN_ReleaseVariantValueProcPtr g_releasevariantvalue = NULL;
+NPN_SetExceptionProcPtr g_setexception = NULL;
+NPN_PushPopupsEnabledStateProcPtr g_pushpopupsenabledstate = NULL;
+NPN_PopPopupsEnabledStateProcPtr g_poppopupsenabledstate = NULL;
+NPN_EnumerateProcPtr g_enumerate = NULL;
+NPN_PluginThreadAsyncCallProcPtr g_pluginthreadasynccall = NULL;
+NPN_ConstructProcPtr g_construct = NULL;
+NPN_GetValueForURLProcPtr g_getvalueforurl = NULL;
+NPN_SetValueForURLProcPtr g_setvalueforurl = NULL;
+NPN_GetAuthenticationInfoProcPtr g_getauthenticationinfo = NULL;
+
+// Must be called prior to calling any of the browser functions below.
+void InitializeBrowserFunctions(NPNetscapeFuncs* functions) {
+  CHECK(functions);
+  DCHECK(g_geturl == NULL || g_geturl == functions->geturl);
+
+  g_version.version = functions->version;
+
+  g_geturl = functions->geturl;
+  g_posturl = functions->posturl;
+  g_requestread = functions->requestread;
+  g_newstream = functions->newstream;
+  g_write = functions->write;
+  g_destroystream = functions->destroystream;
+  g_status = functions->status;
+  g_useragent = functions->uagent;
+  g_memalloc = functions->memalloc;
+  g_memfree = functions->memfree;
+  g_memflush = functions->memflush;
+  g_reloadplugins = functions->reloadplugins;
+  g_getJavaEnv = functions->getJavaEnv;
+  g_getJavaPeer = functions->getJavaPeer;
+  g_geturlnotify = functions->geturlnotify;
+  g_posturlnotify = functions->posturlnotify;
+  g_getvalue = functions->getvalue;
+  g_setvalue = functions->setvalue;
+  g_invalidaterect = functions->invalidaterect;
+  g_invalidateregion = functions->invalidateregion;
+  g_forceredraw = functions->forceredraw;
+  g_getstringidentifier = functions->getstringidentifier;
+  g_getstringidentifiers = functions->getstringidentifiers;
+  g_getintidentifier = functions->getintidentifier;
+  g_identifierisstring = functions->identifierisstring;
+  g_utf8fromidentifier = functions->utf8fromidentifier;
+  g_intfromidentifier = functions->intfromidentifier;
+  g_createobject = functions->createobject;
+  g_retainobject = functions->retainobject;
+  g_releaseobject = functions->releaseobject;
+  g_invoke = functions->invoke;
+  g_invoke_default = functions->invokeDefault;
+  g_evaluate = functions->evaluate;
+  g_getproperty = functions->getproperty;
+  g_setproperty = functions->setproperty;
+  g_removeproperty = functions->removeproperty;
+  g_hasproperty = functions->hasproperty;
+  g_hasmethod = functions->hasmethod;
+  g_releasevariantvalue = functions->releasevariantvalue;
+  g_setexception = functions->setexception;
+  g_pushpopupsenabledstate = functions->pushpopupsenabledstate;
+  g_poppopupsenabledstate = functions->poppopupsenabledstate;
+  g_enumerate = functions->enumerate;
+  g_pluginthreadasynccall = functions->pluginthreadasynccall;
+  g_construct = functions->construct;
+
+  if (g_version.v.minor >= NPVERS_HAS_URL_AND_AUTH_INFO) {
+    g_getvalueforurl = functions->getvalueforurl;
+    g_setvalueforurl = functions->setvalueforurl;
+    g_getauthenticationinfo = functions->getauthenticationinfo;
+  }
+}
+
+void UninitializeBrowserFunctions() {
+  g_version.version = 0;
+
+  // We skip doing this in the official build as it doesn't serve much purpose
+// during shutdown.  The reason for it being here in the other types of builds
+// is to spot potential browser bugs whereby the browser leaves living objects
+// in our DLL after shutdown has been called.  In theory those objects could
+// trigger a call to the browser functions after shutdown has been called
+// and for non official builds we want that to simply crash.
+// For official builds we leave the function pointers around since they
+// continue to valid.
+  g_geturl = NULL;
+  g_posturl = NULL;
+  g_requestread = NULL;
+  g_newstream = NULL;
+  g_write = NULL;
+  g_destroystream = NULL;
+  g_status = NULL;
+  g_useragent = NULL;
+  g_memalloc = NULL;
+  g_memfree = NULL;
+  g_memflush = NULL;
+  g_reloadplugins = NULL;
+  g_getJavaEnv = NULL;
+  g_getJavaPeer = NULL;
+  g_geturlnotify = NULL;
+  g_posturlnotify = NULL;
+  g_getvalue = NULL;
+  g_setvalue = NULL;
+  g_invalidaterect = NULL;
+  g_invalidateregion = NULL;
+  g_forceredraw = NULL;
+  g_getstringidentifier = NULL;
+  g_getstringidentifiers = NULL;
+  g_getintidentifier = NULL;
+  g_identifierisstring = NULL;
+  g_utf8fromidentifier = NULL;
+  g_intfromidentifier = NULL;
+  g_createobject = NULL;
+  g_retainobject = NULL;
+  g_releaseobject = NULL;
+  g_invoke = NULL;
+  g_invoke_default = NULL;
+  g_evaluate = NULL;
+  g_getproperty = NULL;
+  g_setproperty = NULL;
+  g_removeproperty = NULL;
+  g_hasproperty = NULL;
+  g_hasmethod = NULL;
+  g_releasevariantvalue = NULL;
+  g_setexception = NULL;
+  g_pushpopupsenabledstate = NULL;
+  g_poppopupsenabledstate = NULL;
+  g_enumerate = NULL;
+  g_pluginthreadasynccall = NULL;
+  g_construct = NULL;
+  g_getvalueforurl = NULL;
+  g_setvalueforurl = NULL;
+  g_getauthenticationinfo = NULL;
+}
+
+bool IsInitialized() {
+  // We only check one function for convenience.
+  return g_getvalue != NULL;
+}
+
+// Function stubs for functions that the host browser implements.
+uint8 VersionMinor() {
+  return g_version.v.minor;
+}
+
+uint8 VersionMajor() {
+  return g_version.v.major;
+}
+
+NPError GetURL(NPP instance, const char* URL, const char* window) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_geturl(instance, URL, window);
+}
+
+NPError PostURL(NPP instance, const char* URL, const char* window, uint32 len,
+                const char* buf, NPBool file) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_posturl(instance, URL, window, len, buf, file);
+}
+
+NPError RequestRead(NPStream* stream, NPByteRange* rangeList) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_requestread(stream, rangeList);
+}
+
+NPError NewStream(NPP instance, NPMIMEType type, const char* window,
+                  NPStream** stream) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_newstream(instance, type, window, stream);
+}
+
+int32 Write(NPP instance, NPStream* stream, int32 len, void* buffer) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_write(instance, stream, len, buffer);
+}
+
+NPError DestroyStream(NPP instance, NPStream* stream, NPReason reason) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_destroystream(instance, stream, reason);
+}
+
+void Status(NPP instance, const char* message) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_status(instance, message);
+}
+
+const char* UserAgent(NPP instance) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_useragent(instance);
+}
+
+void* MemAlloc(uint32 size) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_memalloc(size);
+}
+
+void MemFree(void* ptr) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_memfree(ptr);
+}
+
+uint32 MemFlush(uint32 size) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_memflush(size);
+}
+
+void ReloadPlugins(NPBool reloadPages) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_reloadplugins(reloadPages);
+}
+
+void* GetJavaEnv() {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_getJavaEnv();
+}
+
+void* GetJavaPeer(NPP instance) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_getJavaPeer(instance);
+}
+
+NPError GetURLNotify(NPP instance, const char* URL, const char* window,
+                     void* notifyData) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_geturlnotify(instance, URL, window, notifyData);
+}
+
+NPError PostURLNotify(NPP instance, const char* URL, const char* window,
+                      uint32 len, const char* buf, NPBool file,
+                      void* notifyData) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_posturlnotify(instance, URL, window, len, buf, file, notifyData);
+}
+
+NPError GetValue(NPP instance, NPNVariable variable, void* ret_value) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_getvalue(instance, variable, ret_value);
+}
+
+NPError SetValue(NPP instance, NPPVariable variable, void* value) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_setvalue(instance, variable, value);
+}
+
+void InvalidateRect(NPP instance, NPRect* rect) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_invalidaterect(instance, rect);
+}
+
+void InvalidateRegion(NPP instance, NPRegion region) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_invalidateregion(instance, region);
+}
+
+void ForceRedraw(NPP instance) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_forceredraw(instance);
+}
+
+void ReleaseVariantValue(NPVariant* variant) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_releasevariantvalue(variant);
+}
+
+NPIdentifier GetStringIdentifier(const NPUTF8* name) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_getstringidentifier(name);
+}
+
+void GetStringIdentifiers(const NPUTF8** names, int32_t nameCount,
+                          NPIdentifier* identifiers) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_getstringidentifiers(names, nameCount, identifiers);
+}
+
+NPIdentifier GetIntIdentifier(int32_t intid) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_getintidentifier(intid);
+}
+
+int32_t IntFromIdentifier(NPIdentifier identifier) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_intfromidentifier(identifier);
+}
+
+bool IdentifierIsString(NPIdentifier identifier) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_identifierisstring(identifier);
+
+}
+
+NPUTF8* UTF8FromIdentifier(NPIdentifier identifier) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_utf8fromidentifier(identifier);
+
+}
+
+NPObject* CreateObject(NPP instance, NPClass* aClass) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_createobject(instance, aClass);
+
+}
+
+NPObject* RetainObject(NPObject* obj) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_retainobject(obj);
+
+}
+
+void ReleaseObject(NPObject* obj) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_releaseobject(obj);
+
+}
+
+bool Invoke(NPP npp, NPObject* obj, NPIdentifier methodName,
+            const NPVariant* args, unsigned argCount, NPVariant* result) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_invoke(npp, obj, methodName, args, argCount, result);
+}
+
+bool InvokeDefault(NPP npp, NPObject* obj, const NPVariant* args,
+                   unsigned argCount, NPVariant* result) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_invoke_default(npp, obj, args, argCount, result);
+}
+
+bool Evaluate(NPP npp, NPObject* obj, NPString* script, NPVariant* result) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_evaluate(npp, obj, script, result);
+}
+
+bool GetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName,
+                 NPVariant* result) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_getproperty(npp, obj, propertyName, result);
+}
+
+bool SetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName,
+                 const NPVariant* value) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_setproperty(npp, obj, propertyName, value);
+}
+
+bool HasProperty(NPP npp, NPObject* npobj, NPIdentifier propertyName) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_hasproperty(npp, npobj, propertyName);
+}
+
+bool HasMethod(NPP npp, NPObject* npobj, NPIdentifier methodName) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_hasmethod(npp, npobj, methodName);
+}
+
+bool RemoveProperty(NPP npp, NPObject* obj, NPIdentifier propertyName) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_removeproperty(npp, obj, propertyName);
+}
+
+void SetException(NPObject* obj, const NPUTF8* message) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_setexception(obj, message);
+}
+
+void PushPopupsEnabledState(NPP npp, NPBool enabled) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_pushpopupsenabledstate(npp, enabled);
+}
+
+void PopPopupsEnabledState(NPP npp) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_poppopupsenabledstate(npp);
+}
+
+bool Enumerate(NPP npp, NPObject* obj, NPIdentifier** identifier,
+               uint32_t* count) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_enumerate(npp, obj, identifier, count);
+}
+
+void PluginThreadAsyncCall(NPP instance, void (*func)(void*), void* userData) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_pluginthreadasynccall(instance, func, userData);
+}
+
+bool Construct(NPP npp, NPObject* obj, const NPVariant* args, uint32_t argCount,
+               NPVariant* result) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  return g_construct(npp, obj, args, argCount, result);
+}
+
+NPError GetValueForURL(NPP instance, NPNURLVariable variable, const char* url,
+                       char** value, uint32* len) {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  DCHECK(npapi::VersionMinor() >= NPVERS_HAS_URL_AND_AUTH_INFO);
+  if (!g_getvalueforurl) {
+    NOTREACHED();
+    return NPERR_INCOMPATIBLE_VERSION_ERROR;
+  }
+
+  return g_getvalueforurl(instance, variable, url, value, len);
+}
+
+NPError SetValueForURL(NPP instance, NPNURLVariable variable, const char* url,
+                       const char* value, uint32 len)  {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  DCHECK(npapi::VersionMinor() >= NPVERS_HAS_URL_AND_AUTH_INFO);
+  if (g_setvalueforurl) {
+    NOTREACHED();
+    return NPERR_INCOMPATIBLE_VERSION_ERROR;
+  }
+
+  return g_setvalueforurl(instance, variable, url, value, len);
+}
+
+NPError GetAuthenticationInfo(NPP instance, const char* protocol,
+                              const char* host, int32 port, const char* scheme,
+                              const char *realm, char** username, uint32* ulen,
+                              char** password, uint32* plen)  {
+  DCHECK(IsInitialized()) << __FUNCTION__;
+  DCHECK(npapi::VersionMinor() >= NPVERS_HAS_URL_AND_AUTH_INFO);
+  if (g_getauthenticationinfo) {
+    NOTREACHED();
+    return NPERR_INCOMPATIBLE_VERSION_ERROR;
+  }
+
+  return g_getauthenticationinfo(instance, protocol, host, port, scheme,
+                                 realm, username, ulen, password, plen);
+}
+
+std::string StringFromIdentifier(NPIdentifier identifier) {
+  std::string ret;
+  NPUTF8* utf8 = UTF8FromIdentifier(identifier);
+  if (utf8) {
+    ret = utf8;
+    MemFree(utf8);
+  }
+  return ret;
+}
+
+}  // namespace npapi
+
+void AllocateStringVariant(const std::string& str, NPVariant* var) {
+  DCHECK(var);
+
+  int len = str.length();
+  NPUTF8* buffer = reinterpret_cast<NPUTF8*>(npapi::MemAlloc(len + 1));
+  if (buffer) {
+    buffer[len] = '\0';
+    memcpy(buffer, str.c_str(), len);
+    STRINGN_TO_NPVARIANT(buffer, len, *var);
+  } else {
+    NULL_TO_NPVARIANT(*var);
+  }
+}
+
diff --git a/chrome_frame/np_browser_functions.h b/chrome_frame/np_browser_functions.h
new file mode 100644
index 0000000..ec29345d
--- /dev/null
+++ b/chrome_frame/np_browser_functions.h
@@ -0,0 +1,246 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_NP_BROWSER_FUNCTIONS_H_
+#define CHROME_FRAME_NP_BROWSER_FUNCTIONS_H_
+
+#include "base/logging.h"
+#include "third_party/WebKit/WebCore/bridge/npapi.h"
+#include "third_party/WebKit/WebCore/plugins/npfunctions.h"
+
+namespace npapi {
+
+// Must be called prior to calling any of the browser functions below.
+void InitializeBrowserFunctions(NPNetscapeFuncs* functions);
+void UninitializeBrowserFunctions();
+
+// Returns true iff InitializeBrowserFunctions has been called successully.
+bool IsInitialized();
+
+// Function stubs for functions that the host browser implements.
+
+uint8 VersionMinor();
+uint8 VersionMajor();
+
+NPError GetURL(NPP instance, const char* URL, const char* window);
+
+NPError PostURL(NPP instance, const char* URL, const char* window, uint32 len,
+                const char* buf, NPBool file);
+
+NPError RequestRead(NPStream* stream, NPByteRange* rangeList);
+
+NPError NewStream(NPP instance, NPMIMEType type, const char* window,
+                  NPStream** stream);
+
+int32 Write(NPP instance, NPStream* stream, int32 len, void* buffer);
+
+NPError DestroyStream(NPP instance, NPStream* stream, NPReason reason);
+
+void Status(NPP instance, const char* message);
+
+const char* UserAgent(NPP instance);
+
+void* MemAlloc(uint32 size);
+
+void MemFree(void* ptr);
+
+uint32 MemFlush(uint32 size);
+
+void ReloadPlugins(NPBool reloadPages);
+
+void* GetJavaEnv();
+
+void* GetJavaPeer(NPP instance);
+
+NPError GetURLNotify(NPP instance, const char* URL, const char* window,
+                     void* notifyData);
+
+NPError PostURLNotify(NPP instance, const char* URL, const char* window,
+                      uint32 len, const char* buf, NPBool file,
+                      void* notifyData);
+
+NPError GetValue(NPP instance, NPNVariable variable, void* ret_value);
+
+NPError SetValue(NPP instance, NPPVariable variable, void* value);
+
+void InvalidateRect(NPP instance, NPRect* rect);
+
+void InvalidateRegion(NPP instance, NPRegion region);
+
+void ForceRedraw(NPP instance);
+
+void ReleaseVariantValue(NPVariant* variant);
+
+NPIdentifier GetStringIdentifier(const NPUTF8* name);
+
+void GetStringIdentifiers(const NPUTF8** names, int32_t nameCount,
+                          NPIdentifier* identifiers);
+
+NPIdentifier GetIntIdentifier(int32_t intid);
+
+int32_t IntFromIdentifier(NPIdentifier identifier);
+
+bool IdentifierIsString(NPIdentifier identifier);
+
+NPUTF8* UTF8FromIdentifier(NPIdentifier identifier);
+
+NPObject* CreateObject(NPP, NPClass* aClass);
+
+NPObject* RetainObject(NPObject* obj);
+
+void ReleaseObject(NPObject* obj);
+
+bool Invoke(NPP npp, NPObject* obj, NPIdentifier methodName,
+            const NPVariant* args, unsigned argCount, NPVariant* result);
+
+bool InvokeDefault(NPP npp, NPObject* obj, const NPVariant* args,
+                   unsigned argCount, NPVariant* result);
+
+bool Evaluate(NPP npp, NPObject* obj, NPString* script, NPVariant* result);
+
+bool GetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName,
+                 NPVariant* result);
+
+bool SetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName,
+                 const NPVariant* value);
+
+bool HasProperty(NPP npp, NPObject* npobj, NPIdentifier propertyName);
+
+bool HasMethod(NPP npp, NPObject* npobj, NPIdentifier methodName);
+
+bool RemoveProperty(NPP npp, NPObject* obj, NPIdentifier propertyName);
+
+void SetException(NPObject* obj, const NPUTF8* message);
+
+void PushPopupsEnabledState(NPP npp, NPBool enabled);
+
+void PopPopupsEnabledState(NPP npp);
+
+bool Enumerate(NPP npp, NPObject* obj, NPIdentifier** identifier,
+               uint32_t* count);
+
+void PluginThreadAsyncCall(NPP instance, void (*func)(void*), void* userData);
+
+bool Construct(NPP npp, NPObject* obj, const NPVariant* args, uint32_t argCount,
+               NPVariant* result);
+
+NPError GetValueForURL(NPP instance, NPNURLVariable variable, const char* url,
+                       char** value, uint32* len);
+NPError SetValueForURL(NPP instance, NPNURLVariable variable, const char* url,
+                       const char* value, uint32 len);
+NPError GetAuthenticationInfo(NPP instance, const char* protocol,
+                              const char* host, int32 port, const char* scheme,
+                              const char *realm, char** username, uint32* ulen,
+                              char** password, uint32* plen);
+uint32 ScheduleTimer(NPP instance, uint32 interval, NPBool repeat,
+                     void (*timerFunc)(NPP npp, uint32 timerID));
+void UnscheduleTimer(NPP instance, uint32 timerID);
+NPError PopUpContextMenu(NPP instance, NPMenu* menu);
+NPBool ConvertPoint(NPP instance, double sourceX, double sourceY,
+                    NPCoordinateSpace sourceSpace, double *destX,
+                    double *destY, NPCoordinateSpace destSpace);
+
+// Helper routine that wraps UTF8FromIdentifier to convert a string identifier
+// to an STL string.  It's not super efficient since it could possibly do two
+// heap allocations (STL string has a stack based buffer for smaller strings).
+// For debugging purposes it is useful.
+std::string StringFromIdentifier(NPIdentifier identifier);
+
+}  // namespace npapi
+
+// Simple helper class for freeing NPVariants at the end of a scope.
+class ScopedNpVariant : public NPVariant {
+ public:
+  ScopedNpVariant() {
+    VOID_TO_NPVARIANT(*this);
+  }
+
+  ~ScopedNpVariant() {
+    Free();
+  }
+
+  void Free() {
+    npapi::ReleaseVariantValue(this);
+    VOID_TO_NPVARIANT(*this);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedNpVariant);
+};
+
+// Simple helper class for freeing NPObjects at the end of a scope.
+template <typename NpoType = NPObject>
+class ScopedNpObject {
+ public:
+  ScopedNpObject() : npo_(NULL) {
+  }
+
+  explicit ScopedNpObject(NpoType* npo) : npo_(npo) {
+  }
+
+  ~ScopedNpObject() {
+    Free();
+  }
+
+  NpoType* get() const {
+    return npo_;
+  }
+
+  operator NpoType*() const {
+    return npo_;
+  }
+
+  NpoType* operator->() const {
+    return npo_;
+  }
+
+  ScopedNpObject<NpoType>& operator=(NpoType* npo) {
+    if (npo != npo_) {
+      DCHECK(npo_ == NULL);
+      npapi::RetainObject(npo);
+      npo_ = npo;
+    }
+    return *this;
+  }
+
+  void Free() {
+    if (npo_) {
+      npapi::ReleaseObject(npo_);
+      npo_ = NULL;
+    }
+  }
+
+  NpoType** Receive() {
+    DCHECK(npo_ == NULL) << "Object leak. Pointer must be NULL";
+    return &npo_;
+  }
+
+  NpoType* Detach() {
+    NpoType* p = npo_;
+    npo_ = NULL;
+    return p;
+  }
+
+  void Attach(NpoType* p) {
+    DCHECK(npo_ == NULL);
+    npo_ = p;
+  }
+
+  NpoType* Copy() const {
+    if (npo_ != NULL)
+      npapi::RetainObject(npo_);
+    return npo_;
+  }
+
+ private:
+  NpoType* npo_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedNpObject);
+};
+
+// Allocates a new NPUTF8 string and assigns it to the variant.
+// If memory allocation fails, the variant type will be set to NULL.
+// The memory allocation is done via the npapi browser functions.
+void AllocateStringVariant(const std::string& str, NPVariant* var);
+
+#endif // CHROME_FRAME_NP_BROWSER_FUNCTIONS_H_
diff --git a/chrome_frame/np_event_listener.cc b/chrome_frame/np_event_listener.cc
new file mode 100644
index 0000000..ff5479a
--- /dev/null
+++ b/chrome_frame/np_event_listener.cc
@@ -0,0 +1,367 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/np_event_listener.h"
+
+#include "base/string_util.h"
+
+#include "third_party/xulrunner-sdk/win/include/string/nsEmbedString.h"
+#include "third_party/xulrunner-sdk/win/include/dom/nsIDOMElement.h"
+#include "third_party/xulrunner-sdk/win/include/dom/nsIDOMEventTarget.h"
+#include "third_party/xulrunner-sdk/win/include/dom/nsIDOMEvent.h"
+
+#include "chrome_frame/scoped_ns_ptr_win.h"
+#include "chrome_frame/ns_associate_iid_win.h"
+
+ASSOCIATE_IID(NS_IDOMELEMENT_IID_STR, nsIDOMElement);
+ASSOCIATE_IID(NS_IDOMNODE_IID_STR, nsIDOMNode);
+ASSOCIATE_IID(NS_IDOMEVENTTARGET_IID_STR, nsIDOMEventTarget);
+ASSOCIATE_IID(NS_IDOMEVENTLISTENER_IID_STR, nsIDOMEventListener);
+
+DomEventListener::DomEventListener(NpEventDelegate* delegate)
+    : NpEventListenerBase<DomEventListener>(delegate) {
+}
+
+DomEventListener::~DomEventListener() {
+}
+
+// We implement QueryInterface etc ourselves in order to avoid
+// extra dependencies brought on by the NS_IMPL_* macros.
+NS_IMETHODIMP DomEventListener::QueryInterface(REFNSIID iid, void** ptr) {
+  DCHECK(thread_id_ == ::GetCurrentThreadId());
+  nsresult res = NS_NOINTERFACE;
+
+  if (memcmp(&iid, &__uuidof(nsIDOMEventListener), sizeof(nsIID)) == 0 ||
+      memcmp(&iid, &__uuidof(nsISupports), sizeof(nsIID)) == 0) {
+    *ptr = static_cast<nsIDOMEventListener*>(this);
+    AddRef();
+    res = NS_OK;
+  }
+
+  return res;
+}
+
+NS_IMETHODIMP DomEventListener::HandleEvent(nsIDOMEvent *event) {
+  DCHECK(thread_id_ == ::GetCurrentThreadId());
+  DCHECK(event);
+
+  nsEmbedString tag;
+  event->GetType(tag);
+  delegate_->OnEvent(WideToUTF8(tag.get()).c_str());
+
+  return NS_OK;
+}
+
+bool DomEventListener::Subscribe(NPP instance,
+                                 const char* event_names[],
+                                 int event_name_count) {
+  DCHECK(event_names);
+  DCHECK(event_name_count > 0);
+
+  ScopedNsPtr<nsIDOMElement> element;
+  bool ret = GetObjectElement(instance, element.Receive());
+  if (ret) {
+    ScopedNsPtr<nsIDOMEventTarget> target;
+    target.QueryFrom(element);
+    if (target) {
+      for (int i = 0; i < event_name_count && ret; ++i) {
+        nsEmbedString name(ASCIIToWide(event_names[i]).c_str());
+        // See NPObjectEventListener::Subscribe (below) for a note on why
+        // we set the useCapture parameter to PR_FALSE.
+        nsresult res = target->AddEventListener(name, this, PR_FALSE);
+        DCHECK(res == NS_OK) << "AddEventListener: " << event_names[i];
+        ret = NS_SUCCEEDED(res);
+      }
+    } else {
+      DLOG(ERROR) << "failed to get nsIDOMEventTarget";
+      ret = false;
+    }
+  }
+
+  return ret;
+}
+
+bool DomEventListener::Unsubscribe(NPP instance,
+                                   const char* event_names[],
+                                   int event_name_count) {
+  DCHECK(event_names);
+  DCHECK(event_name_count > 0);
+
+  ScopedNsPtr<nsIDOMElement> element;
+  bool ret = GetObjectElement(instance, element.Receive());
+  if (ret) {
+    ScopedNsPtr<nsIDOMEventTarget> target;
+    target.QueryFrom(element);
+    if (target) {
+      for (int i = 0; i < event_name_count && ret; ++i) {
+        nsEmbedString name(ASCIIToWide(event_names[i]).c_str());
+        nsresult res = target->RemoveEventListener(name, this, PR_FALSE);
+        DCHECK(res == NS_OK) << "RemoveEventListener: " << event_names[i];
+        ret = NS_SUCCEEDED(res) && ret;
+      }
+    } else {
+      DLOG(ERROR) << "failed to get nsIDOMEventTarget";
+      ret = false;
+    }
+  }
+
+  return ret;
+}
+
+bool DomEventListener::GetObjectElement(NPP instance, nsIDOMElement** element) {
+  DCHECK(element);
+
+  ScopedNsPtr<nsIDOMElement> elem;
+  // Fetching the dom element works in Firefox, but is not implemented
+  // in webkit.
+  npapi::GetValue(instance, NPNVDOMElement, elem.Receive());
+  if (!elem.get()) {
+    DLOG(INFO) << "Failed to get NPNVDOMElement";
+    return false;
+  }
+
+  nsEmbedString tag;
+  nsresult res = elem->GetTagName(tag);
+  if (NS_SUCCEEDED(res)) {
+    if (LowerCaseEqualsASCII(tag.get(), tag.get() + tag.Length(), "embed")) {
+      ScopedNsPtr<nsIDOMNode> parent;
+      elem->GetParentNode(parent.Receive());
+      if (parent) {
+        elem.Release();
+        res = parent.QueryInterface(elem.Receive());
+        DCHECK(NS_SUCCEEDED(res));
+      }
+    }
+  } else {
+    NOTREACHED() << " GetTagName";
+  }
+
+  *element = elem.Detach();
+
+  return *element != NULL;
+}
+
+///////////////////////////////////
+// NPObjectEventListener
+
+NPObjectEventListener::NPObjectEventListener(NpEventDelegate* delegate)
+    : NpEventListenerBase<NPObjectEventListener>(delegate) {
+}
+
+NPObjectEventListener::~NPObjectEventListener() {
+  DLOG_IF(ERROR, npo_.get() == NULL);
+}
+
+NPObject* NPObjectEventListener::GetObjectElement(NPP instance) {
+  NPObject* object = NULL;
+  // We can't trust the return value from getvalue.
+  // In Opera, the return value can be false even though the correct
+  // object is returned.
+  npapi::GetValue(instance, NPNVPluginElementNPObject, &object);
+
+  if (object) {
+    NPIdentifier* ids = GetCachedStringIds();
+    NPVariant var;
+    if (npapi::GetProperty(instance, object, ids[TAG_NAME], &var)) {
+      DCHECK(NPVARIANT_IS_STRING(var));
+      const NPString& np_tag = NPVARIANT_TO_STRING(var);
+      std::string tag(np_tag.UTF8Characters, np_tag.UTF8Length);
+      npapi::ReleaseVariantValue(&var);
+
+      if (lstrcmpiA(tag.c_str(), "embed") == 0) {
+        // We've got the <embed> element but we really want
+        // the <object> element.
+        if (npapi::GetProperty(instance, object, ids[PARENT_ELEMENT], &var)) {
+          DCHECK(NPVARIANT_IS_OBJECT(var));
+          npapi::ReleaseObject(object);
+          object = NPVARIANT_TO_OBJECT(var);
+        }
+      } else {
+        DLOG(INFO) << __FUNCTION__ << " got " << tag;
+      }
+    } else {
+      DLOG(INFO) << __FUNCTION__ << " failed to get the element's tag";
+    }
+  } else {
+    DLOG(WARNING) << __FUNCTION__ << " failed to get NPNVPluginElementNPObject";
+  }
+
+  return object;
+}
+
+// Implementation of NpEventListener
+bool NPObjectEventListener::Subscribe(NPP instance,
+                                      const char* event_names[],
+                                      int event_name_count) {
+  DCHECK(event_names);
+  DCHECK(event_name_count > 0);
+  DCHECK(npo_.get() == NULL);
+
+  ScopedNpObject<> plugin_element(GetObjectElement(instance));
+  if (!plugin_element.get())
+    return false;
+
+  // This object seems to be getting leaked :-(
+  bool ret = false;
+  npo_.Attach(reinterpret_cast<Npo*>(
+      npapi::CreateObject(instance, PluginClass())));
+
+  if (!npo_.get()) {
+    NOTREACHED() << "createobject";
+  } else {
+    npo_->Initialize(this);
+    ret = true;
+
+    NPIdentifier* ids = GetCachedStringIds();
+
+    NPVariant args[3];
+    OBJECT_TO_NPVARIANT(npo_, args[1]);
+    // We don't want to set 'capture' (last parameter) to true.
+    // If we do, then in Opera, we'll simply not get callbacks unless
+    // the target <object> or <embed> element we're syncing with has its
+    // on[event] property assigned to some function handler. weird.
+    // Ideally though we'd like to set capture to true since we'd like to
+    // only be triggered for this particular object (and not for bubbling
+    // events, but alas it's not meant to be.
+    BOOLEAN_TO_NPVARIANT(false, args[2]);
+    for (int i = 0; i < event_name_count; ++i) {
+      ScopedNpVariant result;
+      STRINGZ_TO_NPVARIANT(event_names[i], args[0]);
+      ret = npapi::Invoke(instance, plugin_element, ids[ADD_EVENT_LISTENER],
+                          args, arraysize(args), &result) && ret;
+      if (!ret) {
+        DLOG(WARNING) << __FUNCTION__ << " invoke failed for "
+                      << event_names[i];
+        break;
+      }
+    }
+  }
+
+  return ret;
+}
+
+bool NPObjectEventListener::Unsubscribe(NPP instance,
+                                        const char* event_names[],
+                                        int event_name_count) {
+  DCHECK(event_names);
+  DCHECK(event_name_count > 0);
+  DCHECK(npo_.get() != NULL);
+
+  ScopedNpObject<> plugin_element(GetObjectElement(instance));
+  if (!plugin_element.get())
+    return false;
+
+  NPIdentifier* ids = GetCachedStringIds();
+
+  NPVariant args[3];
+  OBJECT_TO_NPVARIANT(npo_, args[1]);
+  BOOLEAN_TO_NPVARIANT(false, args[2]);
+  for (int i = 0; i < event_name_count; ++i) {
+    // TODO(tommi): look into why chrome isn't releasing the reference
+    //  count here.  As it stands the reference count doesn't go down
+    //  and as a result, the NPO gets leaked.
+    ScopedNpVariant result;
+    STRINGZ_TO_NPVARIANT(event_names[i], args[0]);
+    bool ret = npapi::Invoke(instance, plugin_element,
+        ids[REMOVE_EVENT_LISTENER], args, arraysize(args), &result);
+    DLOG_IF(ERROR, !ret) << __FUNCTION__ << " invoke: " << ret;
+  }
+
+  npo_.Free();
+
+  return true;
+}
+
+void NPObjectEventListener::HandleEvent(Npo* npo, NPObject* event) {
+  DCHECK(npo);
+  DCHECK(event);
+
+  NPIdentifier* ids = GetCachedStringIds();
+  ScopedNpVariant result;
+  bool ret = npapi::GetProperty(npo->npp(), event, ids[TYPE], &result);
+  DCHECK(ret) << "getproperty(type)";
+  if (ret) {
+    DCHECK(NPVARIANT_IS_STRING(result));
+    // Opera doesn't zero terminate its utf8 strings.
+    const NPString& type = NPVARIANT_TO_STRING(result);
+    std::string zero_terminated(type.UTF8Characters, type.UTF8Length);
+    DLOG(INFO) << "handleEvent: " << zero_terminated;
+    delegate_->OnEvent(zero_terminated.c_str());
+  }
+}
+
+NPClass* NPObjectEventListener::PluginClass() {
+  static NPClass _np_class = {
+    NP_CLASS_STRUCT_VERSION,
+    reinterpret_cast<NPAllocateFunctionPtr>(AllocateObject),
+    reinterpret_cast<NPDeallocateFunctionPtr>(DeallocateObject),
+    NULL,  // invalidate
+    reinterpret_cast<NPHasMethodFunctionPtr>(HasMethod),
+    reinterpret_cast<NPInvokeFunctionPtr>(Invoke),
+    NULL,  // InvokeDefault,
+    NULL,  // HasProperty,
+    NULL,  // GetProperty,
+    NULL,  // SetProperty,
+    NULL   // construct
+  };
+
+  return &_np_class;
+}
+
+bool NPObjectEventListener::HasMethod(NPObjectEventListener::Npo* npo,
+                                      NPIdentifier name) {
+  NPIdentifier* ids = GetCachedStringIds();
+  if (name == ids[HANDLE_EVENT])
+    return true;
+
+  return false;
+}
+
+bool NPObjectEventListener::Invoke(NPObjectEventListener::Npo* npo,
+                                   NPIdentifier name, const NPVariant* args,
+                                   uint32_t arg_count, NPVariant* result) {
+  NPIdentifier* ids = GetCachedStringIds();
+  if (name != ids[HANDLE_EVENT])
+    return false;
+
+  if (arg_count != 1 || !NPVARIANT_IS_OBJECT(args[0])) {
+    NOTREACHED();
+  } else {
+    NPObject* ev = NPVARIANT_TO_OBJECT(args[0]);
+    npo->listener()->HandleEvent(npo, ev);
+  }
+
+  return true;
+}
+
+NPObject* NPObjectEventListener::AllocateObject(NPP instance,
+                                                NPClass* class_name) {
+  return new Npo(instance);
+}
+
+void NPObjectEventListener::DeallocateObject(NPObjectEventListener::Npo* npo) {
+  delete npo;
+}
+
+NPIdentifier* NPObjectEventListener::GetCachedStringIds() {
+  static NPIdentifier _identifiers[IDENTIFIER_COUNT] = {0};
+  if (!_identifiers[0]) {
+    const NPUTF8* identifier_names[] = {
+      "handleEvent",
+      "type",
+      "addEventListener",
+      "removeEventListener",
+      "tagName",
+      "parentElement",
+    };
+    COMPILE_ASSERT(arraysize(identifier_names) == arraysize(_identifiers),
+                   mismatched_array_size);
+    npapi::GetStringIdentifiers(identifier_names, IDENTIFIER_COUNT,
+                                _identifiers);
+    for (int i = 0; i < IDENTIFIER_COUNT; ++i) {
+      DCHECK(_identifiers[i] != 0);
+    }
+  }
+  return _identifiers;
+}
diff --git a/chrome_frame/np_event_listener.h b/chrome_frame/np_event_listener.h
new file mode 100644
index 0000000..18cb4eb
--- /dev/null
+++ b/chrome_frame/np_event_listener.h
@@ -0,0 +1,187 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_NP_EVENT_LISTENER_H_
+#define CHROME_FRAME_NP_EVENT_LISTENER_H_
+
+#include "base/logging.h"
+
+#include "chrome_frame/utils.h"
+#include "chrome_frame/np_browser_functions.h"
+
+// Avoid conflicts with basictypes and the gecko sdk.
+// (different definitions of uint32).
+#define NO_NSPR_10_SUPPORT
+#include "third_party/xulrunner-sdk/win/include/dom/nsIDOMEventListener.h"
+
+
+class nsIDOMElement;
+
+class NpEventDelegate {
+ public:
+  virtual void OnEvent(const char* event_name) = 0;
+};
+
+class NpEventListener {
+ public:
+  NS_IMETHOD_(nsrefcnt) AddRef() = 0;
+  NS_IMETHOD_(nsrefcnt) Release() = 0;
+  virtual bool Subscribe(NPP instance,
+                         const char* event_names[],
+                         int event_name_count) = 0;
+  virtual bool Unsubscribe(NPP instance,
+                           const char* event_names[],
+                           int event_name_count) = 0;
+};
+
+// A little helper class to implement simple ref counting
+// and assert on single threadedness.
+template <class T>
+class NpEventListenerBase : public NpEventListener {
+ public:
+  NpEventListenerBase(NpEventDelegate* delegate)
+      : ref_count_(0), delegate_(delegate) {
+    DCHECK(delegate_);
+    thread_id_ = ::GetCurrentThreadId();
+  }
+
+  ~NpEventListenerBase() {
+    DCHECK(thread_id_ == ::GetCurrentThreadId());
+  }
+
+  NS_IMETHOD_(nsrefcnt) AddRef() {
+    DCHECK(thread_id_ == ::GetCurrentThreadId());
+    ref_count_++;
+    return ref_count_;
+  }
+
+  NS_IMETHOD_(nsrefcnt) Release() {
+    DCHECK(thread_id_ == ::GetCurrentThreadId());
+    ref_count_--;
+
+    if (!ref_count_) {
+      T* me = static_cast<T*>(this);
+      delete me;
+      return 0;
+    }
+
+    return ref_count_;
+  }
+
+ protected:
+  nsrefcnt ref_count_;
+  NpEventDelegate* delegate_;
+  AddRefModule module_ref_;
+  // used to DCHECK on expected single-threaded usage
+  unsigned long thread_id_;
+};
+
+// Implements nsIDOMEventListener in order to receive events from DOM
+// elements inside an HTML page.
+class DomEventListener
+  : public nsIDOMEventListener,
+  public NpEventListenerBase<DomEventListener> {
+ public:
+  DomEventListener(NpEventDelegate* delegate);
+  ~DomEventListener();
+
+  // Implementation of NpEventListener
+  virtual bool Subscribe(NPP instance,
+                         const char* event_names[],
+                         int event_name_count);
+  virtual bool Unsubscribe(NPP instance,
+                           const char* event_names[],
+                           int event_name_count);
+ protected:
+  // We implement QueryInterface etc ourselves in order to avoid
+  // extra dependencies brought on by the NS_IMPL_* macros.
+  NS_IMETHOD QueryInterface(REFNSIID iid, void** ptr);
+  NS_IMETHOD_(nsrefcnt) AddRef() {
+    return NpEventListenerBase<DomEventListener>::AddRef();
+  }
+
+  NS_IMETHOD_(nsrefcnt) Release() {
+    return NpEventListenerBase<DomEventListener>::Release();
+  }
+
+  // Implementation of nsIDOMEventListener
+  NS_IMETHOD HandleEvent(nsIDOMEvent *event);
+
+ private:
+  static bool GetObjectElement(NPP instance, nsIDOMElement** element);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DomEventListener);
+};
+
+class NPObjectEventListener
+  : public NpEventListenerBase<NPObjectEventListener> {
+ public:
+  NPObjectEventListener(NpEventDelegate* delegate);
+  ~NPObjectEventListener();
+
+  // Implementation of NpEventListener
+  virtual bool Subscribe(NPP instance,
+                         const char* event_names[],
+                         int event_name_count);
+  virtual bool Unsubscribe(NPP instance,
+                           const char* event_names[],
+                           int event_name_count);
+
+ protected:
+  // NPObject structure which is exposed by NPObjectEventListener.
+  class Npo : public NPObject {
+   public:
+    Npo(NPP npp) : npp_(npp), listener_(NULL) {
+    }
+
+    void Initialize(NPObjectEventListener* listener) {
+      listener_ = listener;
+    }
+
+    inline NPObjectEventListener* listener() const {
+      return listener_;
+    }
+
+    inline NPP npp() const {
+      return npp_;
+    }
+
+   protected:
+    NPP npp_;
+    NPObjectEventListener* listener_;
+    AddRefModule module_ref_;
+  };
+
+  static NPClass* PluginClass();
+
+  static bool HasMethod(Npo* npo, NPIdentifier name);
+  static bool Invoke(Npo* npo, NPIdentifier name, const NPVariant* args,
+                     uint32_t arg_count, NPVariant* result);
+  static NPObject* AllocateObject(NPP instance, NPClass* class_name);
+  static void DeallocateObject(Npo* npo);
+
+  typedef enum {
+    HANDLE_EVENT,
+    TYPE,
+    ADD_EVENT_LISTENER,
+    REMOVE_EVENT_LISTENER,
+    TAG_NAME,
+    PARENT_ELEMENT,
+    IDENTIFIER_COUNT,
+  } CachedStringIdentifiers;
+
+  static NPIdentifier* GetCachedStringIds();
+
+  void HandleEvent(Npo* npo, NPObject* event);
+  static NPObject* GetObjectElement(NPP instance);
+
+ private:
+  // Our NPObject.
+  ScopedNpObject<Npo> npo_;
+
+  DISALLOW_COPY_AND_ASSIGN(NPObjectEventListener);
+};
+
+#endif  // CHROME_FRAME_NP_EVENT_LISTENER_H_
diff --git a/chrome_frame/np_proxy_service.cc b/chrome_frame/np_proxy_service.cc
new file mode 100644
index 0000000..cb26dac
--- /dev/null
+++ b/chrome_frame/np_proxy_service.cc
@@ -0,0 +1,306 @@
+// Copyright (c) 2009 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.
+
+#include "base/string_util.h"
+#include "chrome/common/automation_constants.h"
+#include "chrome/common/json_value_serializer.h"
+#include "chrome_frame/np_proxy_service.h"
+#include "chrome_frame/np_browser_functions.h"
+
+#include "net/proxy/proxy_config.h"
+
+#include "third_party/xulrunner-sdk/win/include/xpcom/nsXPCOM.h"
+#include "third_party/xulrunner-sdk/win/include/xpcom/nsIObserverService.h"
+#include "third_party/xulrunner-sdk/win/include/xpcom/nsISupportsUtils.h"
+#include "third_party/xulrunner-sdk/win/include/xpcom/nsStringAPI.h"
+
+ASSOCIATE_IID(NS_IOBSERVERSERVICE_IID_STR, nsIObserverService);
+ASSOCIATE_IID(NS_IPREFBRANCH_IID_STR, nsIPrefBranch);
+
+// Firefox preference names.
+const char* kProxyObserverRoot = "network.";
+const char* kProxyObserverBranch = "proxy.";
+const char* kProxyType = "proxy.type";
+const char* kProxyAutoconfigUrl = "proxy.autoconfig_url";
+const char* kProxyBypassList = "proxy.no_proxies_on";
+
+const int kInvalidIntPref = -1;
+
+// These are the proxy schemes that Chrome knows about at the moment.
+// SOCKS is a notable ommission here, this will need to be updated when
+// Chrome supports SOCKS proxies.
+const NpProxyService::ProxyNames NpProxyService::kProxyInfo[] = {
+    {"http", "proxy.http", "proxy.http_port"},
+    {"https", "proxy.ssl", "proxy.ssl_port"},
+    {"ftp", "proxy.ftp", "proxy.ftp_port"} };
+
+NpProxyService::NpProxyService(void)
+    : type_(PROXY_CONFIG_LAST), auto_detect_(false), no_proxy_(false),
+      system_config_(false), automation_client_(NULL) {
+}
+
+NpProxyService::~NpProxyService(void) {
+}
+
+bool NpProxyService::Initialize(NPP instance,
+    ChromeFrameAutomationClient* automation_client) {
+  DCHECK(automation_client);
+  automation_client_ = automation_client;
+
+  // Get the pref service
+  bool result = false;
+  ScopedNsPtr<nsISupports> service_manager_base;
+  npapi::GetValue(instance, NPNVserviceManager, service_manager_base.Receive());
+  if (service_manager_base != NULL) {
+    service_manager_.QueryFrom(service_manager_base);
+    if (service_manager_.get() == NULL) {
+      DLOG(ERROR) << "Failed to create ServiceManager. This only works in FF.";
+    } else {
+      service_manager_->GetServiceByContractID(
+          NS_PREFSERVICE_CONTRACTID, NS_GET_IID(nsIPrefService),
+          reinterpret_cast<void**>(pref_service_.Receive()));
+      if (!pref_service_) {
+        DLOG(ERROR) << "Failed to create PreferencesService";
+      } else {
+        result = InitializePrefBranch(pref_service_);
+      }
+    }
+  }
+  return result;
+}
+
+bool NpProxyService::InitializePrefBranch(nsIPrefService* pref_service) {
+  DCHECK(pref_service);
+  // Note that we cannot persist a reference to the pref branch because we
+  // also act as an observer of changes to the branch. As per
+  // nsIPrefBranch2.h, this would result in a circular reference between us
+  // and the pref branch, which can impede cleanup. There are workarounds,
+  // but let's try just not caching the branch reference for now.
+  bool result = false;
+  ScopedNsPtr<nsIPrefBranch> pref_branch;
+
+  pref_service->ReadUserPrefs(nsnull);
+  pref_service->GetBranch(kProxyObserverRoot, pref_branch.Receive());
+
+  if (!pref_branch) {
+    DLOG(ERROR) << "Failed to get nsIPrefBranch";
+  } else {
+    if (!ReadProxySettings(pref_branch.get())) {
+      DLOG(ERROR) << "Could not read proxy settings.";
+    } else {
+      observer_pref_branch_.QueryFrom(pref_branch);
+      if (!observer_pref_branch_) {
+        DLOG(ERROR) << "Failed to get observer nsIPrefBranch2";
+      } else {
+        nsresult res = observer_pref_branch_->AddObserver(kProxyObserverBranch,
+                                                          this, PR_FALSE);
+        result = NS_SUCCEEDED(res);
+      }
+    }
+  }
+  return result;
+}
+
+bool NpProxyService::UnInitialize() {
+  // Fail early if this was never created - we may not be running on FF.
+  if (!pref_service_)
+    return false;
+
+  // Unhook ourselves as an observer.
+  nsresult res = NS_ERROR_FAILURE;
+  if (observer_pref_branch_)
+    res = observer_pref_branch_->RemoveObserver(kProxyObserverBranch, this);
+
+  return NS_SUCCEEDED(res);
+}
+
+NS_IMETHODIMP NpProxyService::Observe(nsISupports* subject, const char* topic,
+                                     const PRUnichar* data) {
+  if (!subject || !topic) {
+    NOTREACHED();
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  std::string topic_str(topic);
+  nsresult res = NS_OK;
+  if (topic_str == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
+    // Looks like our proxy settings changed. We need to reload!
+    // I have observed some extremely strange behaviour here. Specifically,
+    // we are supposed to be able to QI |subject| and get from it an
+    // nsIPrefBranch from which we can query new values. This has erratic
+    // behaviour, specifically subject starts returning null on all member
+    // queries. So I am using the cached nsIPrefBranch2 (that we used to add
+    // the observer) to do the querying.
+    if (NS_SUCCEEDED(res)) {
+      if (!ReadProxySettings(observer_pref_branch_)) {
+        res = NS_ERROR_UNEXPECTED;
+      } else {
+        std::string proxy_settings;
+        if (GetProxyValueJSONString(&proxy_settings))
+          automation_client_->SetProxySettings(proxy_settings);
+      }
+    }
+  } else {
+    NOTREACHED();
+  }
+
+  return res;
+}
+
+std::string NpProxyService::GetStringPref(nsIPrefBranch* pref_branch,
+                                         const char* pref_name) {
+  nsCString pref_string;
+  std::string result;
+  nsresult rv = pref_branch->GetCharPref(pref_name, getter_Copies(pref_string));
+  if (SUCCEEDED(rv) && pref_string.get()) {
+    result = pref_string.get();
+  }
+  return result;
+}
+
+int NpProxyService::GetIntPref(nsIPrefBranch* pref_branch,
+                              const char* pref_name) {
+  PRInt32 pref_int;
+  int result = kInvalidIntPref;
+  nsresult rv = pref_branch->GetIntPref(pref_name, &pref_int);
+  if (SUCCEEDED(rv)) {
+    result = pref_int;
+  }
+  return result;
+}
+
+bool NpProxyService::GetBoolPref(nsIPrefBranch* pref_branch,
+                                const char* pref_name) {
+  PRBool pref_bool;
+  bool result = false;
+  nsresult rv = pref_branch->GetBoolPref(pref_name, &pref_bool);
+  if (SUCCEEDED(rv)) {
+    result = pref_bool == PR_TRUE;
+  }
+  return result;
+}
+
+void NpProxyService::Reset() {
+  type_ = PROXY_CONFIG_LAST;
+  auto_detect_ = false;
+  no_proxy_ = false;
+  system_config_ = false;
+  manual_proxies_.clear();
+  pac_url_.clear();
+  proxy_bypass_list_.clear();
+}
+
+bool NpProxyService::ReadProxySettings(nsIPrefBranch* pref_branch) {
+  DCHECK(pref_branch);
+
+  // Clear our current settings.
+  Reset();
+  type_ = GetIntPref(pref_branch, kProxyType);
+  if (type_ == kInvalidIntPref) {
+    NOTREACHED();
+    return false;
+  }
+
+  switch (type_) {
+    case PROXY_CONFIG_DIRECT:
+    case PROXY_CONFIG_DIRECT4X:
+      no_proxy_ = true;
+      break;
+    case PROXY_CONFIG_SYSTEM:
+      // _SYSTEM is documented as "Use system settings if available, otherwise
+      // DIRECT". It isn't clear under what circumstances system settings would
+      // be unavailable, but I'll special-case this nonetheless and have
+      // GetProxyValueJSONString() return empty if we get this proxy type.
+      DLOG(WARNING) << "Received PROXY_CONFIG_SYSTEM proxy type.";
+      system_config_ = true;
+      break;
+    case PROXY_CONFIG_WPAD:
+      auto_detect_ = true;
+      break;
+    case PROXY_CONFIG_PAC:
+      pac_url_ = GetStringPref(pref_branch, kProxyAutoconfigUrl);
+      break;
+    case PROXY_CONFIG_MANUAL:
+      // Read in the values for each of the known schemes.
+      for (int i = 0; i < arraysize(kProxyInfo); i++) {
+        ManualProxyEntry entry;
+        entry.url = GetStringPref(pref_branch, kProxyInfo[i].pref_name);
+        entry.port = GetIntPref(pref_branch, kProxyInfo[i].port_pref_name);
+        if (!entry.url.empty() && entry.port != kInvalidIntPref) {
+          entry.scheme = kProxyInfo[i].chrome_scheme;
+          manual_proxies_.push_back(entry);
+        }
+      }
+
+      // Also pick up the list of URLs we bypass proxies for.
+      proxy_bypass_list_ = GetStringPref(pref_branch, kProxyBypassList);
+      break;
+    default:
+      NOTREACHED();
+      return false;
+  }
+  return true;
+}
+
+DictionaryValue* NpProxyService::BuildProxyValueSet() {
+  scoped_ptr<DictionaryValue> proxy_settings_value(new DictionaryValue);
+
+  if (auto_detect_) {
+    proxy_settings_value->SetBoolean(automation::kJSONProxyAutoconfig,
+                                     auto_detect_);
+  }
+
+  if (no_proxy_) {
+    proxy_settings_value->SetBoolean(automation::kJSONProxyNoProxy, no_proxy_);
+  }
+
+  if (!pac_url_.empty()) {
+    proxy_settings_value->SetString(automation::kJSONProxyPacUrl, pac_url_);
+  }
+
+  if (!proxy_bypass_list_.empty()) {
+    proxy_settings_value->SetString(automation::kJSONProxyBypassList,
+                                    proxy_bypass_list_);
+  }
+
+  // Fill in the manual proxy settings. Build a string representation that
+  // corresponds to the format of the input parameter to
+  // ProxyConfig::ProxyRules::ParseFromString.
+  std::string manual_proxy_settings;
+  ManualProxyList::const_iterator iter(manual_proxies_.begin());
+  for (; iter != manual_proxies_.end(); iter++) {
+    DCHECK(!iter->scheme.empty());
+    DCHECK(!iter->url.empty());
+    DCHECK(iter->port != kInvalidIntPref);
+    manual_proxy_settings += iter->scheme;
+    manual_proxy_settings += "=";
+    manual_proxy_settings += iter->url;
+    manual_proxy_settings += ":";
+    manual_proxy_settings += IntToString(iter->port);
+    manual_proxy_settings += ";";
+  }
+
+  if (!manual_proxy_settings.empty()) {
+    proxy_settings_value->SetString(automation::kJSONProxyServer,
+                                    manual_proxy_settings);
+  }
+
+  return proxy_settings_value.release();
+}
+
+bool NpProxyService::GetProxyValueJSONString(std::string* output) {
+  DCHECK(output);
+  output->empty();
+
+  // If we detected a PROXY_CONFIG_SYSTEM config type or failed to obtain the
+  // pref service then return false here to make Chrome continue using its
+  // default proxy settings.
+  if (system_config_ || !pref_service_)
+    return false;
+
+  scoped_ptr<DictionaryValue> proxy_settings_value(BuildProxyValueSet());
+
+  JSONStringValueSerializer serializer(output);
+  return serializer.Serialize(*static_cast<Value*>(proxy_settings_value.get()));
+}
diff --git a/chrome_frame/np_proxy_service.h b/chrome_frame/np_proxy_service.h
new file mode 100644
index 0000000..c0a85c8
--- /dev/null
+++ b/chrome_frame/np_proxy_service.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_NP_PROXY_SERVICE_H_
+#define CHROME_FRAME_NP_PROXY_SERVICE_H_
+
+#include <string>
+#include <vector>
+#include "base/values.h"
+#include "base/scoped_ptr.h"
+
+// Avoid conflicts with basictypes and the gecko sdk.
+// (different definitions of uint32).
+#define NO_NSPR_10_SUPPORT
+
+#include "chrome_frame/chrome_frame_automation.h"
+#include "chrome_frame/ns_associate_iid_win.h"
+#include "chrome_frame/ns_isupports_impl.h"
+#include "chrome_frame/scoped_ns_ptr_win.h"
+#include "third_party/WebKit/WebCore/bridge/npapi.h"
+#include "third_party/xulrunner-sdk/win/include/xpcom/nsIObserver.h"
+#include "third_party/xulrunner-sdk/win/include/pref/nsIPrefBranch2.h"
+#include "third_party/xulrunner-sdk/win/include/pref/nsIPrefService.h"
+#include "third_party/xulrunner-sdk/win/include/xpcom/nsIServiceManager.h"
+
+ASSOCIATE_IID(NS_IOBSERVER_IID_STR, nsIObserver);
+ASSOCIATE_IID(NS_ISERVICEMANAGER_IID_STR, nsIServiceManager);
+ASSOCIATE_IID(NS_IPREFSERVICE_IID_STR, nsIPrefService);
+ASSOCIATE_IID(NS_IPREFBRANCH2_IID_STR, nsIPrefBranch2);
+
+class nsIServiceManager;
+class nsIPrefService;
+class nsIPrefBranch2;
+
+// This class reads in proxy settings from firefox.
+// TODO(robertshield): The change notification code is currently broken.
+// Fix it and implement calling back through to the automation proxy with
+// proxy updates.
+class NpProxyService : public NsISupportsImplBase<NpProxyService>,
+                       public nsIObserver {
+ public:
+  // These values correspond to the integer network.proxy.type preference.
+  enum ProxyConfigType {
+    PROXY_CONFIG_DIRECT,
+    PROXY_CONFIG_MANUAL,
+    PROXY_CONFIG_PAC,
+    PROXY_CONFIG_DIRECT4X,
+    PROXY_CONFIG_WPAD,
+    PROXY_CONFIG_SYSTEM,  // use system settings if available, otherwise DIRECT
+    PROXY_CONFIG_LAST
+  };
+
+  // nsISupports
+  NS_IMETHODIMP_(nsrefcnt) AddRef(void) {
+    return NsISupportsImplBase<NpProxyService>::AddRef();
+  }
+
+  NS_IMETHODIMP_(nsrefcnt) Release(void) {
+    return NsISupportsImplBase<NpProxyService>::Release();
+  }
+
+  NS_IMETHOD QueryInterface(REFNSIID iid, void** ptr) {
+    nsresult res =
+        NsISupportsImplBase<NpProxyService>::QueryInterface(iid, ptr);
+    if (NS_FAILED(res) &&
+        memcmp(&iid, &__uuidof(nsIObserver), sizeof(nsIID)) == 0) {
+      *ptr = static_cast<nsIObserver*>(this);
+      AddRef();
+      res = NS_OK;
+    }
+    return res;
+  }
+
+  // nsIObserver
+  NS_IMETHOD Observe(nsISupports* subject, const char* topic,
+                     const PRUnichar* data);
+
+  NpProxyService();
+  ~NpProxyService();
+
+  virtual bool Initialize(NPP instance,
+                          ChromeFrameAutomationClient* automation_client);
+  bool UnInitialize();
+
+  // Places the current Firefox settings as a JSON string suitable for posting
+  // over to Chromium into output. Returns true if the settings were correctly
+  // serialized into a JSON string, false otherwise.
+  // TODO(robertshield): I haven't yet nailed down how much of this should go
+  // here and how much should go in the AutomationProxy. Will do that in a
+  // near-future patch.
+  bool GetProxyValueJSONString(std::string* output);
+
+ private:
+  bool InitializePrefBranch(nsIPrefService* pref_service);
+  bool ReadProxySettings(nsIPrefBranch* pref_branch);
+
+  std::string GetStringPref(nsIPrefBranch* pref_branch, const char* pref_name);
+  int GetIntPref(nsIPrefBranch* pref_branch, const char* pref_name);
+  bool GetBoolPref(nsIPrefBranch* pref_branch, const char* pref_name);
+
+  void Reset();
+  DictionaryValue* BuildProxyValueSet();
+
+  ChromeFrameAutomationClient* automation_client_;
+
+  ScopedNsPtr<nsIServiceManager> service_manager_;
+  ScopedNsPtr<nsIPrefService> pref_service_;
+  ScopedNsPtr<nsIPrefBranch2> observer_pref_branch_;
+
+  struct ProxyNames {
+    // Proxy type (http, https, ftp, etc...).
+    const char* chrome_scheme;
+    // Firefox preference name of the URL for this proxy type.
+    const char* pref_name;
+    // Firefox preference name for the port for this proxy type.
+    const char* port_pref_name;
+  };
+  static const ProxyNames kProxyInfo[];
+
+  struct ManualProxyEntry {
+    std::string scheme;
+    std::string url;
+    int port;
+  };
+  typedef std::vector<ManualProxyEntry> ManualProxyList;
+
+  bool system_config_;
+  bool auto_detect_;
+  bool no_proxy_;
+  int type_;
+  std::string pac_url_;
+  std::string proxy_bypass_list_;
+  ManualProxyList manual_proxies_;
+};
+
+#endif  // CHROME_FRAME_NP_PROXY_SERVICE_H_
diff --git a/chrome_frame/npapi_url_request.cc b/chrome_frame/npapi_url_request.cc
new file mode 100644
index 0000000..33052a18
--- /dev/null
+++ b/chrome_frame/npapi_url_request.cc
@@ -0,0 +1,157 @@
+// Copyright 2009, Google Inc.
+// 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.
+
+#include "chrome_frame/npapi_url_request.h"
+
+#include "base/string_util.h"
+#include "chrome_frame/np_browser_functions.h"
+#include "net/base/net_errors.h"
+
+int NPAPIUrlRequest::instance_count_ = 0;
+
+NPAPIUrlRequest::NPAPIUrlRequest(NPP instance)
+    : ref_count_(0), instance_(instance), stream_(NULL),
+      pending_read_size_(0),
+      status_(URLRequestStatus::FAILED, net::ERR_FAILED),
+      thread_(PlatformThread::CurrentId()) {
+  DLOG(INFO) << "Created request. Count: " << ++instance_count_;
+}
+
+NPAPIUrlRequest::~NPAPIUrlRequest() {
+  DLOG(INFO) << "Deleted request. Count: " << --instance_count_;
+}
+
+// NPAPIUrlRequest member defines.
+bool NPAPIUrlRequest::Start() {
+  NPError result = NPERR_GENERIC_ERROR;
+  DLOG(INFO) << "Starting URL request: " << url();
+  if (LowerCaseEqualsASCII(method(), "get")) {
+    // TODO(joshia): if we have extra headers for HTTP GET, then implement
+    // it using XHR
+    result = npapi::GetURLNotify(instance_, url().c_str(), NULL, this);
+  } else if (LowerCaseEqualsASCII(method(), "post")) {
+    result = npapi::PostURLNotify(instance_, url().c_str(), NULL,
+        extra_headers().length(), extra_headers().c_str(), false, this);
+  } else {
+    NOTREACHED() << "PluginUrlRequest only supports 'GET'/'POST'";
+  }
+
+  if (NPERR_NO_ERROR == result) {
+    request_handler()->AddRequest(this);
+  } else {
+    int os_error = net::ERR_FAILED;
+    switch (result) {
+      case NPERR_INVALID_URL:
+        os_error = net::ERR_INVALID_URL;
+        break;
+      default:
+        break;
+    }
+
+    OnResponseEnd(URLRequestStatus(URLRequestStatus::FAILED, os_error));
+    return false;
+  }
+
+  return true;
+}
+
+void NPAPIUrlRequest::Stop() {
+  DLOG(INFO) << "Finished request: Url - " << url() << " result: "
+      << static_cast<int>(status_.status());
+  if (stream_) {
+    npapi::DestroyStream(instance_, stream_, NPRES_USER_BREAK);
+    stream_ = NULL;
+  }
+
+  request_handler()->RemoveRequest(this);
+  if (!status_.is_io_pending())
+    OnResponseEnd(status_);
+}
+
+bool NPAPIUrlRequest::Read(int bytes_to_read) {
+  pending_read_size_ = bytes_to_read;
+  return true;
+}
+
+bool NPAPIUrlRequest::OnStreamCreated(const char* mime_type, NPStream* stream) {
+  stream_ = stream;
+  status_.set_status(URLRequestStatus::IO_PENDING);
+  // TODO(iyengar)
+  // Add support for passing persistent cookies and information about any URL
+  // redirects to Chrome.
+  OnResponseStarted(mime_type, stream->headers, stream->end,
+      base::Time::FromTimeT(stream->lastmodified), std::string(),
+      std::string(), 0);
+  return true;
+}
+
+void NPAPIUrlRequest::OnStreamDestroyed(NPReason reason) {
+  URLRequestStatus::Status status = URLRequestStatus::FAILED;
+  switch (reason) {
+    case NPRES_DONE:
+      status_.set_status(URLRequestStatus::SUCCESS);
+      status_.set_os_error(0);
+      break;
+    case NPRES_USER_BREAK:
+      status_.set_status(URLRequestStatus::CANCELED);
+      status_.set_os_error(net::ERR_ABORTED);
+      break;
+    case NPRES_NETWORK_ERR:
+    default:
+      status_.set_status(URLRequestStatus::FAILED);
+      status_.set_os_error(net::ERR_CONNECTION_CLOSED);
+      break;
+  }
+}
+
+int NPAPIUrlRequest::OnWriteReady() {
+  return pending_read_size_;
+}
+
+int NPAPIUrlRequest::OnWrite(void* buffer, int len) {
+  pending_read_size_ = 0;
+  OnReadComplete(buffer, len);
+  return len;
+}
+
+STDMETHODIMP_(ULONG) NPAPIUrlRequest::AddRef() {
+  DCHECK_EQ(PlatformThread::CurrentId(), thread_);
+  ++ref_count_;
+  return ref_count_;
+}
+
+STDMETHODIMP_(ULONG) NPAPIUrlRequest::Release() {
+  DCHECK_EQ(PlatformThread::CurrentId(), thread_);
+  unsigned long ret = --ref_count_;
+  if (!ret)
+    delete this;
+
+  return ret;
+}
+
diff --git a/chrome_frame/npapi_url_request.h b/chrome_frame/npapi_url_request.h
new file mode 100644
index 0000000..9151fcf
--- /dev/null
+++ b/chrome_frame/npapi_url_request.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_NPAPI_URL_REQUEST_H_
+#define CHROME_FRAME_NPAPI_URL_REQUEST_H_
+
+#include "base/platform_thread.h"
+#include "chrome_frame/plugin_url_request.h"
+#include "third_party/WebKit/WebCore/bridge/npapi.h"
+
+class NPAPIUrlRequest : public PluginUrlRequest {
+ public:
+  explicit NPAPIUrlRequest(NPP instance);
+  ~NPAPIUrlRequest();
+
+  virtual bool Start();
+  virtual void Stop();
+  virtual bool Read(int bytes_to_read);
+
+  // Called from NPAPI
+  bool OnStreamCreated(const char* mime_type, NPStream* stream);
+  void OnStreamDestroyed(NPReason reason);
+  int OnWriteReady();
+  int OnWrite(void* buffer, int len);
+
+  // Thread unsafe implementation of ref counting, since
+  // this will be called on the plugin UI thread only.
+  virtual unsigned long API_CALL AddRef();
+  virtual unsigned long API_CALL Release();
+
+ private:
+  unsigned long ref_count_;
+  NPP instance_;
+  NPStream* stream_;
+  size_t pending_read_size_;
+  URLRequestStatus status_;
+
+  PlatformThreadId thread_;
+  static int instance_count_;
+  DISALLOW_COPY_AND_ASSIGN(NPAPIUrlRequest);
+};
+
+#endif  // CHROME_FRAME_NPAPI_URL_REQUEST_H_
+
diff --git a/chrome_frame/ns_associate_iid_win.h b/chrome_frame/ns_associate_iid_win.h
new file mode 100644
index 0000000..db6c234
--- /dev/null
+++ b/chrome_frame/ns_associate_iid_win.h
@@ -0,0 +1,15 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_NS_ASSOCIATE_IID_WIN_H_
+#define CHROME_FRAME_NS_ASSOCIATE_IID_WIN_H_
+
+#define ASSOCIATE_IID(iid, class_name) class __declspec(uuid(iid)) class_name
+
+// Associate IIDs with ns interfaces for convenience.
+// This makes ScopedNsPtr more user friendly and avoids the nsIXyz::GetIID()
+// calls which can't be used in templates (runtime vs compile time).
+ASSOCIATE_IID("00000000-0000-0000-c000-000000000046", nsISupports);
+
+#endif  // CHROME_FRAME_NS_ASSOCIATE_IID_WIN_H_
diff --git a/chrome_frame/ns_isupports_impl.h b/chrome_frame/ns_isupports_impl.h
new file mode 100644
index 0000000..4873163
--- /dev/null
+++ b/chrome_frame/ns_isupports_impl.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_NS_ISUPPORTS_IMPL_H_
+#define CHROME_FRAME_NS_ISUPPORTS_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/platform_thread.h"
+#include "chrome_frame/utils.h"
+#include "third_party/xulrunner-sdk/win/include/xpcom/nscore.h"
+#include "third_party/xulrunner-sdk/win/include/xpcom/nsid.h"
+#include "third_party/xulrunner-sdk/win/include/xpcom/nsISupportsBase.h"
+
+// A simple single-threaded implementation of methods needed to support
+// nsISupports. Must be inherited by classes that also inherit from nsISupports.
+template<class Derived>
+class NsISupportsImplBase {
+ public:
+  NsISupportsImplBase() : nsiimpl_ref_count_(0) {
+    nsiimpl_thread_id_ = PlatformThread::CurrentId();
+  }
+
+  virtual ~NsISupportsImplBase() {
+    DCHECK(nsiimpl_thread_id_ == PlatformThread::CurrentId());
+  }
+
+  NS_IMETHOD QueryInterface(REFNSIID iid, void** ptr) {
+    DCHECK(nsiimpl_thread_id_ == PlatformThread::CurrentId());
+    nsresult res = NS_NOINTERFACE;
+
+    if (memcmp(&iid, &__uuidof(nsISupports), sizeof(nsIID)) == 0) {
+      *ptr = static_cast<nsISupports*>(static_cast<typename Derived*>(this));
+      AddRef();
+      res = NS_OK;
+    }
+
+    return res;
+  }
+
+  NS_IMETHOD_(nsrefcnt) AddRef() {
+    DCHECK(nsiimpl_thread_id_ == PlatformThread::CurrentId());
+    nsiimpl_ref_count_++;
+    return nsiimpl_ref_count_;
+  }
+
+  NS_IMETHOD_(nsrefcnt) Release() {
+    DCHECK(nsiimpl_thread_id_ == PlatformThread::CurrentId());
+    nsiimpl_ref_count_--;
+
+    if (!nsiimpl_ref_count_) {
+      Derived* me = static_cast<Derived*>(this);
+      delete me;
+      return 0;
+    }
+
+    return nsiimpl_ref_count_;
+  }
+
+ protected:
+  nsrefcnt nsiimpl_ref_count_;
+  AddRefModule nsiimpl_module_ref_;
+  // used to DCHECK on expected single-threaded usage
+  uint64 nsiimpl_thread_id_;
+};
+
+#endif  // CHROME_FRAME_NS_ISUPPORTS_IMPL_H_
diff --git a/chrome_frame/ole_document_impl.h b/chrome_frame/ole_document_impl.h
new file mode 100644
index 0000000..4c346d8
--- /dev/null
+++ b/chrome_frame/ole_document_impl.h
@@ -0,0 +1,253 @@
+// Copyright (c) 2009 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.
+
+// TODO(slightlyoff): Add any required LICENSE block changes for MSFT code
+// inclusion.
+
+// ole_document_impl.h : IOleDocument implementation
+//
+// This file is a modified version of the OleDocument.h file, which is
+// part of the ActiveDoc MSDN sample. The modifications are largely
+// conversions to Google coding guidelines. Below if the original header
+// from the file.
+
+// This is a part of the Active Template Library.
+// Copyright (c) Microsoft Corporation.  All rights reserved.
+//
+// This source code is only intended as a supplement to the
+// Active Template Library Reference and related
+// electronic documentation provided with the library.
+// See these sources for detailed information regarding the
+// Active Template Library product.
+
+#ifndef CHROME_FRAME_OLE_DOCUMENT_IMPL_H_
+#define CHROME_FRAME_OLE_DOCUMENT_IMPL_H_
+
+// TODO(sanjeevr): Revisit this impl file and cleanup dependencies
+#include <atlbase.h>
+#include <docobj.h>
+
+#include "base/logging.h"
+
+//////////////////////////////////////////////////////////////////////////////
+// IOleDocumentImpl
+template <class T>
+class ATL_NO_VTABLE IOleDocumentImpl : public IOleDocument {
+ public:
+  STDMETHOD(CreateView)(IOleInPlaceSite* in_place_site,
+                        IStream* stream,
+                        DWORD reserved ,
+                        IOleDocumentView** new_view) {
+    DLOG(INFO) << __FUNCTION__;
+    if (new_view == NULL)
+      return E_POINTER;
+    T* t = static_cast<T*>(this);
+    // If we've already created a view then we can't create another as we
+    // currently only support the ability to create one view
+    if (t->m_spInPlaceSite) {
+      return E_FAIL;
+    }
+    IOleDocumentView* view;
+    t->GetUnknown()->QueryInterface(IID_IOleDocumentView,
+                                    reinterpret_cast<void**>(&view));
+    // If we support IOleDocument we should support IOleDocumentView
+    ATLENSURE(view != NULL);
+    // If they've given us a site then use it
+    if (in_place_site != NULL) {
+      view->SetInPlaceSite(in_place_site);
+    }
+    // If they have given us an IStream pointer then use it to
+    // initialize the view
+    if (stream != NULL) {
+      view->ApplyViewState(stream);
+    }
+    // Return the view
+    *new_view = view;
+    return S_OK;
+  }
+
+  STDMETHOD(GetDocMiscStatus)(DWORD* status) {
+    DLOG(INFO) << __FUNCTION__;
+    if (NULL == status) {
+      return E_POINTER;
+    }
+    *status = DOCMISC_NOFILESUPPORT;
+    return S_OK;
+  }
+
+  STDMETHOD(EnumViews)(IEnumOleDocumentViews** enum_views,
+                       IOleDocumentView** view) {
+    DLOG(INFO) << __FUNCTION__;
+    if (view == NULL)
+      return E_POINTER;
+    T* t = static_cast<T*>(this);
+    // We only support one view
+    return t->_InternalQueryInterface(IID_IOleDocumentView,
+                                      reinterpret_cast<void**>(view));
+  }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// IOleDocumentViewImpl
+
+template <class T>
+class ATL_NO_VTABLE IOleDocumentViewImpl : public IOleDocumentView {
+ public:
+  STDMETHOD(SetInPlaceSite)(IOleInPlaceSite* in_place_site) {
+    DLOG(INFO) << __FUNCTION__;
+    T* t = static_cast<T*>(this);
+    if (t->m_spInPlaceSite) {
+      // If we already have a site get rid of it
+      UIActivate(FALSE);
+      HRESULT hr = t->InPlaceDeactivate();
+      if (FAILED(hr)) {
+        return hr;
+      }
+      DCHECK(!t->m_bInPlaceActive);
+    }
+    if (in_place_site != NULL) {
+      t->m_spInPlaceSite.Release();
+      in_place_site->QueryInterface(
+          IID_IOleInPlaceSiteWindowless,
+          reinterpret_cast<void **>(&t->m_spInPlaceSite));
+      if (!t->m_spInPlaceSite) {
+        // TODO(sanjeevr): This is a super-hack because m_spInPlaceSite
+        // is an IOleInPlaceSiteWindowless pointer and we are setting
+        // an IOleInPlaceSite pointer into it. The problem is that ATL
+        // (CComControlBase) uses this in a schizophrenic manner based
+        // on the m_bWndLess flag. Ouch, ouch, ouch! Find a way to clean
+        // this up.
+        // Disclaimer: I did not invent this hack, it exists in the MSDN
+        // sample from where this code has been derived and it also exists
+        // in ATL itself (look at atlctl.h line 938).
+        t->m_spInPlaceSite =
+            reinterpret_cast<IOleInPlaceSiteWindowless*>(in_place_site);
+      }
+    }
+    return S_OK;
+  }
+
+  STDMETHOD(GetInPlaceSite)(IOleInPlaceSite** in_place_site) {
+    DLOG(INFO) << __FUNCTION__;
+    if (in_place_site == NULL) {
+      return E_POINTER;
+    }
+    T* t = static_cast<T*>(this);
+    return t->m_spInPlaceSite->QueryInterface(
+        IID_IOleInPlaceSite,
+        reinterpret_cast<LPVOID *>(in_place_site));
+  }
+
+  STDMETHOD(GetDocument)(IUnknown** document) {
+    DLOG(INFO) << __FUNCTION__;
+    if (document == NULL) {
+      return E_POINTER;
+    }
+    T* t = static_cast<T*>(this);
+    *document = t->GetUnknown();
+    (*document)->AddRef();
+    return S_OK;
+  }
+
+  STDMETHOD(SetRect)(LPRECT view_rect) {
+    static bool is_resizing = false;
+    if (is_resizing) {
+      return S_OK;
+    }
+    is_resizing = true;
+    DLOG(INFO) << __FUNCTION__ << " " << view_rect->left << "," <<
+        view_rect->top << "," << view_rect->right << "," << view_rect->bottom;
+    T* t = static_cast<T*>(this);
+    t->SetObjectRects(view_rect, view_rect);
+    is_resizing = false;
+    return S_OK;
+  }
+
+  STDMETHOD(GetRect)(LPRECT view_rect) {
+    DLOG(INFO) << __FUNCTION__;
+    if (view_rect == NULL) {
+      return E_POINTER;
+    }
+    T* t = static_cast<T*>(this);
+    *view_rect = t->m_rcPos;
+    return S_OK;
+  }
+
+  STDMETHOD(SetRectComplex)(LPRECT view_rect,
+                            LPRECT hscroll_rect,
+                            LPRECT vscroll_rect,
+                            LPRECT size_box_rect) {
+    DLOG(INFO) << __FUNCTION__ << " not implemented";
+    return E_NOTIMPL;
+  }
+
+  STDMETHOD(Show)(BOOL show) {
+    DLOG(INFO) << __FUNCTION__;
+    T* t = static_cast<T*>(this);
+    HRESULT hr = S_OK;
+    if (show) {
+      if (!t->m_bUIActive)
+        hr = t->ActiveXDocActivate(OLEIVERB_INPLACEACTIVATE);
+    } else {
+      hr = t->UIActivate(FALSE);
+      ::ShowWindow(t->m_hWnd, SW_HIDE);
+    }
+    return hr;
+  }
+
+  STDMETHOD(UIActivate)(BOOL ui_activate) {
+    DLOG(INFO) << __FUNCTION__;
+    T* t = static_cast<T*>(this);
+    HRESULT hr = S_OK;
+    if (ui_activate) {
+      // We must know the client site first
+      if (t->m_spInPlaceSite == NULL) {
+        return E_UNEXPECTED;
+      }
+      if (!t->m_bUIActive) {
+        hr = t->ActiveXDocActivate(OLEIVERB_UIACTIVATE);
+      }
+    } else {
+      t->InPlaceMenuDestroy();
+      // t->DestroyToolbar();
+      hr = t->UIDeactivate();
+    }
+    return hr;
+  }
+
+  STDMETHOD(Open)() {
+    DLOG(INFO) << __FUNCTION__ << " not implemented";
+    return E_NOTIMPL;
+  }
+
+  STDMETHOD(CloseView)(DWORD reserved) {
+    DLOG(INFO) << __FUNCTION__;
+    T* t = static_cast<T*>(this);
+    t->Show(FALSE);
+    t->SetInPlaceSite(NULL);
+    return S_OK;
+  }
+
+  STDMETHOD(SaveViewState)(LPSTREAM stream) {
+    DLOG(INFO) << __FUNCTION__ << " not implemented";
+    return E_NOTIMPL;
+  }
+
+  STDMETHOD(ApplyViewState)(LPSTREAM stream) {
+    DLOG(INFO) << __FUNCTION__ << " not implemented";
+    return E_NOTIMPL;
+  }
+
+  STDMETHOD(Clone)(IOleInPlaceSite* new_in_place_site,
+                   IOleDocumentView** new_view) {
+    DLOG(INFO) << __FUNCTION__ << " not implemented";
+    return E_NOTIMPL;
+  }
+
+  HRESULT ActiveXDocActivate(LONG verb) {
+    return E_NOTIMPL;
+  }
+};
+
+#endif  // CHROME_FRAME_OLE_DOCUMENT_IMPL_H_
diff --git a/chrome_frame/plugin_url_request.cc b/chrome_frame/plugin_url_request.cc
new file mode 100644
index 0000000..1d34b0c
--- /dev/null
+++ b/chrome_frame/plugin_url_request.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/plugin_url_request.h"
+
+#include "chrome/test/automation/automation_messages.h"
+#include "chrome_frame/np_browser_functions.h"
+
+PluginUrlRequest::PluginUrlRequest()
+    : request_handler_(NULL), tab_(0), remote_request_id_(0),
+      status_(URLRequestStatus::IO_PENDING),
+      frame_busting_enabled_(false) {
+}
+
+PluginUrlRequest::~PluginUrlRequest() {
+}
+
+bool PluginUrlRequest::Initialize(PluginRequestHandler* request_handler,
+    int tab,  int remote_request_id, const std::string& url,
+    const std::string& method, const std::string& referrer,
+    const std::string& extra_headers, net::UploadData* upload_data,
+    bool enable_frame_busting) {
+  request_handler_ = request_handler;
+  tab_ = tab;
+  remote_request_id_ = remote_request_id;
+  url_ = url;
+  method_ = method;
+  referrer_ = referrer;
+  extra_headers_ = extra_headers;
+  upload_data_ = upload_data;
+  frame_busting_enabled_ = enable_frame_busting;
+  return true;
+}
+
+void PluginUrlRequest::OnResponseStarted(const char* mime_type,
+    const char* headers, int size, base::Time last_modified,
+    const std::string& persistent_cookies,
+    const std::string& redirect_url, int redirect_status) {
+  const IPC::AutomationURLResponse response = {
+    mime_type,
+    headers ? headers : "",
+    size,
+    last_modified,
+    persistent_cookies,
+    redirect_url,
+    redirect_status
+  };
+  request_handler_->Send(new AutomationMsg_RequestStarted(0, tab_,
+      remote_request_id_, response));
+}
+
+void PluginUrlRequest::OnResponseEnd(const URLRequestStatus& status) {
+  DCHECK(!status.is_io_pending());
+  DCHECK(status.is_success() || status.os_error());
+  request_handler_->Send(new AutomationMsg_RequestEnd(0, tab_,
+      remote_request_id_, status));
+}
+
+void PluginUrlRequest::OnReadComplete(const void* buffer, int len) {
+  std::string data(reinterpret_cast<const char*>(buffer), len);
+  request_handler_->Send(new AutomationMsg_RequestData(0, tab_,
+      remote_request_id_, data));
+}
+
diff --git a/chrome_frame/plugin_url_request.h b/chrome_frame/plugin_url_request.h
new file mode 100644
index 0000000..fb28c775
--- /dev/null
+++ b/chrome_frame/plugin_url_request.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_PLUGIN_URL_REQUEST_H_
+#define CHROME_FRAME_PLUGIN_URL_REQUEST_H_
+
+#include <string>
+#include <vector>
+
+#include "base/time.h"
+#include "ipc/ipc_message.h"
+#include "net/base/upload_data.h"
+#include "net/url_request/url_request_status.h"
+#include "base/ref_counted.h"
+
+class PluginUrlRequest;
+
+// Interface for a class that keeps a collection of outstanding
+// reqeusts and offers an outgoing channel.
+class PluginRequestHandler : public IPC::Message::Sender {
+ public:
+  virtual bool AddRequest(PluginUrlRequest* request) = 0;
+  virtual void RemoveRequest(PluginUrlRequest* request) = 0;
+};
+
+// A reference counting solution that's compatible with COM IUnknown
+class UrlRequestReference {
+ public:
+  virtual unsigned long API_CALL AddRef() = 0;
+  virtual unsigned long API_CALL Release() = 0;
+};
+
+class PluginUrlRequest : public UrlRequestReference {
+ public:
+  PluginUrlRequest();
+  ~PluginUrlRequest();
+
+  bool Initialize(PluginRequestHandler* handler, int tab,
+                  int remote_request_id, const std::string& url,
+                  const std::string& method, const std::string& referrer,
+                  const std::string& extra_headers,
+                  net::UploadData* upload_data,
+                  bool intercept_frame_options);
+
+  // Called in response to automation IPCs
+  virtual bool Start() = 0;
+  virtual void Stop() = 0;
+  virtual bool Read(int bytes_to_read) = 0;
+
+  // Persistent cookies are read from the host browser and passed off to Chrome
+  // These cookies are sent when we receive a response for every URL request
+  // initiated by Chrome. Ideally we should only send cookies for the top level
+  // URL and any subframes. However we don't receive information from Chrome
+  // about the context for a URL, i.e. whether it is a subframe, etc. 
+  // Additionally cookies for a URL should be sent once for the page. This
+  // is not done now as it is difficult to track URLs, specifically if they
+  // are redirected, etc.
+  void OnResponseStarted(const char* mime_type, const char* headers, int size,
+      base::Time last_modified, const std::string& peristent_cookies,
+      const std::string& redirect_url, int redirect_status);
+
+  void OnReadComplete(const void* buffer, int len);
+  void OnResponseEnd(const URLRequestStatus& status);
+
+  PluginRequestHandler* request_handler() const {
+    return request_handler_;
+  }
+  int id() const {
+    return remote_request_id_;
+  }
+  const std::string& url() const {
+    return url_;
+  }
+  const std::string& method() const {
+    return method_;
+  }
+  const std::string& referrer() const {
+    return referrer_;
+  }
+  const std::string& extra_headers() const {
+    return extra_headers_;
+  }
+  scoped_refptr<net::UploadData> upload_data() {
+    return upload_data_;
+  }
+
+  bool is_done() const {
+    return (URLRequestStatus::IO_PENDING != status_);
+  }
+
+  void set_url(const std::string& url) {
+    url_ = url;
+  }
+
+ protected:
+  void SendData();
+  bool frame_busting_enabled_;
+
+ private:
+  PluginRequestHandler* request_handler_;
+  int tab_;
+  int remote_request_id_;
+  std::string url_;
+  std::string method_;
+  std::string referrer_;
+  std::string extra_headers_;
+  scoped_refptr<net::UploadData> upload_data_;
+  URLRequestStatus::Status status_;
+};
+
+
+#endif  // CHROME_FRAME_PLUGIN_URL_REQUEST_H_
+
diff --git a/chrome_frame/precompiled.cc b/chrome_frame/precompiled.cc
new file mode 100644
index 0000000..6b08ac82
--- /dev/null
+++ b/chrome_frame/precompiled.cc
@@ -0,0 +1,9 @@
+// Copyright (c) 2009 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.
+
+// precompiled.cc : source file that includes just the standard includes
+// precompiled.pch will be the pre-compiled header
+// precompiled.obj will contain the pre-compiled type information
+
+#include "precompiled.h"
diff --git a/chrome_frame/precompiled.h b/chrome_frame/precompiled.h
new file mode 100644
index 0000000..0a13651
--- /dev/null
+++ b/chrome_frame/precompiled.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2009 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.
+
+// precompiled.h : include file for standard system include files,
+// or project specific include files that are used frequently,
+// but are changed infrequently
+
+#ifndef CHROME_FRAME_PRECOMPILED_H_
+#define CHROME_FRAME_PRECOMPILED_H_
+
+#include "resource.h"
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <atlctl.h>
+
+#endif  // CHROME_FRAME_PRECOMPILED_H_
diff --git a/chrome_frame/protocol_sink_wrap.cc b/chrome_frame/protocol_sink_wrap.cc
new file mode 100644
index 0000000..a567e9f7
--- /dev/null
+++ b/chrome_frame/protocol_sink_wrap.cc
@@ -0,0 +1,615 @@
+// Copyright (c) 2009 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.
+
+#include <htiframe.h>
+
+#include "chrome_frame/protocol_sink_wrap.h"
+
+#include "base/scoped_bstr_win.h"
+#include "base/logging.h"
+#include "base/registry.h"
+#include "base/scoped_bstr_win.h"
+#include "base/singleton.h"
+#include "base/string_util.h"
+
+#include "chrome_frame/utils.h"
+#include "chrome_frame/vtable_patch_manager.h"
+
+// BINDSTATUS_SERVER_MIMETYPEAVAILABLE == 54. Introduced in IE 8, so 
+// not in everyone's headers yet. See:
+// http://msdn.microsoft.com/en-us/library/ms775133(VS.85,loband).aspx
+#ifndef BINDSTATUS_SERVER_MIMETYPEAVAILABLE
+#define BINDSTATUS_SERVER_MIMETYPEAVAILABLE 54
+#endif
+
+static const wchar_t* kChromeMimeType = L"application/chromepage";
+static const char kTextHtmlMimeType[] = "text/html";
+
+static const int kInternetProtocolStartIndex = 3;
+static const int kInternetProtocolReadIndex = 9;
+static const int kInternetProtocolStartExIndex = 13;
+
+BEGIN_VTABLE_PATCHES(IInternetProtocol)
+  VTABLE_PATCH_ENTRY(kInternetProtocolStartIndex, ProtocolSinkWrap::OnStart)
+  VTABLE_PATCH_ENTRY(kInternetProtocolReadIndex, ProtocolSinkWrap::OnRead)
+END_VTABLE_PATCHES()
+
+BEGIN_VTABLE_PATCHES(IInternetProtocolEx)
+  VTABLE_PATCH_ENTRY(kInternetProtocolStartIndex, ProtocolSinkWrap::OnStart)
+  VTABLE_PATCH_ENTRY(kInternetProtocolReadIndex, ProtocolSinkWrap::OnRead)
+  VTABLE_PATCH_ENTRY(kInternetProtocolStartExIndex, ProtocolSinkWrap::OnStartEx)
+END_VTABLE_PATCHES()
+
+//
+// ProtocolSinkWrap implementation
+//
+
+// Static map initialization
+ProtocolSinkWrap::ProtocolSinkMap ProtocolSinkWrap::sink_map_;
+CComAutoCriticalSection ProtocolSinkWrap::sink_map_lock_;
+
+ProtocolSinkWrap::ProtocolSinkWrap()
+    : protocol_(NULL), renderer_type_(UNDETERMINED),
+      buffer_size_(0), buffer_pos_(0), is_saved_result_(false),
+      result_code_(0), result_error_(0), report_data_recursiveness_(0) {
+  memset(buffer_, 0, arraysize(buffer_));
+}
+
+ProtocolSinkWrap::~ProtocolSinkWrap() {
+  CComCritSecLock<CComAutoCriticalSection> lock(sink_map_lock_);
+  DCHECK(sink_map_.end() != sink_map_.find(protocol_));
+  sink_map_.erase(protocol_);
+  protocol_ = NULL;
+  DLOG(INFO) << "ProtocolSinkWrap: active sinks: " << sink_map_.size();
+}
+
+bool ProtocolSinkWrap::PatchProtocolHandler(const wchar_t* dll,
+    const CLSID& handler_clsid) {
+  HMODULE module = ::GetModuleHandle(dll);
+  if (!module) {
+    NOTREACHED() << "urlmon is not yet loaded. Error: " << GetLastError();
+    return false;
+  }
+
+  typedef HRESULT (WINAPI* DllGetClassObject_Fn)(REFCLSID, REFIID, LPVOID*);
+  DllGetClassObject_Fn fn = reinterpret_cast<DllGetClassObject_Fn>(
+      ::GetProcAddress(module, "DllGetClassObject"));
+  if (!fn) {
+    NOTREACHED() << "DllGetClassObject not found in urlmon.dll";
+    return false;
+  }
+
+  ScopedComPtr<IClassFactory> protocol_class_factory;
+  HRESULT hr = fn(handler_clsid, IID_IClassFactory,
+      reinterpret_cast<LPVOID*>(protocol_class_factory.Receive()));
+  if (FAILED(hr)) {
+    NOTREACHED() << "DllGetclassObject failed. Error: " << hr;
+    return false;
+  }
+
+  ScopedComPtr<IInternetProtocol> handler_instance;
+  hr = protocol_class_factory->CreateInstance(NULL, IID_IInternetProtocol,
+      reinterpret_cast<void**>(handler_instance.Receive()));
+  if (FAILED(hr)) {
+    NOTREACHED() << "ClassFactory::CreateInstance failed for InternetProtocol."
+        << " Error: " << hr;
+    return false;
+  }
+
+  ScopedComPtr<IInternetProtocolEx> ipex;
+  ipex.QueryFrom(handler_instance);
+  if (ipex) {
+    vtable_patch::PatchInterfaceMethods(ipex, IInternetProtocolEx_PatchInfo);
+  } else {
+    vtable_patch::PatchInterfaceMethods(handler_instance,
+                                        IInternetProtocol_PatchInfo);
+  }
+
+  return true;
+}
+
+// IInternetProtocol/Ex method implementation.
+HRESULT ProtocolSinkWrap::OnStart(InternetProtocol_Start_Fn orig_start,
+    IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink,
+    IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
+  DCHECK(orig_start);
+  DLOG_IF(INFO, url != NULL) << "OnStart: " << url;
+
+  ScopedComPtr<IInternetProtocolSink> sink_to_use(MaybeWrapSink(protocol,
+      prot_sink, url));
+  return orig_start(protocol, url, sink_to_use, bind_info, flags, reserved);
+}
+
+HRESULT ProtocolSinkWrap::OnStartEx(InternetProtocol_StartEx_Fn orig_start_ex,
+    IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink,
+    IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
+  DCHECK(orig_start_ex);
+
+  ScopedBstr url;
+  uri->GetPropertyBSTR(Uri_PROPERTY_ABSOLUTE_URI, url.Receive(), 0);
+  DLOG_IF(INFO, url != NULL) << "OnStartEx: " << url;
+
+  ScopedComPtr<IInternetProtocolSink> sink_to_use(MaybeWrapSink(protocol,
+      prot_sink, url));
+  return orig_start_ex(protocol, uri, sink_to_use, bind_info, flags, reserved);
+}
+
+HRESULT ProtocolSinkWrap::OnRead(InternetProtocol_Read_Fn orig_read,
+    IInternetProtocol* protocol, void* buffer, ULONG size, ULONG* size_read) {
+  DCHECK(orig_read);
+
+  scoped_refptr<ProtocolSinkWrap> instance =
+      ProtocolSinkWrap::InstanceFromProtocol(protocol);
+  HRESULT hr;
+  if (instance) {
+    DCHECK(instance->protocol_ == protocol);
+    hr = instance->OnReadImpl(buffer, size, size_read, orig_read);
+  } else {
+    hr = orig_read(protocol, buffer, size, size_read);
+  }
+
+  return hr;
+}
+
+bool ProtocolSinkWrap::Initialize(IInternetProtocol* protocol,
+    IInternetProtocolSink* original_sink, const wchar_t* url) {
+  DCHECK(original_sink);
+  delegate_ = original_sink;
+  protocol_ = protocol;
+  if (url)
+    url_ = url;
+
+  CComCritSecLock<CComAutoCriticalSection> lock(sink_map_lock_);
+  DCHECK(sink_map_.end() == sink_map_.find(protocol));
+  sink_map_[protocol] = this;
+  DLOG(INFO) << "ProtocolSinkWrap: active sinks: " << sink_map_.size();
+  return true;
+}
+
+HRESULT WINAPI ProtocolSinkWrap::CheckOutgoingInterface(void* obj,
+    REFIID iid, LPVOID* ret, DWORD cookie) {
+  ProtocolSinkWrap* instance = reinterpret_cast<ProtocolSinkWrap*>(obj);
+  HRESULT hr = E_NOINTERFACE;
+  if (instance && instance->delegate_)
+    hr = instance->delegate_->QueryInterface(iid, ret);
+
+#ifndef NDEBUG
+  if (SUCCEEDED(hr)) {
+    wchar_t iid_string[64] = {0};
+    StringFromGUID2(iid, iid_string, arraysize(iid_string));
+    DLOG(INFO) << "Giving out wrapped interface: " << iid_string;
+  }
+#endif
+
+  return hr;
+}
+
+HRESULT WINAPI ProtocolSinkWrap::IfDelegateSupports(void* obj,
+    REFIID iid, LPVOID* ret, DWORD cookie) {
+  HRESULT hr = E_NOINTERFACE;
+  ProtocolSinkWrap* instance = reinterpret_cast<ProtocolSinkWrap*>(obj);
+  if (instance && instance->delegate_) {
+    ScopedComPtr<IUnknown> original;
+    hr = instance->delegate_->QueryInterface(iid,
+        reinterpret_cast<void**>(original.Receive()));
+    if (original) {
+      IUnknown* supported_interface = reinterpret_cast<IUnknown*>(
+          reinterpret_cast<DWORD_PTR>(obj) + cookie);
+      supported_interface->AddRef();
+      *ret = supported_interface;
+      hr = S_OK;
+    }
+  }
+
+  return hr;
+}
+
+// IInternetProtocolSink methods
+STDMETHODIMP ProtocolSinkWrap::Switch(PROTOCOLDATA* protocol_data) {
+  HRESULT hr = E_FAIL;
+  if (delegate_)
+    hr = delegate_->Switch(protocol_data);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::ReportProgress(ULONG status_code,
+    LPCWSTR status_text) {
+  DLOG(INFO) << "ProtocolSinkWrap::ReportProgress: Code:" << status_code <<
+    " Text: " << (status_text ? status_text : L"");
+  if ((BINDSTATUS_MIMETYPEAVAILABLE == status_code) ||
+      (BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE == status_code)) {
+    // If we have a MIMETYPE and that MIMETYPE is not "text/html". we don't
+    // want to do anything with this.
+    if (status_text) {
+      size_t status_text_length = lstrlenW(status_text);
+      const wchar_t* status_text_end = status_text + std::min(
+          status_text_length, arraysize(kTextHtmlMimeType) - 1);
+      if (!LowerCaseEqualsASCII(status_text, status_text_end,
+                                kTextHtmlMimeType)) {
+        renderer_type_ = OTHER;
+      }
+    }
+  }
+
+  HRESULT hr = E_FAIL;
+  if (delegate_)
+    hr = delegate_->ReportProgress(status_code, status_text);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::ReportData(DWORD flags, ULONG progress,
+    ULONG max_progress) {
+  DCHECK(protocol_);
+  DCHECK(delegate_);
+  DLOG(INFO) << "ProtocolSinkWrap::ReportData: flags: " << flags <<
+    " progress: " << progress << " progress_max: " << max_progress;
+
+  scoped_refptr<ProtocolSinkWrap> self_ref(this);
+
+  // Maintain a stack depth to make a determination. ReportData is called
+  // recursively in IE8. If the request can be served in a single Read, the
+  // situation ends up like this:
+  // orig_prot
+  // |--> ProtocolSinkWrap::ReportData (BSCF_FIRSTDATANOTIFICATION)
+  //       |--> orig_prot->Read(...) - 1st read - S_OK and data
+  //            |--> ProtocolSinkWrap::ReportData (BSCF_LASTDATANOTIFICATION)
+  //                 |--> orig_prot->Read(...) - 2nd read S_FALSE, 0 bytes
+  //
+  // Inner call returns S_FALSE and no data. We try to make a determination
+  // of render type then and incorrectly set it to 'OTHER' as we don't have
+  // any data yet. However, we can make a determination in the context of
+  // outer ReportData since the first read will return S_OK with data. Then
+  // the next Read in the loop will return S_FALSE and we will enter the
+  // determination logic.
+
+  // NOTE: We use the report_data_recursiveness_ variable to detect situations
+  //    in which calls to ReportData are re-entrant (such as when the entire 
+  //    contents of a page fit inside a single packet). In these cases, we 
+  //    don't care about re-entrant calls beyond the second, and so we compare 
+  //    report_data_recursiveness_ inside the while loop, making sure we skip 
+  //    what would otherwise be spurious calls to ReportProgress().
+  report_data_recursiveness_++;
+
+  HRESULT hr = S_OK;
+  if (is_undetermined()) {
+    HRESULT hr_read = S_OK;
+    while (hr_read == S_OK) {
+      ULONG size_read = 0;
+      hr_read = protocol_->Read(buffer_ + buffer_size_,
+          kMaxContentSniffLength - buffer_size_, &size_read);
+      buffer_size_ += size_read;
+
+      // Attempt to determine the renderer type if we have received
+      // sufficient data. Do not attempt this when we are called recursively.
+      if (report_data_recursiveness_ < 2 && (S_FALSE == hr_read) ||
+         (buffer_size_ >= kMaxContentSniffLength)) {
+        DetermineRendererType();
+        if (renderer_type() == CHROME) {
+          // Workaround for IE 8 and "nosniff". See:
+          // http://blogs.msdn.com/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx
+          delegate_->ReportProgress(
+              BINDSTATUS_SERVER_MIMETYPEAVAILABLE, kChromeMimeType);
+          // For IE < 8.
+          delegate_->ReportProgress(
+              BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE, kChromeMimeType);
+
+          delegate_->ReportData(
+              BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE, 0, 0);
+        }
+        break;
+      }
+    }
+  }
+
+  // we call original only if the renderer type is other
+  if (renderer_type() == OTHER) {
+    hr = delegate_->ReportData(flags, progress, max_progress);
+
+    if (is_saved_result_) {
+      is_saved_result_ = false;
+      delegate_->ReportResult(result_code_, result_error_,
+          result_text_.c_str());
+    }
+  }
+
+  report_data_recursiveness_--;
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::ReportResult(HRESULT result, DWORD error,
+    LPCWSTR result_text) {
+  DLOG(INFO) << "ProtocolSinkWrap::ReportResult: result: " << result <<
+    " error: " << error << " Text: " << (result_text ? result_text : L"");
+
+  // If this request failed, we don't want to have anything to do with this.
+  if (FAILED(result))
+    renderer_type_ = OTHER;
+
+  // if we are still not sure about the renderer type, cache the result,
+  // othewise urlmon will get confused about getting reported about a
+  // success result for which it never received any data.
+  if (is_undetermined()) {
+    is_saved_result_ = true;
+    result_code_ = result;
+    result_error_ = error;
+    if (result_text)
+      result_text_ = result_text;
+    return S_OK;
+  }
+
+  HRESULT hr = E_FAIL;
+  if (delegate_)
+    hr = delegate_->ReportResult(result, error, result_text);
+
+  return hr;
+}
+
+// IInternetBindInfoEx
+STDMETHODIMP ProtocolSinkWrap::GetBindInfo(
+    DWORD* flags, BINDINFO* bind_info_ret) {
+  ScopedComPtr<IInternetBindInfo> bind_info;
+  HRESULT hr = bind_info.QueryFrom(delegate_);
+  if (bind_info)
+    hr = bind_info->GetBindInfo(flags, bind_info_ret);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::GetBindString(ULONG string_type,
+    LPOLESTR* string_array, ULONG array_size, ULONG* size_returned) {
+  ScopedComPtr<IInternetBindInfo> bind_info;
+  HRESULT hr = bind_info.QueryFrom(delegate_);
+  if (bind_info)
+    hr = bind_info->GetBindString(string_type, string_array,
+        array_size, size_returned);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::GetBindInfoEx(DWORD *flags, BINDINFO* bind_info,
+    DWORD* bindf2, DWORD *reserved) {
+  ScopedComPtr<IInternetBindInfoEx> bind_info_ex;
+  HRESULT hr = bind_info_ex.QueryFrom(delegate_);
+  if (bind_info_ex)
+    hr = bind_info_ex->GetBindInfoEx(flags, bind_info, bindf2, reserved);
+  return hr;
+}
+
+// IServiceProvider
+STDMETHODIMP ProtocolSinkWrap::QueryService(REFGUID service_guid,
+    REFIID riid, void** service) {
+  ScopedComPtr<IServiceProvider> service_provider;
+  HRESULT hr = service_provider.QueryFrom(delegate_);
+  if (service_provider)
+    hr = service_provider->QueryService(service_guid, riid, service);
+  return hr;
+}
+
+// IAuthenticate
+STDMETHODIMP ProtocolSinkWrap::Authenticate(HWND* window,
+    LPWSTR* user_name, LPWSTR* password) {
+  ScopedComPtr<IAuthenticate> authenticate;
+  HRESULT hr = authenticate.QueryFrom(delegate_);
+  if (authenticate)
+    hr = authenticate->Authenticate(window, user_name, password);
+  return hr;
+}
+
+// IInternetProtocolEx
+STDMETHODIMP ProtocolSinkWrap::Start(LPCWSTR url,
+    IInternetProtocolSink *protocol_sink, IInternetBindInfo* bind_info,
+    DWORD flags, HANDLE_PTR reserved) {
+  ScopedComPtr<IInternetProtocolRoot> protocol;
+  HRESULT hr = protocol.QueryFrom(delegate_);
+  if (protocol)
+    hr = protocol->Start(url, protocol_sink, bind_info, flags, reserved);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::Continue(PROTOCOLDATA* protocol_data) {
+  ScopedComPtr<IInternetProtocolRoot> protocol;
+  HRESULT hr = protocol.QueryFrom(delegate_);
+  if (protocol)
+    hr = protocol->Continue(protocol_data);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::Abort(HRESULT reason, DWORD options) {
+  ScopedComPtr<IInternetProtocolRoot> protocol;
+  HRESULT hr = protocol.QueryFrom(delegate_);
+  if (protocol)
+    hr = protocol->Abort(reason, options);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::Terminate(DWORD options) {
+  ScopedComPtr<IInternetProtocolRoot> protocol;
+  HRESULT hr = protocol.QueryFrom(delegate_);
+  if (protocol)
+    hr = protocol->Terminate(options);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::Suspend() {
+  ScopedComPtr<IInternetProtocolRoot> protocol;
+  HRESULT hr = protocol.QueryFrom(delegate_);
+  if (protocol)
+    hr = protocol->Suspend();
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::Resume() {
+  ScopedComPtr<IInternetProtocolRoot> protocol;
+  HRESULT hr = protocol.QueryFrom(delegate_);
+  if (protocol)
+    hr = protocol->Resume();
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::Read(void *buffer, ULONG size,
+    ULONG* size_read) {
+  ScopedComPtr<IInternetProtocol> protocol;
+  HRESULT hr = protocol.QueryFrom(delegate_);
+  if (protocol)
+    hr = protocol->Read(buffer, size, size_read);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::Seek(LARGE_INTEGER move, DWORD origin,
+    ULARGE_INTEGER* new_pos) {
+  ScopedComPtr<IInternetProtocol> protocol;
+  HRESULT hr = protocol.QueryFrom(delegate_);
+  if (protocol)
+    hr = protocol->Seek(move, origin, new_pos);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::LockRequest(DWORD options) {
+  ScopedComPtr<IInternetProtocol> protocol;
+  HRESULT hr = protocol.QueryFrom(delegate_);
+  if (protocol)
+    hr = protocol->LockRequest(options);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::UnlockRequest() {
+  ScopedComPtr<IInternetProtocol> protocol;
+  HRESULT hr = protocol.QueryFrom(delegate_);
+  if (protocol)
+    hr = protocol->UnlockRequest();
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::StartEx(IUri* uri,
+    IInternetProtocolSink* protocol_sink, IInternetBindInfo* bind_info,
+    DWORD flags, HANDLE_PTR reserved) {
+  ScopedComPtr<IInternetProtocolEx> protocol;
+  HRESULT hr = protocol.QueryFrom(delegate_);
+  if (protocol)
+    hr = protocol->StartEx(uri, protocol_sink, bind_info, flags, reserved);
+  return hr;
+}
+
+// IInternetPriority
+STDMETHODIMP ProtocolSinkWrap::SetPriority(LONG priority) {
+  ScopedComPtr<IInternetPriority> internet_priority;
+  HRESULT hr = internet_priority.QueryFrom(delegate_);
+  if (internet_priority)
+    hr = internet_priority->SetPriority(priority);
+  return hr;
+}
+
+STDMETHODIMP ProtocolSinkWrap::GetPriority(LONG* priority) {
+  ScopedComPtr<IInternetPriority> internet_priority;
+  HRESULT hr = internet_priority.QueryFrom(delegate_);
+  if (internet_priority)
+    hr = internet_priority->GetPriority(priority);
+  return hr;
+}
+
+// IWrappedProtocol
+STDMETHODIMP ProtocolSinkWrap::GetWrapperCode(LONG *code, DWORD_PTR reserved) {
+  ScopedComPtr<IWrappedProtocol> wrapped_protocol;
+  HRESULT hr = wrapped_protocol.QueryFrom(delegate_);
+  if (wrapped_protocol)
+    hr = wrapped_protocol->GetWrapperCode(code, reserved);
+  return hr;
+}
+
+
+// public IUriContainer
+STDMETHODIMP ProtocolSinkWrap::GetIUri(IUri** uri) {
+  ScopedComPtr<IUriContainer> uri_container;
+  HRESULT hr = uri_container.QueryFrom(delegate_);
+  if (uri_container)
+    hr = uri_container->GetIUri(uri);
+  return hr;
+}
+
+// Protected helpers
+
+void ProtocolSinkWrap::DetermineRendererType() {
+  if (is_undetermined()) {
+    if (IsOptInUrl(url_.c_str())) {
+      renderer_type_ = CHROME;
+    } else {
+      std::wstring xua_compat_content;
+      // Note that document_contents_ may have NULL characters in it. While
+      // browsers may handle this properly, we don't and will stop scanning for
+      // the XUACompat content value if we encounter one.
+      DCHECK(buffer_size_ < arraysize(buffer_));
+      buffer_[buffer_size_] = 0;
+      std::wstring html_contents;
+      // TODO(joshia): detect and handle different content encodings
+      UTF8ToWide(buffer_, buffer_size_, &html_contents);
+      UtilGetXUACompatContentValue(html_contents, &xua_compat_content);
+      if (StrStrI(xua_compat_content.c_str(), kChromeContentPrefix)) {
+        renderer_type_ = CHROME;
+      } else {
+        renderer_type_ = OTHER;
+      }
+    }
+  }
+}
+
+HRESULT ProtocolSinkWrap::OnReadImpl(void* buffer, ULONG size, ULONG* size_read,
+    InternetProtocol_Read_Fn orig_read) {
+  // We want to switch the renderer to chrome, we cannot return any
+  // data now.
+  if (CHROME == renderer_type())
+    return S_FALSE;
+
+  // Serve data from our buffer first.
+  if (OTHER == renderer_type()) {
+    const ULONG bytes_to_copy = std::min(buffer_size_ - buffer_pos_, size);
+    if (bytes_to_copy) {
+      memcpy(buffer, buffer_ + buffer_pos_, bytes_to_copy);
+      *size_read = bytes_to_copy;
+      buffer_pos_ += bytes_to_copy;
+      return S_OK;
+    }
+  }
+
+  return orig_read(protocol_, buffer, size, size_read);
+}
+
+scoped_refptr<ProtocolSinkWrap> ProtocolSinkWrap::InstanceFromProtocol(
+    IInternetProtocol* protocol) {
+  CComCritSecLock<CComAutoCriticalSection> lock(sink_map_lock_);
+  scoped_refptr<ProtocolSinkWrap> instance;
+  ProtocolSinkMap::iterator it = sink_map_.find(protocol);
+  if (sink_map_.end() != it)
+    instance = it->second;
+  return instance;
+}
+
+HRESULT ProtocolSinkWrap::WebBrowserFromProtocolSink(
+    IInternetProtocolSink* sink, IWebBrowser2** web_browser) {
+  // TODO(tommi): GUID_NULL doesn't work when loading from history.
+  //  asking for IID_IHttpNegotiate as the service id works, but
+  //  getting the IWebBrowser2 interface still doesn't work.
+  ScopedComPtr<IHttpNegotiate> http_negotiate;
+  HRESULT hr = DoQueryService(GUID_NULL, sink, http_negotiate.Receive());
+  if (http_negotiate)
+    hr = DoQueryService(IID_ITargetFrame2, http_negotiate, web_browser);
+
+  return hr;
+}
+
+ScopedComPtr<IInternetProtocolSink> ProtocolSinkWrap::MaybeWrapSink(
+    IInternetProtocol* protocol, IInternetProtocolSink* prot_sink,
+    const wchar_t* url) {
+  ScopedComPtr<IInternetProtocolSink> sink_to_use;
+  sink_to_use.QueryFrom(prot_sink);
+  ScopedComPtr<IWebBrowser2> web_browser;
+  WebBrowserFromProtocolSink(prot_sink, web_browser.Receive());
+  if (web_browser) {
+    CComObject<ProtocolSinkWrap>* wrap = NULL;
+    CComObject<ProtocolSinkWrap>::CreateInstance(&wrap);
+    DCHECK(wrap);
+    if (wrap->Initialize(protocol, prot_sink, url)) {
+      sink_to_use = wrap;
+    }
+  }
+
+  return sink_to_use;
+}
diff --git a/chrome_frame/protocol_sink_wrap.h b/chrome_frame/protocol_sink_wrap.h
new file mode 100644
index 0000000..e4f9cfb3
--- /dev/null
+++ b/chrome_frame/protocol_sink_wrap.h
@@ -0,0 +1,221 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_PROTOCOL_SINK_WRAP_H_
+#define CHROME_FRAME_PROTOCOL_SINK_WRAP_H_
+
+#include <exdisp.h>
+#include <urlmon.h>
+#include <atlbase.h>
+#include <atlcom.h>
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_comptr_win.h"
+#include "googleurl/src/gurl.h"
+#include "chrome_frame/ie8_types.h"
+
+// Typedefs for IInternetProtocol and related methods that we patch.
+typedef HRESULT (STDMETHODCALLTYPE* InternetProtocol_Start_Fn)(
+    IInternetProtocol* this_object, LPCWSTR url,
+    IInternetProtocolSink* prot_sink, IInternetBindInfo* bind_info,
+    DWORD flags, HANDLE_PTR reserved);
+typedef HRESULT (STDMETHODCALLTYPE* InternetProtocol_Read_Fn)(
+    IInternetProtocol* this_object, void* buffer, ULONG size,
+    ULONG* size_read);
+typedef HRESULT (STDMETHODCALLTYPE* InternetProtocol_StartEx_Fn)(
+    IInternetProtocolEx* this_object, IUri* uri,
+    IInternetProtocolSink* prot_sink, IInternetBindInfo* bind_info,
+    DWORD flags, HANDLE_PTR reserved);
+typedef HRESULT (STDMETHODCALLTYPE* InternetProtocolRoot_Continue_Fn)(
+    IInternetProtocolRoot* me, PROTOCOLDATA* data);
+
+// A class to wrap protocol sink in IInternetProtocol::Start[Ex] for
+// HTTP and HTTPS protocols.
+//
+// This is an alternative to a mime filter and we have to do this in order
+// to inspect initial portion of HTML for 'chrome' meta tag and report
+// a different mime type in that case.
+//
+// We implement several documented interfaces
+// supported by the original sink provided by urlmon. There are a few
+// undocumented interfaces that we have chosen not to implement
+// but delegate simply the QI.
+class ProtocolSinkWrap
+    : public CComObjectRootEx<CComMultiThreadModel>,
+      public IInternetProtocolSink,
+      public IInternetBindInfoEx,
+      public IServiceProvider,
+      public IAuthenticate,
+      public IInternetProtocolEx,
+      public IInternetPriority,
+      public IWrappedProtocol,
+      // public IPreBindingSupport, // undocumented
+      // public ITransProtocolSink, // Undocumented
+      // public ITransactionInternal, // undocumented
+      public IUriContainer {
+ public:
+
+#define COM_INTERFACE_ENTRY_IF_DELEGATE_SUPPORTS(x) \
+    COM_INTERFACE_ENTRY_FUNC(_ATL_IIDOF(x), \
+        offsetofclass(x, _ComMapClass), \
+        IfDelegateSupports)
+
+BEGIN_COM_MAP(ProtocolSinkWrap)
+  COM_INTERFACE_ENTRY(IInternetProtocolSink)
+  COM_INTERFACE_ENTRY(IInternetBindInfo)
+  COM_INTERFACE_ENTRY(IInternetBindInfoEx)
+  COM_INTERFACE_ENTRY(IServiceProvider)
+  COM_INTERFACE_ENTRY(IAuthenticate)
+  COM_INTERFACE_ENTRY(IInternetProtocolRoot)
+  COM_INTERFACE_ENTRY(IInternetProtocol)
+  COM_INTERFACE_ENTRY(IInternetProtocolEx)
+  COM_INTERFACE_ENTRY(IInternetPriority)
+  COM_INTERFACE_ENTRY(IWrappedProtocol)
+  COM_INTERFACE_ENTRY_IF_DELEGATE_SUPPORTS(IUriContainer)
+  COM_INTERFACE_ENTRY_FUNC_BLIND(0, CheckOutgoingInterface)
+END_COM_MAP()
+
+  ProtocolSinkWrap();
+  virtual ~ProtocolSinkWrap();
+
+  bool Initialize(IInternetProtocol* protocol,
+      IInternetProtocolSink* original_sink, const wchar_t* url);
+
+  static bool PatchProtocolHandler(const wchar_t* dll,
+      const CLSID& handler_clsid);
+
+  // IInternetProtocol/Ex patches.
+  static HRESULT STDMETHODCALLTYPE OnStart(InternetProtocol_Start_Fn orig_start,
+      IInternetProtocol* protocol, LPCWSTR url,
+      IInternetProtocolSink* prot_sink, IInternetBindInfo* bind_info,
+      DWORD flags, HANDLE_PTR reserved);
+
+  static HRESULT STDMETHODCALLTYPE OnStartEx(
+      InternetProtocol_StartEx_Fn orig_start_ex, IInternetProtocolEx* protocol,
+      IUri* uri, IInternetProtocolSink* prot_sink,
+      IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved);
+
+  static HRESULT STDMETHODCALLTYPE OnRead(InternetProtocol_Read_Fn orig_read,
+      IInternetProtocol* protocol, void* buffer, ULONG size, ULONG* size_read);
+
+  // IInternetProtocolSink methods
+  STDMETHOD(Switch)(PROTOCOLDATA* protocol_data);
+  STDMETHOD(ReportProgress)(ULONG status_code, LPCWSTR status_text);
+  STDMETHOD(ReportData)(DWORD flags, ULONG progress, ULONG max_progress);
+  STDMETHOD(ReportResult)(HRESULT result, DWORD error, LPCWSTR result_text);
+
+  // IInternetBindInfoEx
+  STDMETHOD(GetBindInfo)(DWORD* flags, BINDINFO* bind_info);
+  STDMETHOD(GetBindString)(ULONG string_type, LPOLESTR* string_array,
+      ULONG array_size, ULONG* size_returned);
+  STDMETHOD(GetBindInfoEx)(DWORD *flags, BINDINFO* bind_info,
+      DWORD* bindf2, DWORD *reserved);
+
+  // IServiceProvider
+  STDMETHOD(QueryService)(REFGUID service_guid, REFIID riid, void** service);
+
+  // IAuthenticate
+  STDMETHOD(Authenticate)(HWND* window, LPWSTR* user_name, LPWSTR* password);
+
+  // IInternetProtocolEx
+  STDMETHOD(Start)(LPCWSTR url, IInternetProtocolSink *protocol_sink,
+      IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved);
+  STDMETHOD(Continue)(PROTOCOLDATA* protocol_data);
+  STDMETHOD(Abort)(HRESULT reason, DWORD options);
+  STDMETHOD(Terminate)(DWORD options);
+  STDMETHOD(Suspend)();
+  STDMETHOD(Resume)();
+  STDMETHOD(Read)(void *buffer, ULONG size, ULONG* size_read);
+  STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos);
+  STDMETHOD(LockRequest)(DWORD options);
+  STDMETHOD(UnlockRequest)();
+  STDMETHOD(StartEx)(IUri* uri, IInternetProtocolSink* protocol_sink,
+      IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved);
+
+  // IInternetPriority
+  STDMETHOD(SetPriority)(LONG priority);
+  STDMETHOD(GetPriority)(LONG* priority);
+
+  // IWrappedProtocol
+  STDMETHOD(GetWrapperCode)(LONG *code, DWORD_PTR reserved);
+
+  // public IUriContainer
+  STDMETHOD(GetIUri)(IUri** uri);
+
+  // IPreBindingSupport, // undocumented
+  // ITransProtocolSink, // Undocumented
+  // ITransactionInternal, // undocumented
+
+ protected:
+  enum RendererType {
+    UNDETERMINED,
+    CHROME,
+    OTHER
+  };
+
+  typedef std::map<IInternetProtocol*, ProtocolSinkWrap*> ProtocolSinkMap;
+  static const int kMaxContentSniffLength = 1024;
+
+  static scoped_refptr<ProtocolSinkWrap> InstanceFromProtocol(
+      IInternetProtocol* protocol);
+  static HRESULT WebBrowserFromProtocolSink(IInternetProtocolSink* sink,
+      IWebBrowser2** web_browser);
+  static ScopedComPtr<IInternetProtocolSink> MaybeWrapSink(
+      IInternetProtocol* protocol, IInternetProtocolSink* prot_sink,
+      const wchar_t* url);
+  static HRESULT WINAPI CheckOutgoingInterface(void* obj, REFIID iid,
+      LPVOID* ret, DWORD cookie);
+  static HRESULT WINAPI IfDelegateSupports(void* obj, REFIID iid,
+      LPVOID* ret, DWORD cookie);
+
+  void DetermineRendererType();
+  HRESULT OnReadImpl(void* buffer, ULONG size, ULONG* size_read,
+      InternetProtocol_Read_Fn orig_read);
+
+  bool is_undetermined() const {
+    return (UNDETERMINED == renderer_type_);
+  }
+  RendererType renderer_type() const {
+    return renderer_type_;
+  }
+
+  // WARNING: Don't use GURL variables here.  Please see
+  // http://b/issue?id=2102171 for details.
+
+  // Remember original sink
+  CComPtr<IInternetProtocolSink> delegate_;
+  // Cannot take a reference on the protocol.
+  IInternetProtocol* protocol_;
+  RendererType renderer_type_;
+
+  // Buffer for accumulated data including 1 extra for NULL-terminator
+  char buffer_[kMaxContentSniffLength + 1];
+  unsigned long buffer_size_;
+  unsigned long buffer_pos_;
+
+  // Accumulated result
+  bool is_saved_result_;
+  HRESULT result_code_;
+  DWORD result_error_;
+  std::wstring result_text_;
+  // For tracking re-entrency and preventing duplicate Read()s from 
+  // distorting the outcome of ReportData.
+  int report_data_recursiveness_;
+
+  static ProtocolSinkMap sink_map_;
+  // TODO(joshia): Replace with Lock
+  static CComAutoCriticalSection sink_map_lock_;
+
+  std::wstring url_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProtocolSinkWrap);
+};
+
+
+#endif  // CHROME_FRAME_PROTOCOL_SINK_WRAP_H_
+
diff --git a/chrome_frame/rename_me_to_supplement.gypi b/chrome_frame/rename_me_to_supplement.gypi
new file mode 100644
index 0000000..fbe6e55
--- /dev/null
+++ b/chrome_frame/rename_me_to_supplement.gypi
@@ -0,0 +1,24 @@
+# Copyright (c) 2009 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.

+

+# It is used to override the Chrome Frame appid, as well as

+# add extra required source files and defines. The file is checked in as

+# rename_me_to_supplement.gypi so as to not be active on the official builder.

+# This is required because with these fields present, Chrome will build in

+# Chrome Frame mode, which isn't what the Chrome official builder wants yet.

+#

+# Renaming this file to supplement.gypi will cause gyp to pick it up. 

+# supplement.gypi is a magic gyp include file that gets pulled in before any

+# other includes. 

+#

+# The official builder will define these extra vars and whatnot in the build

+# scripts themselves.

+{

+  'variables': {

+    'google_update_appid': '{8BA986DA-5100-405E-AA35-86F34A02ACBF}',

+    'extra_installer_util_sources': 1,

+    'branding': 'Chrome',

+    'experimental_build_define': 1,

+  },

+} 
\ No newline at end of file
diff --git a/chrome_frame/resource.h b/chrome_frame/resource.h
new file mode 100644
index 0000000..53f51a5
--- /dev/null
+++ b/chrome_frame/resource.h
@@ -0,0 +1,19 @@
+//{{NO_DEPENDENCIES}}
+
+// Copyright (c) 2009 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.
+
+// Microsoft Visual C++ generated include file.
+// Used by resources/tlb_resource.rc
+
+// Default values for new objects
+// 
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        204
+#define _APS_NEXT_COMMAND_VALUE         32770
+#define _APS_NEXT_CONTROL_VALUE         205
+#define _APS_NEXT_SYMED_VALUE           108
+#endif
+#endif
diff --git a/chrome_frame/resources/chrome_frame_resources.grd b/chrome_frame/resources/chrome_frame_resources.grd
new file mode 100644
index 0000000..c249ce8d
--- /dev/null
+++ b/chrome_frame/resources/chrome_frame_resources.grd
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 
+Copyright (c) 2009 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.
+-->
+
+<!--
+Embeded strings, branding resource, etc. See chrome_frame_strings.grd 
+for localizable strings
+-->
+
+<grit latest_public_release="0" current_release="1">
+  <outputs>
+    <output filename="grit/chrome_frame_resources.h" type="rc_header">
+      <emit emit_type='prepend'></emit>
+    </output>
+    <output filename="chrome_frame_resources.rc" type="rc_all" lang="en"/>
+    <output filename="chrome_frame_resources.pak" type="data_package" />
+  </outputs>
+  <release seq="1">
+    <messages>
+      <!-- TODO(slightlyoff): should these be in chrome_frame_strings.grd
+             instead?  -->
+      <message name="IDS_PROJNAME">
+        ChromeTab
+      </message>
+      <message name="IDS_VERSIONMISMATCH_HEADER">
+        ChromeFrame Update.
+      </message>
+      <message name="IDS_VERSIONMISMATCH">
+        ChromeFrame has been updated. Please restart your browser. Chrome version: <ph name="TODO_0001">%ls<ex>TODO</ex></ph>, Chrome Frame version: <ph name="TODO_0002">%ls<ex>TODO</ex></ph>
+      </message>
+      <message name="IDS_VERSIONUNKNOWN">
+        Very old
+      </message>
+    </messages>
+    <structures first_id="50000">
+      <structure name="IDD_FIND_DIALOG" file="structured_resources.rc" type="dialog" >
+      </structure>
+    </structures>
+    <includes>
+      <include name="IDB_CHROME_ACTIVE_DOCUMENT" file="../chrome_active_document.bmp" 
+               type="BITMAP" />
+      <include name="IDR_BHO" file="../bho.rgs" type="REGISTRY" />
+      <include name="IDR_CHROMETAB" file="../chrome_tab.rgs" type="REGISTRY" />
+      <include name="IDR_CHROMEPROTOCOL" file="../chrome_protocol.rgs" type="REGISTRY" />
+      <include name="IDR_CHROMEACTIVEDOCUMENT" file="../chrome_active_document.rgs" 
+               type="REGISTRY" />
+      <include name="IDR_CHROMEFRAME" file="../chrome_frame_activex.rgs" type="REGISTRY" />
+      <include name="IDR_CHROMEFRAME_NPAPI" file="../chrome_frame_npapi.rgs" type="REGISTRY" />
+    </includes>
+  </release>
+</grit>
diff --git a/chrome_frame/resources/chrome_frame_strings.grd b/chrome_frame/resources/chrome_frame_strings.grd
new file mode 100644
index 0000000..39bcfd9
--- /dev/null
+++ b/chrome_frame/resources/chrome_frame_strings.grd
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 
+Copyright (c) 2009 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.
+-->
+
+<!-- Definitions of resources that will be translated for each locale.
+-->
+
+<grit base_dir="." latest_public_release="0" current_release="1"
+      source_lang_id="en" enc_check="möl">
+  <outputs>
+    <output filename="grit/chrome_frame_strings.h" type="rc_header">
+      <emit emit_type='prepend'></emit>
+    </output>
+    <output filename="chrome_frame_strings.rc" type="rc_all" lang="en" />
+  </outputs>
+  <translations>
+    <!--
+    <file path="chrome_frame_strings_en-GB.xtb" lang="en-GB" />
+    ...
+    -->
+  </translations>
+  <release seq="1" allow_pseudo="false">
+    <messages fallback_to_english="true" first_id="30000">
+    
+      <!-- Menus -->
+      <message name="IDS_CHROME_FRAME_MENU_ABOUT" desc="About Chrome Frame label">
+        About Chrome Frame...
+      </message>
+
+      <!-- General application strings -->
+      <message name="IDS_CHROME_FRAME_NAME" desc="Official plugin name.">
+        Google Chrome Frame
+      </message>
+    </messages>
+  </release>
+</grit>
diff --git a/chrome_frame/resources/structured_resources.rc b/chrome_frame/resources/structured_resources.rc
new file mode 100644
index 0000000..22e03378
--- /dev/null
+++ b/chrome_frame/resources/structured_resources.rc
@@ -0,0 +1,24 @@
+// Copyright (c) 2009 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.
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_FIND_DIALOG DIALOGEX 0, 0, 278, 60
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Find"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+    EDITTEXT        IDC_FIND_TEXT,51,7,154,14,ES_AUTOHSCROLL
+    DEFPUSHBUTTON   "&Find Next",IDOK,221,7,50,14
+    PUSHBUTTON      "Cancel",IDCANCEL,221,24,50,14
+    CONTROL         "Match &case",IDC_MATCH_CASE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,24,52,10
+    GROUPBOX        "Direction",IDC_STATIC,85,24,119,24
+    CONTROL         "&Down",IDC_DIRECTION_DOWN,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,101,34,34,10
+    CONTROL         "&Up",IDC_DIRECTION_UP,"Button",BS_AUTORADIOBUTTON,155,34,38,10
+    LTEXT           "Fi&nd what:",IDC_STATIC,6,7,35,8
+END
diff --git a/chrome_frame/resources/tlb_resource.rc b/chrome_frame/resources/tlb_resource.rc
new file mode 100644
index 0000000..b16c9d43
--- /dev/null
+++ b/chrome_frame/resources/tlb_resource.rc
@@ -0,0 +1,68 @@
+// Copyright (c) 2009 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.
+
+// NOTE(slightlyoff): the below is generated, and apparently, magical.
+// Seemingly innocuous edits totally destroy registration of the DLL. 
+// Edit at your own peril.
+
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE 
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE 
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE 
+BEGIN
+    "1 TYPELIB ""chrome_tab.tlb""\r\n"
+    "\0"
+END
+#else
+#include "chrome_tab_version.rc"
+#endif    // APSTUDIO_INVOKED
+
+#endif    // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+1 TYPELIB "chrome_tab.tlb"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/chrome_frame/scoped_ns_ptr_win.h b/chrome_frame/scoped_ns_ptr_win.h
new file mode 100644
index 0000000..91780fd
--- /dev/null
+++ b/chrome_frame/scoped_ns_ptr_win.h
@@ -0,0 +1,149 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_SCOPED_NS_PTR_WIN_H_
+#define CHROME_FRAME_SCOPED_NS_PTR_WIN_H_
+
+#include "base/logging.h"
+#include "base/ref_counted.h"
+
+#include "third_party/xulrunner-sdk/win/include/xpcom/nsISupports.h"
+
+
+// Utility template to prevent users of ScopedNsPtr from calling AddRef and/or
+// Release() without going through the ScopedNsPtr class.
+template <class nsInterface>
+class BlocknsISupportsMethods : public nsInterface {
+ private:
+  NS_IMETHOD QueryInterface(REFNSIID iid, void** object) = 0;
+  NS_IMETHOD_(nsrefcnt) AddRef() = 0;
+  NS_IMETHOD_(nsrefcnt) Release() = 0;
+};
+
+// A smart pointer class for nsISupports.
+// Based on ScopedComPtr.
+// We have our own class instead of nsCOMPtr.  nsCOMPtr has parts of its
+// implementation in the xpcomglue lib which we can't use since that lib
+// is built with incompatible build flags to ours.
+template <class nsInterface,
+          const nsIID* interface_id =
+              reinterpret_cast<const nsIID*>(&__uuidof(nsInterface))>
+class ScopedNsPtr : public scoped_refptr<nsInterface> {
+ public:
+  typedef scoped_refptr<nsInterface> ParentClass;
+
+  ScopedNsPtr() {
+  }
+
+  explicit ScopedNsPtr(nsInterface* p) : ParentClass(p) {
+  }
+
+  explicit ScopedNsPtr(const ScopedNsPtr<nsInterface, interface_id>& p)
+      : ParentClass(p) {
+  }
+
+  ~ScopedNsPtr() {
+    // We don't want the smart pointer class to be bigger than the pointer
+    // it wraps.
+    COMPILE_ASSERT(sizeof(ScopedNsPtr<nsInterface, interface_id>) ==
+                   sizeof(nsInterface*), ScopedNsPtrSize);
+  }
+
+  // Explicit Release() of the held object.  Useful for reuse of the
+  // ScopedNsPtr instance.
+  // Note that this function equates to nsISupports::Release and should not
+  // be confused with e.g. scoped_ptr::release().
+  void Release() {
+    if (ptr_ != NULL) {
+      ptr_->Release();
+      ptr_ = NULL;
+    }
+  }
+
+  // Sets the internal pointer to NULL and returns the held object without
+  // releasing the reference.
+  nsInterface* Detach() {
+    nsInterface* p = ptr_;
+    ptr_ = NULL;
+    return p;
+  }
+
+  // Accepts an interface pointer that has already been addref-ed.
+  void Attach(nsInterface* p) {
+    DCHECK(ptr_ == NULL);
+    ptr_ = p;
+  }
+
+  // Retrieves the pointer address.
+  // Used to receive object pointers as out arguments (and take ownership).
+  // The function DCHECKs on the current value being NULL.
+  // Usage: Foo(p.Receive());
+  nsInterface** Receive() {
+    DCHECK(ptr_ == NULL) << "Object leak. Pointer must be NULL";
+    return &ptr_;
+  }
+
+  template <class Query>
+  nsresult QueryInterface(Query** p) {
+    DCHECK(p != NULL);
+    DCHECK(ptr_ != NULL);
+    return ptr_->QueryInterface(Query::GetIID(), reinterpret_cast<void**>(p));
+  }
+
+  template <class Query>
+  nsresult QueryInterface(const nsIID& iid, Query** p) {
+    DCHECK(p != NULL);
+    DCHECK(ptr_ != NULL);
+    return ptr_->QueryInterface(iid, reinterpret_cast<void**>(p));
+  }
+
+  // Queries |other| for the interface this object wraps and returns the
+  // error code from the other->QueryInterface operation.
+  nsresult QueryFrom(nsISupports* other) {
+    DCHECK(other != NULL);
+    return other->QueryInterface(iid(), reinterpret_cast<void**>(Receive()));
+  }
+
+  // Checks if the identity of |other| and this object is the same.
+  bool IsSameObject(nsISupports* other) {
+    if (!other && !ptr_)
+      return true;
+
+    if (!other || !ptr_)
+      return false;
+
+    nsIID iid = NS_ISUPPORTS_IID;
+    ScopedNsPtr<nsISupports, iid> my_identity;
+    QueryInterface(my_identity.Receive());
+
+    ScopedNsPtr<nsISupports, iid> other_identity;
+    other->QueryInterface(other_identity.Receive());
+
+    return static_cast<nsISupports*>(my_identity) ==
+           static_cast<nsISupports*>(other_identity);
+  }
+
+  // Provides direct access to the interface.
+  // Here we use a well known trick to make sure we block access to
+  // IUknown methods so that something bad like this doesn't happen:
+  //    ScopedNsPtr<nsISupports> p(Foo());
+  //    p->Release();
+  //    ... later the destructor runs, which will Release() again.
+  // and to get the benefit of the DCHECKs we add to QueryInterface.
+  // There's still a way to call these methods if you absolutely must
+  // by statically casting the ScopedNsPtr instance to the wrapped interface
+  // and then making the call... but generally that shouldn't be necessary.
+  BlocknsISupportsMethods<nsInterface>* operator->() const {
+    DCHECK(ptr_ != NULL);
+    return reinterpret_cast<BlocknsISupportsMethods<nsInterface>*>(ptr_);
+  }
+
+  // static methods
+
+  static const nsIID& iid() {
+    return *interface_id;
+  }
+};
+
+#endif  // CHROME_FRAME_SCOPED_NS_PTR_WIN_H_
diff --git a/chrome_frame/script_security_manager.h b/chrome_frame/script_security_manager.h
new file mode 100644
index 0000000..a586c84
--- /dev/null
+++ b/chrome_frame/script_security_manager.h
@@ -0,0 +1,785 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef CHROME_FRAME_SCRIPT_SECURITY_MANAGER_H_
+#define CHROME_FRAME_SCRIPT_SECURITY_MANAGER_H_
+
+// Gecko headers need this on Windows.
+#ifndef XP_WIN
+#define XP_WIN
+#endif
+
+#include "chrome_frame/ns_associate_iid_win.h"
+#include "third_party/xulrunner-sdk/win/include/caps/nsIScriptSecurityManager.h"
+
+ASSOCIATE_IID(NS_ISCRIPTSECURITYMANAGER_IID_STR, nsIScriptSecurityManager);
+
+// Because we need to support both Firefox 3.0.x and 3.5.x, and because Mozilla
+// changed these unfrozen interfaces, both are declared here, with specific
+// names for specific versions.  Doing this makes it easier to adopt new
+// version of the interfaces as they evolve in future version of Firefox.
+
+// The xxx_FF30 declatations below were taken from the file
+// nsIScriptSecurityManager.h in the gecko 1.9.0.5 SDK.
+// The xxx_FF35 declarations below were taken from the file
+// nsIScriptSecurityManager.h in the gecko 1.9.1 SDK.
+
+#define NS_IXPCSECURITYMANAGER_IID_FF30 \
+  {0x31431440, 0xf1ce, 0x11d2, \
+    { 0x98, 0x5a, 0x00, 0x60, 0x08, 0x96, 0x24, 0x22 }}
+
+class NS_NO_VTABLE nsIXPCSecurityManager_FF30 : public nsISupports {
+ public:
+
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPCSECURITYMANAGER_IID_FF30)
+
+  /**
+    * These flags are used when calling nsIXPConnect::SetSecurityManager
+    */
+  enum { HOOK_CREATE_WRAPPER = 1U };
+
+  enum { HOOK_CREATE_INSTANCE = 2U };
+
+  enum { HOOK_GET_SERVICE = 4U };
+
+  enum { HOOK_CALL_METHOD = 8U };
+
+  enum { HOOK_GET_PROPERTY = 16U };
+
+  enum { HOOK_SET_PROPERTY = 32U };
+
+  enum { HOOK_ALL = 63U };
+
+  /**
+    * For each of these hooks returning NS_OK means 'let the action continue'.
+    * Returning an error code means 'veto the action'. XPConnect will return
+    * JS_FALSE to the js engine if the action is vetoed. The implementor of this
+    * interface is responsible for setting a JS exception into the JSContext
+    * if that is appropriate.
+    */
+  /* void CanCreateWrapper (in JSContextPtr aJSContext, in nsIIDRef aIID,
+                            in nsISupports aObj, in nsIClassInfo aClassInfo,
+                            inout voidPtr aPolicy); */
+  NS_IMETHOD CanCreateWrapper(JSContext*  aJSContext, const nsIID & aIID,
+                              nsISupports* aObj, nsIClassInfo* aClassInfo,
+                              void** aPolicy) = 0;
+
+  /* void CanCreateInstance (in JSContextPtr aJSContext, in nsCIDRef aCID); */
+  NS_IMETHOD CanCreateInstance(JSContext* aJSContext, const nsCID& aCID) = 0;
+
+  /* void CanGetService (in JSContextPtr aJSContext, in nsCIDRef aCID); */
+  NS_IMETHOD CanGetService(JSContext* aJSContext, const nsCID& aCID) = 0;
+
+  enum { ACCESS_CALL_METHOD = 0U };
+
+  enum { ACCESS_GET_PROPERTY = 1U };
+
+  enum { ACCESS_SET_PROPERTY = 2U };
+
+  /* void CanAccess (in PRUint32 aAction,
+                     in nsAXPCNativeCallContextPtr aCallContext,
+                     in JSContextPtr aJSContext, in JSObjectPtr aJSObject,
+                     in nsISupports aObj, in nsIClassInfo aClassInfo,
+                     in JSVal aName, inout voidPtr aPolicy); */
+  NS_IMETHOD CanAccess(PRUint32 aAction,
+                       nsAXPCNativeCallContext* aCallContext,
+                       JSContext* aJSContext,
+                       JSObject* aJSObject,
+                       nsISupports* aObj,
+                       nsIClassInfo* aClassInfo,
+                       jsval aName, void** aPolicy) = 0;
+
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIXPCSecurityManager_FF30,
+                              NS_IXPCSECURITYMANAGER_IID_FF30);
+
+
+#define NS_ISCRIPTSECURITYMANAGER_IID_STR_FF30 \
+    "3fffd8e8-3fea-442e-a0ed-2ba81ae197d5"
+
+#define NS_ISCRIPTSECURITYMANAGER_IID_FF30 \
+  {0x3fffd8e8, 0x3fea, 0x442e, \
+    { 0xa0, 0xed, 0x2b, 0xa8, 0x1a, 0xe1, 0x97, 0xd5 }}
+
+/**
+ * WARNING!! The JEP needs to call GetSubjectPrincipal()
+ * to support JavaScript-to-Java LiveConnect.  So every change to the
+ * nsIScriptSecurityManager interface (big enough to change its IID) also
+ * breaks JavaScript-to-Java LiveConnect on mac.
+ *
+ * If you REALLY have to change this interface, please mark your bug as
+ * blocking bug 293973.
+ */
+class NS_NO_VTABLE NS_SCRIPTABLE nsIScriptSecurityManager_FF30 :
+    public nsIXPCSecurityManager_FF30 {
+ public: 
+
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTSECURITYMANAGER_IID_FF30)
+
+  /**
+     * Checks whether the running script is allowed to access aProperty.
+     */
+  /* [noscript] void checkPropertyAccess (in JSContextPtr aJSContext,
+                                          in JSObjectPtr aJSObject,
+                                          in string aClassName,
+                                          in JSVal aProperty,
+                                          in PRUint32 aAction); */
+  NS_IMETHOD CheckPropertyAccess(JSContext* aJSContext, JSObject* aJSObject,
+                                 const char* aClassName, jsval aProperty,
+                                 PRUint32 aAction) = 0;
+
+  /**
+     * Checks whether the running script is allowed to connect to aTargetURI
+     */
+  /* [noscript] void checkConnect (in JSContextPtr aJSContext,
+                                   in nsIURI aTargetURI, in string aClassName,
+                                   in string aProperty); */
+  NS_IMETHOD CheckConnect(JSContext* aJSContext, nsIURI* aTargetURI,
+                          const char* aClassName, const char* aProperty) = 0;
+
+  /**
+     * Check that the script currently running in context "cx" can load "uri".
+     *
+     * Will return error code NS_ERROR_DOM_BAD_URI if the load request 
+     * should be denied.
+     *
+     * @param cx the JSContext of the script causing the load
+     * @param uri the URI that is being loaded
+     */
+  /* [noscript] void checkLoadURIFromScript (in JSContextPtr cx,
+                                             in nsIURI uri); */
+  NS_IMETHOD CheckLoadURIFromScript(JSContext* cx, nsIURI* uri) = 0;
+
+  /**
+     * Default CheckLoadURI permissions
+     */
+  enum { STANDARD = 0U };
+
+  enum { LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT = 1U };
+
+  enum { ALLOW_CHROME = 2U };
+
+  enum { DISALLOW_INHERIT_PRINCIPAL = 4U };
+
+  enum { DISALLOW_SCRIPT_OR_DATA = 4U };
+
+  enum { DISALLOW_SCRIPT = 8U };
+
+  /**
+     * Check that content with principal aPrincipal can load "uri".
+     *
+     * Will return error code NS_ERROR_DOM_BAD_URI if the load request 
+     * should be denied.
+     *
+     * @param aPrincipal the principal identifying the actor causing the load
+     * @param uri the URI that is being loaded
+     * @param flags the permission set, see above
+     */
+  /* void checkLoadURIWithPrincipal (in nsIPrincipal aPrincipal, in nsIURI uri,
+                                     in unsigned long flags); */
+  NS_SCRIPTABLE NS_IMETHOD CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
+      nsIURI* uri, PRUint32 flags) = 0;
+
+  /**
+     * Check that content from "from" can load "uri".
+     *
+     * Will return error code NS_ERROR_DOM_BAD_URI if the load request 
+     * should be denied.
+     *
+     * @param from the URI causing the load
+     * @param uri the URI that is being loaded
+     * @param flags the permission set, see above
+     *
+     * @deprecated Use checkLoadURIWithPrincipal instead of this function.
+     */
+  /* void checkLoadURI (in nsIURI from, in nsIURI uri,
+                        in unsigned long flags); */
+  NS_SCRIPTABLE NS_IMETHOD CheckLoadURI(nsIURI* from, nsIURI* uri,
+                                        PRUint32 flags) = 0;
+
+  /**
+     * Similar to checkLoadURIWithPrincipal but there are two differences:
+     *
+     * 1) The URI is a string, not a URI object.
+     * 2) This function assumes that the URI may still be subject to fixup (and
+     * hence will check whether fixed-up versions of the URI are allowed to
+     * load as well); if any of the versions of this URI is not allowed, this
+     * function will return error code NS_ERROR_DOM_BAD_URI.
+     */
+  /* void checkLoadURIStrWithPrincipal (in nsIPrincipal aPrincipal,
+                                        in AUTF8String uri,
+                                        in unsigned long flags); */
+  NS_SCRIPTABLE NS_IMETHOD CheckLoadURIStrWithPrincipal(
+      nsIPrincipal* aPrincipal, const nsACString& uri, PRUint32 flags) = 0;
+
+  /**
+     * Same as CheckLoadURI but takes string arguments for ease of use
+     * by scripts
+     *
+     * @deprecated Use checkLoadURIStrWithPrincipal instead of this function.
+     */
+  /* void checkLoadURIStr (in AUTF8String from, in AUTF8String uri,
+                           in unsigned long flags); */
+  NS_SCRIPTABLE NS_IMETHOD CheckLoadURIStr(const nsACString& from,
+                                           const nsACString& uri,
+                                           PRUint32 flags) = 0;
+
+  /**
+     * Check that the function 'funObj' is allowed to run on 'targetObj'
+     *
+     * Will return error code NS_ERROR_DOM_SECURITY_ERR if the function
+     * should not run
+     *
+     * @param cx The current active JavaScript context.
+     * @param funObj The function trying to run..
+     * @param targetObj The object the function will run on.
+     */
+  /* [noscript] void checkFunctionAccess (in JSContextPtr cx,
+                                          in voidPtr funObj,
+                                          in voidPtr targetObj); */
+  NS_IMETHOD CheckFunctionAccess(JSContext* cx, void* funObj,
+                                 void* targetObj) = 0;
+
+  /**
+     * Return true if content from the given principal is allowed to
+     * execute scripts.
+     */
+  /* [noscript] boolean canExecuteScripts (in JSContextPtr cx,
+                                           in nsIPrincipal principal); */
+  NS_IMETHOD CanExecuteScripts(JSContext* cx, nsIPrincipal* principal,
+                               PRBool* _retval) = 0;
+
+  /**
+     * Return the principal of the innermost frame of the currently 
+     * executing script. Will return null if there is no script 
+     * currently executing.
+     */
+  /* [noscript] nsIPrincipal getSubjectPrincipal (); */
+  NS_IMETHOD GetSubjectPrincipal(nsIPrincipal** _retval) = 0;
+
+  /**
+     * Return the all-powerful system principal.
+     */
+  /* [noscript] nsIPrincipal getSystemPrincipal (); */
+  NS_IMETHOD GetSystemPrincipal(nsIPrincipal** _retval) = 0;
+
+  /**
+     * Return a principal with the specified certificate fingerprint, subject
+     * name (the full name or concatenated set of names of the entity
+     * represented by the certificate), pretty name, certificate, and
+     * codebase URI.  The certificate fingerprint and subject name MUST be
+     * nonempty; otherwise an error will be thrown.  Similarly, aCert must
+     * not be null.
+     */
+  /* [noscript] nsIPrincipal getCertificatePrincipal (
+         in AUTF8String aCertFingerprint, in AUTF8String aSubjectName,
+         in AUTF8String aPrettyName, in nsISupports aCert, in nsIURI aURI); */
+  NS_IMETHOD GetCertificatePrincipal(const nsACString& aCertFingerprint,
+                                     const nsACString& aSubjectName,
+                                     const nsACString& aPrettyName,
+                                     nsISupports* aCert,
+                                     nsIURI* aURI, nsIPrincipal** _retval) = 0;
+
+  /**
+     * Return a principal that has the same origin as aURI.
+     */
+  /* nsIPrincipal getCodebasePrincipal (in nsIURI aURI); */
+  NS_SCRIPTABLE NS_IMETHOD GetCodebasePrincipal(nsIURI* aURI,
+                                                nsIPrincipal **_retval) = 0;
+
+  /**
+     * Request that 'capability' can be enabled by scripts or applets
+     * running with 'principal'. Will prompt user if
+     * necessary. Returns nsIPrincipal::ENABLE_GRANTED or
+     * nsIPrincipal::ENABLE_DENIED based on user's choice.
+     */
+  /* [noscript] short requestCapability (in nsIPrincipal principal,
+                                         in string capability); */
+  NS_IMETHOD RequestCapability(nsIPrincipal* principal,
+                               const char* capability, PRInt16* _retval) = 0;
+
+  /**
+     * Return true if the currently executing script has 'capability' enabled.
+     */
+  /* boolean isCapabilityEnabled (in string capability); */
+  NS_SCRIPTABLE NS_IMETHOD IsCapabilityEnabled(const char* capability,
+                                               PRBool* _retval) = 0;
+
+  /**
+     * Enable 'capability' in the innermost frame of the currently executing
+     * script.
+     */
+  /* void enableCapability (in string capability); */
+  NS_SCRIPTABLE NS_IMETHOD EnableCapability(const char* capability) = 0;
+
+  /**
+     * Remove 'capability' from the innermost frame of the currently
+     * executing script. Any setting of 'capability' from enclosing
+     * frames thus comes into effect.
+     */
+  /* void revertCapability (in string capability); */
+  NS_SCRIPTABLE NS_IMETHOD RevertCapability(const char* capability) = 0;
+
+  /**
+     * Disable 'capability' in the innermost frame of the currently executing
+     * script.
+     */
+  /* void disableCapability (in string capability); */
+  NS_SCRIPTABLE NS_IMETHOD DisableCapability(const char* capability) = 0;
+
+  /**
+     * Allow 'certificateID' to enable 'capability.' Can only be performed
+     * by code signed by the system certificate.
+     */
+  /* void setCanEnableCapability (in AUTF8String certificateFingerprint,
+                                  in string capability, in short canEnable); */
+  NS_SCRIPTABLE NS_IMETHOD SetCanEnableCapability(
+      const nsACString& certificateFingerprint, const char* capability,
+      PRInt16 canEnable) = 0;
+
+  /**
+     * Return the principal of the specified object in the specified context.
+     */
+  /* [noscript] nsIPrincipal getObjectPrincipal (in JSContextPtr cx,
+                                                 in JSObjectPtr obj); */
+  NS_IMETHOD GetObjectPrincipal(JSContext* cx, JSObject* obj,
+                                nsIPrincipal** _retval) = 0;
+
+  /**
+     * Returns true if the principal of the currently running script is the
+     * system principal, false otherwise.
+     */
+  /* [noscript] boolean subjectPrincipalIsSystem (); */
+  NS_IMETHOD SubjectPrincipalIsSystem(PRBool* _retval) = 0;
+
+  /**
+     * Returns OK if aJSContext and target have the same "origin"
+     * (scheme, host, and port).
+     */
+  /* [noscript] void checkSameOrigin (in JSContextPtr aJSContext,
+                                      in nsIURI aTargetURI); */
+  NS_IMETHOD CheckSameOrigin(JSContext* aJSContext, nsIURI* aTargetURI) = 0;
+
+  /**
+     * Returns OK if aSourceURI and target have the same "origin"
+     * (scheme, host, and port).
+     * ReportError flag suppresses error reports for functions that
+     * don't need reporting.
+     */
+  /* void checkSameOriginURI (in nsIURI aSourceURI, in nsIURI aTargetURI,
+                              in boolean reportError); */
+  NS_SCRIPTABLE NS_IMETHOD CheckSameOriginURI(nsIURI* aSourceURI,
+                                              nsIURI* aTargetURI,
+                                              PRBool reportError) = 0;
+
+  /**
+     * Returns the principal of the global object of the given context, or null
+     * if no global or no principal.
+     */
+  /* [noscript] nsIPrincipal getPrincipalFromContext (in JSContextPtr cx); */
+  NS_IMETHOD GetPrincipalFromContext(JSContext* cx,
+                                     nsIPrincipal** _retval) = 0;
+
+  /**
+     * Get the principal for the given channel.  This will typically be the
+     * channel owner if there is one, and the codebase principal for the
+     * channel's URI otherwise.  aChannel must not be null.
+     */
+  /* nsIPrincipal getChannelPrincipal (in nsIChannel aChannel); */
+  NS_SCRIPTABLE NS_IMETHOD GetChannelPrincipal(nsIChannel* aChannel,
+                                               nsIPrincipal** _retval) = 0;
+
+  /**
+     * Check whether a given principal is a system principal.  This allows us
+     * to avoid handing back the system principal to script while allowing
+     * script to check whether a given principal is system.
+     */
+  /* boolean isSystemPrincipal (in nsIPrincipal aPrincipal); */
+  NS_SCRIPTABLE NS_IMETHOD IsSystemPrincipal(nsIPrincipal* aPrincipal,
+                                             PRBool* _retval) = 0;
+
+  /**
+     * Same as getSubjectPrincipal(), only faster. cx must *never* be
+     * passed null, and it must be the context on the top of the
+     * context stack. Does *not* reference count the returned
+     * principal.
+     */
+  /* [noscript, notxpcom] nsIPrincipal getCxSubjectPrincipal (
+         in JSContextPtr cx); */
+  NS_IMETHOD_(nsIPrincipal *) GetCxSubjectPrincipal(JSContext* cx) = 0;
+
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptSecurityManager_FF30,
+                              NS_ISCRIPTSECURITYMANAGER_IID_FF30);
+
+ASSOCIATE_IID(NS_ISCRIPTSECURITYMANAGER_IID_STR_FF30,
+              nsIScriptSecurityManager_FF30);
+
+
+#define NS_ISCRIPTSECURITYMANAGER_IID_STR_FF35 \
+    "f8e350b9-9f31-451a-8c8f-d10fea26b780"
+
+#define NS_ISCRIPTSECURITYMANAGER_IID_FF35 \
+  {0xf8e350b9, 0x9f31, 0x451a, \
+    { 0x8c, 0x8f, 0xd1, 0x0f, 0xea, 0x26, 0xb7, 0x80 }}
+
+#ifndef NS_OUTPARAM
+#define NS_OUTPARAM
+#endif
+
+/**
+ * WARNING!! The JEP needs to call GetSubjectPrincipal()
+ * to support JavaScript-to-Java LiveConnect.  So every change to the
+ * nsIScriptSecurityManager interface (big enough to change its IID) also
+ * breaks JavaScript-to-Java LiveConnect on mac.
+ *
+ * If you REALLY have to change this interface, please mark your bug as
+ * blocking bug 293973.
+ */
+class NS_NO_VTABLE NS_SCRIPTABLE nsIScriptSecurityManager_FF35 :
+    public nsIXPCSecurityManager_FF30 {
+ public:
+
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTSECURITYMANAGER_IID_FF35)
+
+  /**
+     * Checks whether the running script is allowed to access aProperty.
+     */
+  /* [noscript] void checkPropertyAccess (in JSContextPtr aJSContext,
+                                          in JSObjectPtr aJSObject,
+                                          in string aClassName,
+                                          in JSVal aProperty,
+                                          in PRUint32 aAction); */
+  NS_IMETHOD CheckPropertyAccess(JSContext* aJSContext, JSObject* aJSObject,
+                                 const char* aClassName, jsval aProperty,
+                                 PRUint32 aAction) = 0;
+
+  /**
+     * Checks whether the running script is allowed to connect to aTargetURI
+     */
+  /* [noscript] void checkConnect (in JSContextPtr aJSContext,
+                                   in nsIURI aTargetURI, in string aClassName,
+                                   in string aProperty); */
+  NS_IMETHOD CheckConnect(JSContext* aJSContext, nsIURI* aTargetURI,
+                          const char* aClassName, const char* aProperty) = 0;
+
+  /**
+     * Check that the script currently running in context "cx" can load "uri".
+     *
+     * Will return error code NS_ERROR_DOM_BAD_URI if the load request
+     * should be denied.
+     *
+     * @param cx the JSContext of the script causing the load
+     * @param uri the URI that is being loaded
+     */
+  /* [noscript] void checkLoadURIFromScript (in JSContextPtr cx,
+                                             in nsIURI uri); */
+  NS_IMETHOD CheckLoadURIFromScript(JSContext* cx, nsIURI* uri) = 0;
+
+  /**
+     * Default CheckLoadURI permissions
+     */
+  enum { STANDARD = 0U };
+
+  enum { LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT = 1U };
+
+  enum { ALLOW_CHROME = 2U };
+
+  enum { DISALLOW_INHERIT_PRINCIPAL = 4U };
+
+  enum { DISALLOW_SCRIPT_OR_DATA = 4U };
+
+  enum { DISALLOW_SCRIPT = 8U };
+
+  /**
+     * Check that content with principal aPrincipal can load "uri".
+     *
+     * Will return error code NS_ERROR_DOM_BAD_URI if the load request
+     * should be denied.
+     *
+     * @param aPrincipal the principal identifying the actor causing the load
+     * @param uri the URI that is being loaded
+     * @param flags the permission set, see above
+     */
+  /* void checkLoadURIWithPrincipal (in nsIPrincipal aPrincipal, in nsIURI uri,
+                                     in unsigned long flags); */
+  NS_SCRIPTABLE NS_IMETHOD CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
+                                                     nsIURI* uri,
+                                                     PRUint32 flags) = 0;
+
+  /**
+     * Check that content from "from" can load "uri".
+     *
+     * Will return error code NS_ERROR_DOM_BAD_URI if the load request
+     * should be denied.
+     *
+     * @param from the URI causing the load
+     * @param uri the URI that is being loaded
+     * @param flags the permission set, see above
+     *
+     * @deprecated Use checkLoadURIWithPrincipal instead of this function.
+     */
+  /* void checkLoadURI (in nsIURI from, in nsIURI uri,
+                        in unsigned long flags); */
+  NS_SCRIPTABLE NS_IMETHOD CheckLoadURI(nsIURI* from, nsIURI* uri,
+                                        PRUint32 flags) = 0;
+
+  /**
+     * Similar to checkLoadURIWithPrincipal but there are two differences:
+     *
+     * 1) The URI is a string, not a URI object.
+     * 2) This function assumes that the URI may still be subject to fixup (and
+     * hence will check whether fixed-up versions of the URI are allowed to
+     * load as well); if any of the versions of this URI is not allowed, this
+     * function will return error code NS_ERROR_DOM_BAD_URI.
+     */
+  /* void checkLoadURIStrWithPrincipal (in nsIPrincipal aPrincipal,
+                                        in AUTF8String uri,
+                                        in unsigned long flags); */
+  NS_SCRIPTABLE NS_IMETHOD CheckLoadURIStrWithPrincipal(
+      nsIPrincipal* aPrincipal, const nsACString& uri, PRUint32 flags) = 0;
+
+  /**
+     * Same as CheckLoadURI but takes string arguments for ease of use
+     * by scripts
+     *
+     * @deprecated Use checkLoadURIStrWithPrincipal instead of this function.
+     */
+  /* void checkLoadURIStr (in AUTF8String from, in AUTF8String uri,
+                           in unsigned long flags); */
+  NS_SCRIPTABLE NS_IMETHOD CheckLoadURIStr(const nsACString& from,
+                                           const nsACString & uri,
+                                           PRUint32 flags) = 0;
+
+  /**
+     * Check that the function 'funObj' is allowed to run on 'targetObj'
+     *
+     * Will return error code NS_ERROR_DOM_SECURITY_ERR if the function
+     * should not run
+     *
+     * @param cx The current active JavaScript context.
+     * @param funObj The function trying to run..
+     * @param targetObj The object the function will run on.
+     */
+  /* [noscript] void checkFunctionAccess (in JSContextPtr cx,
+                                          in voidPtr funObj,
+                                          in voidPtr targetObj); */
+  NS_IMETHOD CheckFunctionAccess(JSContext* cx, void* funObj,
+                                 void* targetObj) = 0;
+
+  /**
+     * Return true if content from the given principal is allowed to
+     * execute scripts.
+     */
+  /* [noscript] boolean canExecuteScripts (in JSContextPtr cx,
+                                           in nsIPrincipal principal); */
+  NS_IMETHOD CanExecuteScripts(JSContext* cx, nsIPrincipal* principal,
+                               PRBool* _retval NS_OUTPARAM) = 0;
+
+  /**
+     * Return the principal of the innermost frame of the currently
+     * executing script. Will return null if there is no script
+     * currently executing.
+     */
+  /* [noscript] nsIPrincipal getSubjectPrincipal (); */
+  NS_IMETHOD GetSubjectPrincipal(nsIPrincipal **_retval NS_OUTPARAM) = 0;
+
+  /**
+     * Return the all-powerful system principal.
+     */
+  /* [noscript] nsIPrincipal getSystemPrincipal (); */
+  NS_IMETHOD GetSystemPrincipal(nsIPrincipal **_retval NS_OUTPARAM) = 0;
+
+  /**
+     * Return a principal with the specified certificate fingerprint, subject
+     * name (the full name or concatenated set of names of the entity
+     * represented by the certificate), pretty name, certificate, and
+     * codebase URI.  The certificate fingerprint and subject name MUST be
+     * nonempty; otherwise an error will be thrown.  Similarly, aCert must
+     * not be null.
+     */
+  /* [noscript] nsIPrincipal getCertificatePrincipal (
+         in AUTF8String aCertFingerprint, in AUTF8String aSubjectName,
+         in AUTF8String aPrettyName, in nsISupports aCert, in nsIURI aURI); */
+  NS_IMETHOD GetCertificatePrincipal(const nsACString& aCertFingerprint,
+                                     const nsACString& aSubjectName,
+                                     const nsACString& aPrettyName,
+                                     nsISupports* aCert, nsIURI *aURI,
+                                     nsIPrincipal** _retval NS_OUTPARAM) = 0;
+
+  /**
+     * Return a principal that has the same origin as aURI.
+     */
+  /* nsIPrincipal getCodebasePrincipal (in nsIURI aURI); */
+  NS_SCRIPTABLE NS_IMETHOD GetCodebasePrincipal(nsIURI* aURI,
+      nsIPrincipal** _retval NS_OUTPARAM) = 0;
+
+  /**
+     * Request that 'capability' can be enabled by scripts or applets
+     * running with 'principal'. Will prompt user if
+     * necessary. Returns nsIPrincipal::ENABLE_GRANTED or
+     * nsIPrincipal::ENABLE_DENIED based on user's choice.
+     */
+  /* [noscript] short requestCapability (in nsIPrincipal principal,
+                                         in string capability); */
+  NS_IMETHOD RequestCapability(nsIPrincipal* principal, const char* capability,
+                               PRInt16* _retval NS_OUTPARAM) = 0;
+
+  /**
+     * Return true if the currently executing script has 'capability' enabled.
+     */
+  /* boolean isCapabilityEnabled (in string capability); */
+  NS_SCRIPTABLE NS_IMETHOD IsCapabilityEnabled(const char* capability,
+                                               PRBool* _retval NS_OUTPARAM) = 0;
+
+  /**
+     * Enable 'capability' in the innermost frame of the currently executing
+     * script.
+     */
+  /* void enableCapability (in string capability); */
+  NS_SCRIPTABLE NS_IMETHOD EnableCapability(const char* capability) = 0;
+
+  /**
+     * Remove 'capability' from the innermost frame of the currently
+     * executing script. Any setting of 'capability' from enclosing
+     * frames thus comes into effect.
+     */
+  /* void revertCapability (in string capability); */
+  NS_SCRIPTABLE NS_IMETHOD RevertCapability(const char* capability) = 0;
+
+  /**
+     * Disable 'capability' in the innermost frame of the currently executing
+     * script.
+     */
+  /* void disableCapability (in string capability); */
+  NS_SCRIPTABLE NS_IMETHOD DisableCapability(const char* capability) = 0;
+
+  /**
+     * Allow 'certificateID' to enable 'capability.' Can only be performed
+     * by code signed by the system certificate.
+     */
+  /* void setCanEnableCapability (in AUTF8String certificateFingerprint,
+                                  in string capability, in short canEnable); */
+  NS_SCRIPTABLE NS_IMETHOD SetCanEnableCapability(
+      const nsACString& certificateFingerprint, const char *capability,
+      PRInt16 canEnable) = 0;
+
+  /**
+     * Return the principal of the specified object in the specified context.
+     */
+  /* [noscript] nsIPrincipal getObjectPrincipal (in JSContextPtr cx,
+                                                 in JSObjectPtr obj); */
+  NS_IMETHOD GetObjectPrincipal(JSContext* cx, JSObject* obj,
+                                nsIPrincipal** _retval NS_OUTPARAM) = 0;
+
+  /**
+     * Returns true if the principal of the currently running script is the
+     * system principal, false otherwise.
+     */
+  /* [noscript] boolean subjectPrincipalIsSystem (); */
+  NS_IMETHOD SubjectPrincipalIsSystem(PRBool* _retval NS_OUTPARAM) = 0;
+
+  /**
+     * Returns OK if aJSContext and target have the same "origin"
+     * (scheme, host, and port).
+     */
+  /* [noscript] void checkSameOrigin (in JSContextPtr aJSContext,
+                                      in nsIURI aTargetURI); */
+  NS_IMETHOD CheckSameOrigin(JSContext* aJSContext, nsIURI* aTargetURI) = 0;
+
+  /**
+     * Returns OK if aSourceURI and target have the same "origin"
+     * (scheme, host, and port).
+     * ReportError flag suppresses error reports for functions that
+     * don't need reporting.
+     */
+  /* void checkSameOriginURI (in nsIURI aSourceURI, in nsIURI aTargetURI,
+                              in boolean reportError); */
+  NS_SCRIPTABLE NS_IMETHOD CheckSameOriginURI(nsIURI* aSourceURI,
+                                              nsIURI* aTargetURI,
+                                              PRBool reportError) = 0;
+
+  /**
+     * Returns the principal of the global object of the given context, or null
+     * if no global or no principal.
+     */
+  /* [noscript] nsIPrincipal getPrincipalFromContext (in JSContextPtr cx); */
+  NS_IMETHOD GetPrincipalFromContext(JSContext* cx,
+                                     nsIPrincipal** _retval NS_OUTPARAM) = 0;
+
+  /**
+     * Get the principal for the given channel.  This will typically be the
+     * channel owner if there is one, and the codebase principal for the
+     * channel's URI otherwise.  aChannel must not be null.
+     */
+  /* nsIPrincipal getChannelPrincipal (in nsIChannel aChannel); */
+  NS_SCRIPTABLE NS_IMETHOD GetChannelPrincipal(nsIChannel* aChannel,
+      nsIPrincipal** _retval NS_OUTPARAM) = 0;
+
+  /**
+     * Check whether a given principal is a system principal.  This allows us
+     * to avoid handing back the system principal to script while allowing
+     * script to check whether a given principal is system.
+     */
+  /* boolean isSystemPrincipal (in nsIPrincipal aPrincipal); */
+  NS_SCRIPTABLE NS_IMETHOD IsSystemPrincipal(nsIPrincipal* aPrincipal,
+                                             PRBool* _retval NS_OUTPARAM) = 0;
+
+  /**
+     * Same as getSubjectPrincipal(), only faster. cx must *never* be
+     * passed null, and it must be the context on the top of the
+     * context stack. Does *not* reference count the returned
+     * principal.
+     */
+  /* [noscript, notxpcom] nsIPrincipal getCxSubjectPrincipal (
+         in JSContextPtr cx); */
+  NS_IMETHOD_(nsIPrincipal*) GetCxSubjectPrincipal(JSContext* cx) = 0;
+
+  /* [noscript, notxpcom] nsIPrincipal getCxSubjectPrincipalAndFrame (in JSContextPtr cx, out JSStackFramePtr fp); */
+  NS_IMETHOD_(nsIPrincipal*) GetCxSubjectPrincipalAndFrame(JSContext* cx,
+      JSStackFrame** fp NS_OUTPARAM) = 0;
+
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptSecurityManager_FF35,
+                              NS_ISCRIPTSECURITYMANAGER_IID_FF35);
+
+ASSOCIATE_IID(NS_ISCRIPTSECURITYMANAGER_IID_STR_FF35,
+              nsIScriptSecurityManager_FF35);
+
+#endif  // CHROME_FRAME_SCRIPT_SECURITY_MANAGER_H_
diff --git a/chrome_frame/support.gyp b/chrome_frame/support.gyp
new file mode 100644
index 0000000..30f5889
--- /dev/null
+++ b/chrome_frame/support.gyp
@@ -0,0 +1,17 @@
+# Copyright (c) 2009 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.
+
+{
+  'targets': [
+    {
+      'target_name': 'support',
+      'type': 'none',
+      'dependencies': [
+        'chrome_frame.gyp:*',
+       ],
+    },
+  ],
+}
+
+# vim: shiftwidth=2:et:ai:tabstop=2
diff --git a/chrome_frame/sync_msg_reply_dispatcher.cc b/chrome_frame/sync_msg_reply_dispatcher.cc
new file mode 100644
index 0000000..b301698
--- /dev/null
+++ b/chrome_frame/sync_msg_reply_dispatcher.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/sync_msg_reply_dispatcher.h"
+
+#include "ipc/ipc_sync_message.h"
+
+void SyncMessageReplyDispatcher::Push(IPC::SyncMessage* msg, void* callback,
+                                      void* key) {
+  MessageSent pending(IPC::SyncMessage::GetMessageId(*msg),
+                      msg->type(), callback, key);
+  AutoLock lock(message_queue_lock_);
+  message_queue_.push_back(pending);
+}
+
+bool SyncMessageReplyDispatcher::HandleMessageType(const IPC::Message& msg,
+                                                   const MessageSent& origin) {
+  return false;
+}
+
+bool SyncMessageReplyDispatcher::OnMessageReceived(const IPC::Message& msg) {
+  MessageSent origin;
+  if (!Pop(msg, &origin)) {
+    return false;
+  }
+
+  // No callback e.g. no return values and/or don't care
+  if (origin.callback == NULL)
+    return true;
+
+  return HandleMessageType(msg, origin);
+}
+
+void SyncMessageReplyDispatcher::Cancel(void* key) {
+  DCHECK(key != NULL);
+  AutoLock lock(message_queue_lock_);
+  PendingSyncMessageQueue::iterator it;
+  for (it = message_queue_.begin(); it != message_queue_.end(); ++it) {
+    if (it->key == key) {
+      it->key = NULL;
+    }
+  }
+}
+
+bool SyncMessageReplyDispatcher::Pop(const IPC::Message& msg, MessageSent* t) {
+  if (!msg.is_reply())
+    return false;
+
+  int id = IPC::SyncMessage::GetMessageId(msg);
+  AutoLock lock(message_queue_lock_);
+  PendingSyncMessageQueue::iterator it;
+  for (it = message_queue_.begin(); it != message_queue_.end(); ++it) {
+    if (it->id == id) {
+      *t = *it;
+      message_queue_.erase(it);
+      return true;
+    }
+  }
+  return false;
+}
diff --git a/chrome_frame/sync_msg_reply_dispatcher.h b/chrome_frame/sync_msg_reply_dispatcher.h
new file mode 100644
index 0000000..05e5162
--- /dev/null
+++ b/chrome_frame/sync_msg_reply_dispatcher.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_SYNC_MSG_REPLY_DISPATCHER_H_
+#define CHROME_FRAME_SYNC_MSG_REPLY_DISPATCHER_H_
+
+#include <deque>
+
+#include "base/lock.h"
+#include "ipc/ipc_channel_proxy.h"
+
+// Base class used to allow synchronous IPC messages to be sent and
+// received in an asynchronous manner. To use this class add it as a filter to
+// your IPC channel using ChannelProxy::AddFilter(). From then on, before
+// sending a synchronous message, call SyncMessageReplyDispatcher::Push() with
+// a callback and a key. This class will then handle the message response and
+// will call the callback when it is received.
+//
+// This class is intended to be extended by classes implementing
+// HandleMessageType with delegation for the messages they expect to receive in
+// cases where you care about the return values of synchronous messages.
+//
+// Sample usage pattern:
+//
+// // Add handling for desired message types.
+// class SyncMessageReplyDispatcherImpl : public SyncMessageReplyDispatcher {
+//   virtual bool HandleMessageType(const IPC::Message& msg,
+//                                  const MessageSent& origin) {
+//    switch (origin.type) {
+//      case AutomationMsg_CreateExternalTab::ID:
+//        InvokeCallback<Tuple3<HWND, HWND, int> >(msg, origin);
+//        break;
+//     [HANDLING FOR OTHER EXPECTED MESSAGE TYPES]
+//   }
+// }
+//
+// // Add the filter
+// IPC::SyncChannel channel_;
+// channel_.AddFilter(new SyncMessageReplyDispatcherImpl());
+//
+// sync_->Push(msg, NewCallback(this, CallbackMethod, this);
+// channel_->ChannelProxy::Send(msg);
+//
+class SyncMessageReplyDispatcher : public IPC::ChannelProxy::MessageFilter {
+ public:
+  SyncMessageReplyDispatcher() {}
+  void Push(IPC::SyncMessage* msg, void* callback, void* key);
+  void Cancel(void* key);
+
+ protected:
+  struct MessageSent {
+    MessageSent() {}
+    MessageSent(int id, uint16 type, void* callback, void* key)
+      : id(id), callback(callback), type(type), key(key) {}
+    int id;
+    void* callback;
+    void* key;
+    uint16 type;
+  };
+
+  typedef std::deque<MessageSent> PendingSyncMessageQueue;
+
+  bool Pop(const IPC::Message& msg, MessageSent* t);
+  virtual bool OnMessageReceived(const IPC::Message& msg);
+
+  // Child classes must implement a handler for the message types they are
+  // interested in handling responses for. If you don't care about the replies
+  // to any of the sync messages you are handling, then you don't have to
+  // implement this.
+  virtual bool HandleMessageType(const IPC::Message& msg,
+                                 const MessageSent& origin);
+
+  template <typename T>
+  void InvokeCallback(const IPC::Message& msg, const MessageSent& origin) {
+    // Ensure we delete the callback
+    scoped_ptr<CallbackRunner<T> > c(
+        reinterpret_cast<CallbackRunner<T>*>(origin.callback));
+    if (origin.key == NULL) {  // Canceled
+      return;
+    }
+
+    T tmp;  // Acts as "initializer" for output parameters.
+    IPC::ParamDeserializer<T> deserializer(tmp);
+    if (deserializer.MessageReplyDeserializer::SerializeOutputParameters(msg)) {
+      c->RunWithParams(deserializer.out_);
+    } else {
+      // TODO(stoyan): How to handle errors?
+    }
+  }
+
+  PendingSyncMessageQueue message_queue_;
+  Lock message_queue_lock_;
+};
+
+#endif  // CHROME_FRAME_SYNC_MSG_REPLY_DISPATCHER_H_
diff --git a/chrome_frame/test/ChromeTab_UnitTests.vcproj b/chrome_frame/test/ChromeTab_UnitTests.vcproj
new file mode 100644
index 0000000..9f4369b
--- /dev/null
+++ b/chrome_frame/test/ChromeTab_UnitTests.vcproj
@@ -0,0 +1,273 @@
+<?xml version="1.0" encoding="Windows-1252"?>

+<VisualStudioProject

+	ProjectType="Visual C++"

+	Version="8.00"

+	Name="chrometab_unittests"

+	ProjectGUID="{BA08FE92-567D-4411-B344-17ADAECA2B5A}"

+	RootNamespace="ChromeTab_UnitTests"

+	Keyword="Win32Proj"

+	>

+	<Platforms>

+		<Platform

+			Name="Win32"

+		/>

+	</Platforms>

+	<ToolFiles>

+	</ToolFiles>

+	<Configurations>

+		<Configuration

+			Name="Debug|Win32"

+			OutputDirectory="..\..\chrome\$(ConfigurationName)"

+			IntermediateDirectory="$(ConfigurationName)"

+			ConfigurationType="1"

+			InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\libxslt\build\using_libxslt.vsprops;..\..\testing\using_gtest.vsprops;$(SolutionDir)..\chrome\third_party\wtl\using_wtl.vsprops;.\chrometab_unittests.vsprops"

+			>

+			<Tool

+				Name="VCPreBuildEventTool"

+			/>

+			<Tool

+				Name="VCCustomBuildTool"

+			/>

+			<Tool

+				Name="VCXMLDataGeneratorTool"

+			/>

+			<Tool

+				Name="VCWebServiceProxyGeneratorTool"

+			/>

+			<Tool

+				Name="VCMIDLTool"

+			/>

+			<Tool

+				Name="VCCLCompilerTool"

+				Optimization="0"

+				PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"

+				MinimalRebuild="true"

+				BasicRuntimeChecks="3"

+				RuntimeLibrary="1"

+				UsePrecompiledHeader="0"

+				WarningLevel="3"

+				Detect64BitPortabilityProblems="false"

+				DebugInformationFormat="4"

+			/>

+			<Tool

+				Name="VCManagedResourceCompilerTool"

+			/>

+			<Tool

+				Name="VCResourceCompilerTool"

+			/>

+			<Tool

+				Name="VCPreLinkEventTool"

+			/>

+			<Tool

+				Name="VCLinkerTool"

+				LinkIncremental="2"

+				GenerateDebugInformation="true"

+				SubSystem="1"

+				TargetMachine="1"

+			/>

+			<Tool

+				Name="VCALinkTool"

+			/>

+			<Tool

+				Name="VCManifestTool"

+			/>

+			<Tool

+				Name="VCXDCMakeTool"

+			/>

+			<Tool

+				Name="VCBscMakeTool"

+			/>

+			<Tool

+				Name="VCFxCopTool"

+			/>

+			<Tool

+				Name="VCAppVerifierTool"

+			/>

+			<Tool

+				Name="VCPostBuildEventTool"

+			/>

+		</Configuration>

+		<Configuration

+			Name="Release|Win32"

+			OutputDirectory="..\..\chrome\$(ConfigurationName)"

+			IntermediateDirectory="$(ConfigurationName)"

+			ConfigurationType="1"

+			InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\libxslt\build\using_libxslt.vsprops;..\..\testing\using_gtest.vsprops;$(SolutionDir)..\chrome\third_party\wtl\using_wtl.vsprops;$(SolutionDir)..\build\release.vsprops;.\chrometab_unittests.vsprops"

+			UseOfATL="1"

+			ATLMinimizesCRunTimeLibraryUsage="false"

+			WholeProgramOptimization="1"

+			>

+			<Tool

+				Name="VCPreBuildEventTool"

+			/>

+			<Tool

+				Name="VCCustomBuildTool"

+			/>

+			<Tool

+				Name="VCXMLDataGeneratorTool"

+			/>

+			<Tool

+				Name="VCWebServiceProxyGeneratorTool"

+			/>

+			<Tool

+				Name="VCMIDLTool"

+			/>

+			<Tool

+				Name="VCCLCompilerTool"

+				Optimization="2"

+				OmitFramePointers="false"

+				WholeProgramOptimization="false"

+				PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;"

+				StringPooling="true"

+				BasicRuntimeChecks="0"

+				RuntimeLibrary="0"

+				UsePrecompiledHeader="0"

+				WarningLevel="3"

+				Detect64BitPortabilityProblems="false"

+				DebugInformationFormat="3"

+			/>

+			<Tool

+				Name="VCManagedResourceCompilerTool"

+			/>

+			<Tool

+				Name="VCResourceCompilerTool"

+			/>

+			<Tool

+				Name="VCPreLinkEventTool"

+			/>

+			<Tool

+				Name="VCLinkerTool"

+				LinkIncremental="1"

+				AdditionalLibraryDirectories="$(ConfigurationName)\lib;"

+				IgnoreDefaultLibraryNames=""

+				GenerateDebugInformation="true"

+				SubSystem="1"

+				OptimizeReferences="2"

+				EnableCOMDATFolding="2"

+				OptimizeForWindows98="1"

+				TargetMachine="1"

+			/>

+			<Tool

+				Name="VCALinkTool"

+			/>

+			<Tool

+				Name="VCManifestTool"

+			/>

+			<Tool

+				Name="VCXDCMakeTool"

+			/>

+			<Tool

+				Name="VCBscMakeTool"

+			/>

+			<Tool

+				Name="VCFxCopTool"

+			/>

+			<Tool

+				Name="VCAppVerifierTool"

+			/>

+			<Tool

+				Name="VCPostBuildEventTool"

+			/>

+		</Configuration>

+	</Configurations>

+	<References>

+	</References>

+	<Files>

+		<Filter

+			Name="Source Files"

+			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"

+			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"

+			>

+			<Filter

+				Name="Common"

+				>

+				<File

+					RelativePath=".\cf_test_utils.cc"

+					>

+				</File>

+				<File

+					RelativePath=".\cf_test_utils.h"

+					>

+				</File>

+				<File

+					RelativePath=".\chrometab_unittests.h"

+					>

+				</File>

+				<File

+					RelativePath=".\run_all_unittests.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\..\base\test_suite.h"

+					>

+				</File>

+				<File

+					RelativePath="..\test_utils.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\test_utils.h"

+					>

+				</File>

+			</Filter>

+			<Filter

+				Name="Tests"

+				>

+				<File

+					RelativePath="..\chrome_frame_automation.cc"

+					>

+				</File>

+				<File

+					RelativePath=".\chrometab_unittests.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\com_message_event.cc"

+					>

+				</File>

+				<File

+					RelativePath=".\com_message_event_unittest.cc"

+					>

+				</File>

+				<File

+					RelativePath=".\function_stub_unittest.cc"

+					>

+				</File>

+				<File

+					RelativePath=".\html_util_unittests.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\html_utils.cc"

+					>

+				</File>

+				<File

+					RelativePath=".\icu_stubs_unittests.cc"

+					>

+				</File>

+				<File

+					RelativePath=".\util_unittests.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\utils.cc"

+					>

+				</File>

+			</Filter>

+		</Filter>

+		<Filter

+			Name="Header Files"

+			Filter="h;hpp;hxx;hm;inl;inc;xsd"

+			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"

+			>

+		</Filter>

+		<Filter

+			Name="Resource Files"

+			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"

+			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"

+			>

+		</Filter>

+	</Files>

+	<Globals>

+	</Globals>

+</VisualStudioProject>

diff --git a/chrome_frame/test/chrome_frame_automation_mock.cc b/chrome_frame/test/chrome_frame_automation_mock.cc
new file mode 100644
index 0000000..d900176
--- /dev/null
+++ b/chrome_frame/test/chrome_frame_automation_mock.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2006-2009 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.
+
+#include "chrome_frame/test/chrome_frame_automation_mock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const int kLongWaitTimeout = 25 * 1000;
+const int kShortWaitTimeout = 5 * 1000;
+
+TEST(ChromeFrame, Launch) {
+  MessageLoopForUI loop;
+  AutomationMockLaunch mock_launch(&loop, kLongWaitTimeout);
+
+  mock_launch.Navigate("about:blank");
+  loop.Run(NULL);
+  EXPECT_EQ(true, mock_launch.launch_result());
+}
+
+TEST(ChromeFrame, Navigate) {
+  MessageLoopForUI loop;
+  AutomationMockNavigate mock_navigate(&loop, kLongWaitTimeout);
+
+  mock_navigate.NavigateRelativeFile(L"postmessage_basic_frame.html");
+  loop.Run(NULL);
+  EXPECT_EQ(true, mock_navigate.navigation_result());
+}
+
+TEST(ChromeFrame, PostMessage) {
+  MessageLoopForUI loop;
+  AutomationMockPostMessage mock_postmessage(&loop, kLongWaitTimeout);
+
+  mock_postmessage.NavigateRelativeFile(L"postmessage_basic_frame.html");
+  loop.Run(NULL);
+  EXPECT_EQ(true, mock_postmessage.postmessage_result());
+}
+
+TEST(ChromeFrame, RequestStart) {
+  MessageLoopForUI loop;
+  AutomationMockHostNetworkRequestStart mock_request_start(&loop,
+                                                           kLongWaitTimeout);
+
+  mock_request_start.NavigateRelative(L"postmessage_basic_frame.html");
+  loop.Run(NULL);
+  EXPECT_EQ(true, mock_request_start.request_start_result());
+}
+
diff --git a/chrome_frame/test/chrome_frame_automation_mock.h b/chrome_frame/test/chrome_frame_automation_mock.h
new file mode 100644
index 0000000..4c3fe7b
--- /dev/null
+++ b/chrome_frame/test/chrome_frame_automation_mock.h
@@ -0,0 +1,208 @@
+// Copyright (c) 2006-2009 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.
+#ifndef CHROME_FRAME_TEST_CHROME_FRAME_AUTOMATION_MOCK_H_
+#define CHROME_FRAME_TEST_CHROME_FRAME_AUTOMATION_MOCK_H_
+
+#include <string>
+
+#include "base/file_path.h"
+#include "base/path_service.h"
+#include "chrome_frame/chrome_frame_automation.h"
+#include "chrome_frame/chrome_frame_plugin.h"
+#include "chrome_frame/test/http_server.h"
+
+template <typename T>
+class AutomationMockDelegate
+    : public CWindowImpl<T>,
+      public ChromeFramePlugin<T> {
+ public:
+  AutomationMockDelegate(MessageLoop* caller_message_loop,
+      int launch_timeout, bool perform_version_check,
+      const std::wstring& profile_name,
+      const std::wstring& extra_chrome_arguments, bool incognito)
+      : caller_message_loop_(caller_message_loop), is_connected_(false) {
+    test_server_.SetUp();
+    automation_client_.reset(new ChromeFrameAutomationClient);
+    automation_client_->Initialize(this, launch_timeout, perform_version_check,
+        profile_name, extra_chrome_arguments, incognito);
+  }
+  ~AutomationMockDelegate() {
+    if (automation_client_.get()) {
+      automation_client_->Uninitialize();
+      automation_client_.reset();
+    }
+    if (IsWindow())
+      DestroyWindow();
+
+    test_server_.TearDown();
+  }
+
+  // Navigate external tab to the specified url through automation
+  bool Navigate(const std::string& url) {
+    url_ = GURL(url);
+    return automation_client_->InitiateNavigation(url);
+  }
+
+  // Navigate the external to a 'file://' url for unit test files
+  bool NavigateRelativeFile(const std::wstring& file) {
+    FilePath cf_source_path;
+    PathService::Get(base::DIR_SOURCE_ROOT, &cf_source_path);
+    std::wstring file_url(L"file://");
+    file_url.append(cf_source_path.Append(
+        FILE_PATH_LITERAL("chrome_frame")).Append(
+            FILE_PATH_LITERAL("test")).Append(
+                FILE_PATH_LITERAL("data")).Append(file).value());
+    return Navigate(WideToUTF8(file_url));
+  }
+
+  bool NavigateRelative(const std::wstring& relative_url) {
+    return Navigate(test_server_.Resolve(relative_url.c_str()).spec());
+  }
+
+  virtual void OnAutomationServerReady() {
+    if (automation_client_.get()) {
+      Create(NULL, 0, NULL, WS_OVERLAPPEDWINDOW);
+      DCHECK(IsWindow());
+      is_connected_ = true;
+    } else {
+      NOTREACHED();
+    }
+  }
+
+  virtual void OnAutomationServerLaunchFailed() {
+    QuitMessageLoop();
+  }
+
+  virtual void OnLoad(int tab_handle, const GURL& url) {
+    if (url_ == url) {
+      navigation_result_ = true;
+    } else {
+      QuitMessageLoop();
+    }
+  }
+
+  virtual void OnLoadFailed(int error_code, const std::string& url) {
+    QuitMessageLoop();
+  }
+
+  ChromeFrameAutomationClient* automation() {
+    return automation_client_.get();
+  }
+  const GURL& url() const {
+    return url_;
+  }
+  bool is_connected() const {
+    return is_connected_;
+  }
+  bool navigation_result() const {
+    return navigation_result_;
+  }
+
+  BEGIN_MSG_MAP(AutomationMockDelegate)
+  END_MSG_MAP()
+
+ protected:
+  void QuitMessageLoop() {
+    // Quit on the caller message loop has to be called on the caller
+    // thread.
+    caller_message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask);
+  }
+
+ private:
+  ChromeFrameHTTPServer test_server_;
+  MessageLoop* caller_message_loop_;
+  GURL url_;
+  bool is_connected_;
+  bool navigation_result_;
+};
+
+class AutomationMockLaunch
+    : public AutomationMockDelegate<AutomationMockLaunch> {
+ public:
+  typedef AutomationMockDelegate<AutomationMockLaunch> Base;
+  AutomationMockLaunch(MessageLoop* caller_message_loop,
+                       int launch_timeout)
+      : Base(caller_message_loop, launch_timeout, true, L"", L"", false) {
+  }
+  virtual void OnAutomationServerReady() {
+    Base::OnAutomationServerReady();
+    QuitMessageLoop();
+  }
+  bool launch_result() const {
+    return is_connected();
+  }
+};
+
+class AutomationMockNavigate
+    : public AutomationMockDelegate<AutomationMockNavigate> {
+ public:
+  typedef AutomationMockDelegate<AutomationMockNavigate> Base;
+  AutomationMockNavigate(MessageLoop* caller_message_loop,
+                         int launch_timeout)
+      : Base(caller_message_loop, launch_timeout, true, L"", L"", false) {
+  }
+  virtual void OnLoad(int tab_handle, const GURL& url) {
+    Base::OnLoad(tab_handle, url);
+    QuitMessageLoop();
+  }
+};
+
+class AutomationMockPostMessage
+    : public AutomationMockDelegate<AutomationMockPostMessage> {
+ public:
+  typedef AutomationMockDelegate<AutomationMockPostMessage> Base;
+  AutomationMockPostMessage(MessageLoop* caller_message_loop,
+                            int launch_timeout)
+      : Base(caller_message_loop, launch_timeout, true, L"", L"", false),
+        postmessage_result_(false) {}
+  bool postmessage_result() const {
+    return postmessage_result_;
+  }
+  virtual void OnLoad(int tab_handle, const GURL& url) {
+    Base::OnLoad(tab_handle, url);
+    if (navigation_result()) {
+      automation()->ForwardMessageFromExternalHost("Test", "null", "*");
+    }
+  }
+  virtual void OnMessageFromChromeFrame(int tab_handle,
+                                        const std::string& message,
+                                        const std::string& origin,
+                                        const std::string& target) {
+    postmessage_result_ = true;
+    QuitMessageLoop();
+  }
+ private:
+  bool postmessage_result_;
+};
+
+class AutomationMockHostNetworkRequestStart
+    : public AutomationMockDelegate<AutomationMockHostNetworkRequestStart> {
+ public:
+  typedef AutomationMockDelegate<AutomationMockHostNetworkRequestStart> Base;
+  AutomationMockHostNetworkRequestStart(MessageLoop* caller_message_loop,
+      int launch_timeout)
+      : Base(caller_message_loop, launch_timeout, true, L"", L"", false),
+        request_start_result_(false) {
+    if (automation()) {
+      automation()->set_use_chrome_network(false);
+    }
+  }
+  bool request_start_result() const {
+    return request_start_result_;
+  }
+  virtual void OnRequestStart(int tab_handle, int request_id,
+                              const IPC::AutomationURLRequest& request) {
+    request_start_result_ = true;
+    QuitMessageLoop();
+  }
+  virtual void OnLoad(int tab_handle, const GURL& url) {
+    Base::OnLoad(tab_handle, url);
+  }
+ private:
+  bool request_start_result_;
+};
+
+
+#endif  // CHROME_FRAME_TEST_CHROME_FRAME_AUTOMATION_MOCK_H_
+
diff --git a/chrome_frame/test/chrome_frame_test_utils.cc b/chrome_frame/test/chrome_frame_test_utils.cc
new file mode 100644
index 0000000..a75d7914
--- /dev/null
+++ b/chrome_frame/test/chrome_frame_test_utils.cc
@@ -0,0 +1,416 @@
+// Copyright (c) 2006-2008 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.
+
+#include "chrome_frame/test/chrome_frame_test_utils.h"
+
+#include <atlbase.h>
+#include <atlwin.h>
+#include <iepmapi.h>
+
+#include "base/registry.h"   // to find IE and firefox
+#include "base/scoped_handle.h"
+#include "base/scoped_comptr_win.h"
+#include "base/string_util.h"
+#include "base/win_util.h"
+#include "chrome/common/chrome_switches.h"
+
+namespace chrome_frame_test {
+
+const wchar_t kIEImageName[] = L"iexplore.exe";
+const wchar_t kIEBrokerImageName[] = L"ieuser.exe";
+const wchar_t kFirefoxImageName[] = L"firefox.exe";
+const wchar_t kOperaImageName[] = L"opera.exe";
+const wchar_t kSafariImageName[] = L"safari.exe";
+const wchar_t kChromeImageName[] = L"chrome.exe";
+
+bool IsTopLevelWindow(HWND window) {
+  long style = GetWindowLong(window, GWL_STYLE);  // NOLINT
+  if (!(style & WS_CHILD))
+    return true;
+
+  HWND parent = GetParent(window);
+  if (!parent)
+    return true;
+
+  if (parent == GetDesktopWindow())
+    return true;
+
+  return false;
+}
+
+// Callback function for EnumThreadWindows.
+BOOL CALLBACK CloseWindowsThreadCallback(HWND hwnd, LPARAM param) {
+  int& count = *reinterpret_cast<int*>(param);
+  if (IsWindowVisible(hwnd)) {
+    if (IsWindowEnabled(hwnd)) {
+      DWORD results = 0;
+      if (!::SendMessageTimeout(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0, SMTO_BLOCK,
+                                10000, &results)) {
+        DLOG(WARNING) << "Window hung: " << StringPrintf(L"%08X", hwnd);
+      }
+      count++;
+    } else {
+      DLOG(WARNING) << "Skipping disabled window: "
+                  << StringPrintf(L"%08X", hwnd);
+    }
+  }
+  return TRUE;  // continue enumeration
+}
+
+// Attempts to close all non-child, visible windows on the given thread.
+// The return value is the number of visible windows a close request was
+// sent to.
+int CloseVisibleTopLevelWindowsOnThread(DWORD thread_id) {
+  int window_close_attempts = 0;
+  EnumThreadWindows(thread_id, CloseWindowsThreadCallback,
+                    reinterpret_cast<LPARAM>(&window_close_attempts));
+  return window_close_attempts;
+}
+
+// Enumerates the threads of a process and attempts to close visible non-child
+// windows on all threads of the process.
+// The return value is the number of visible windows a close request was
+// sent to.
+int CloseVisibleWindowsOnAllThreads(HANDLE process) {
+  DWORD process_id = ::GetProcessId(process);
+  if (process_id == 0) {
+    NOTREACHED();
+    return 0;
+  }
+
+  ScopedHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0));
+  if (!snapshot.IsValid()) {
+    NOTREACHED();
+    return 0;
+  }
+
+  int window_close_attempts = 0;
+  THREADENTRY32 te = { sizeof(THREADENTRY32) };
+  if (Thread32First(snapshot, &te)) {
+    do {
+      if (RTL_CONTAINS_FIELD(&te, te.dwSize, th32OwnerProcessID) &&
+          te.th32OwnerProcessID == process_id) {
+        window_close_attempts +=
+            CloseVisibleTopLevelWindowsOnThread(te.th32ThreadID);
+      }
+      te.dwSize = sizeof(te);
+    } while (Thread32Next(snapshot, &te));
+  }
+
+  return window_close_attempts;
+}
+
+class ForegroundHelperWindow : public CWindowImpl<ForegroundHelperWindow> {
+ public:
+BEGIN_MSG_MAP(ForegroundHelperWindow)
+  MESSAGE_HANDLER(WM_HOTKEY, OnHotKey)
+END_MSG_MAP()
+
+  HRESULT SetForeground(HWND window) {
+    DCHECK(::IsWindow(window));
+    if (NULL == Create(NULL, NULL, NULL, WS_POPUP))
+      return AtlHresultFromLastError();
+
+    static const int hotkey_id = 0x0000baba;
+
+    SetWindowLongPtr(GWLP_USERDATA, reinterpret_cast<ULONG_PTR>(window));
+    RegisterHotKey(m_hWnd, hotkey_id, 0, VK_F22);
+
+    MSG msg = {0};
+    PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
+
+    INPUT hotkey = {0};
+    hotkey.type = INPUT_KEYBOARD;
+    hotkey.ki.wVk =  VK_F22;
+    SendInput(1, &hotkey, sizeof(hotkey));
+
+    while (GetMessage(&msg, NULL, 0, 0)) {
+      TranslateMessage(&msg);
+      DispatchMessage(&msg);
+      if (WM_HOTKEY == msg.message)
+        break;
+    }
+
+    UnregisterHotKey(m_hWnd, hotkey_id);
+    DestroyWindow();
+
+    return S_OK;
+  }
+
+  LRESULT OnHotKey(UINT msg, WPARAM wp, LPARAM lp, BOOL& handled) {  // NOLINT
+    HWND window = reinterpret_cast<HWND>(GetWindowLongPtr(GWLP_USERDATA));
+    SetForegroundWindow(window);
+    return 1;
+  }
+};
+
+bool ForceSetForegroundWindow(HWND window) {
+  if (GetForegroundWindow() == window)
+    return true;
+  ForegroundHelperWindow foreground_helper_window;
+  HRESULT hr = foreground_helper_window.SetForeground(window);
+  return SUCCEEDED(hr);
+}
+
+struct PidAndWindow {
+  base::ProcessId pid;
+  HWND hwnd;
+};
+
+BOOL CALLBACK FindWindowInProcessCallback(HWND hwnd, LPARAM param) {
+  PidAndWindow* paw = reinterpret_cast<PidAndWindow*>(param);
+  base::ProcessId pid;
+  GetWindowThreadProcessId(hwnd, &pid);
+  if (pid == paw->pid && IsWindowVisible(hwnd)) {
+    paw->hwnd = hwnd;
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+bool EnsureProcessInForeground(base::ProcessId process_id) {
+  HWND hwnd = GetForegroundWindow();
+  base::ProcessId current_foreground_pid = 0;
+  DWORD active_thread_id = GetWindowThreadProcessId(hwnd,
+      &current_foreground_pid);
+  if (current_foreground_pid == process_id)
+    return true;
+
+  PidAndWindow paw = { process_id };
+  EnumWindows(FindWindowInProcessCallback, reinterpret_cast<LPARAM>(&paw));
+  if (!IsWindow(paw.hwnd)) {
+    DLOG(ERROR) << "failed to find process window";
+    return false;
+  }
+
+  bool ret = ForceSetForegroundWindow(paw.hwnd);
+  DLOG_IF(ERROR, !ret) << "ForceSetForegroundWindow: " << ret;
+
+  return ret;
+}
+
+// Iterates through all the characters in the string and simulates
+// keyboard input.  The input goes to the currently active application.
+bool SendString(const wchar_t* string) {
+  DCHECK(string != NULL);
+
+  INPUT input[2] = {0};
+  input[0].type = INPUT_KEYBOARD;
+  input[0].ki.dwFlags = KEYEVENTF_UNICODE;  // to avoid shift, etc.
+  input[1] = input[0];
+  input[1].ki.dwFlags |= KEYEVENTF_KEYUP;
+
+  for (const wchar_t* p = string; *p; p++) {
+    input[0].ki.wScan = input[1].ki.wScan = *p;
+    SendInput(2, input, sizeof(INPUT));
+  }
+
+  return true;
+}
+
+void SendVirtualKey(int16 key) {
+  INPUT input = { INPUT_KEYBOARD };
+  input.ki.wVk = key;
+  SendInput(1, &input, sizeof(input));
+  input.ki.dwFlags = KEYEVENTF_KEYUP;
+  SendInput(1, &input, sizeof(input));
+}
+
+void SendChar(char c) {
+  SendVirtualKey(VkKeyScanA(c));
+}
+
+void SendString(const char* s) {
+  while (*s) {
+    SendChar(*s);
+    s++;
+  }
+}
+
+// Sends a keystroke to the currently active application with optional
+// modifiers set.
+bool SendMnemonic(WORD mnemonic_char, bool shift_pressed, bool control_pressed,
+                  bool alt_pressed) {
+  INPUT special_keys[3] = {0};
+  for (int index = 0; index < arraysize(special_keys); ++index) {
+    special_keys[index].type = INPUT_KEYBOARD;
+    special_keys[index].ki.dwFlags = 0;
+  }
+
+  int num_special_keys = 0;
+  if (shift_pressed)  {
+    special_keys[num_special_keys].ki.wVk = VK_SHIFT;
+    num_special_keys++;
+  }
+
+  if (control_pressed)  {
+    special_keys[num_special_keys].ki.wVk = VK_CONTROL;
+    num_special_keys++;
+  }
+
+  if (alt_pressed)  {
+    special_keys[num_special_keys].ki.wVk = VK_MENU;
+    num_special_keys++;
+  }
+
+  // Depress the modifiers.
+  SendInput(num_special_keys, special_keys, sizeof(INPUT));
+
+  Sleep(100);
+
+  INPUT mnemonic = {0};
+  mnemonic.type = INPUT_KEYBOARD;
+  mnemonic.ki.wVk = mnemonic_char;
+
+  // Depress and release the mnemonic.
+  SendInput(1, &mnemonic, sizeof(INPUT));
+  Sleep(100);
+
+  mnemonic.ki.dwFlags |= KEYEVENTF_KEYUP;
+  SendInput(1, &mnemonic, sizeof(INPUT));
+  Sleep(100);
+
+  // Now release the modifiers.
+  for (int index = 0;  index < num_special_keys; index++) {
+    special_keys[index].ki.dwFlags |= KEYEVENTF_KEYUP;
+  }
+
+  SendInput(num_special_keys, special_keys, sizeof(INPUT));
+  Sleep(100);
+
+  return true;
+}
+
+std::wstring GetExecutableAppPath(const std::wstring& file) {
+  std::wstring kAppPathsKey =
+      L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\";
+
+  std::wstring app_path;
+  RegKey key(HKEY_LOCAL_MACHINE, (kAppPathsKey + file).c_str());
+  if (key.Handle()) {
+    key.ReadValue(NULL, &app_path);
+  }
+
+  return app_path;
+}
+
+std::wstring FormatCommandForApp(const std::wstring& exe_name,
+                                 const std::wstring& argument) {
+  std::wstring reg_path(StringPrintf(L"Applications\\%ls\\shell\\open\\command",
+                                     exe_name.c_str()));
+  RegKey key(HKEY_CLASSES_ROOT, reg_path.c_str());
+
+  std::wstring command;
+  if (key.Handle()) {
+    key.ReadValue(NULL, &command);
+    int found = command.find(L"%1");
+    if (found >= 0) {
+      command.replace(found, 2, argument);
+    }
+  }
+  return command;
+}
+
+base::ProcessHandle LaunchExecutable(const std::wstring& executable,
+                                     const std::wstring& argument) {
+  base::ProcessHandle process = NULL;
+  std::wstring path = GetExecutableAppPath(executable);
+  if (path.empty()) {
+    path = FormatCommandForApp(executable, argument);
+    if (path.empty()) {
+      DLOG(ERROR) << "Failed to find executable: " << executable;
+    } else {
+      CommandLine cmdline(L"");
+      cmdline.ParseFromString(path);
+      base::LaunchApp(cmdline, false, false, &process);
+    }
+  } else {
+    CommandLine cmdline(path);
+    cmdline.AppendLooseValue(argument);
+    base::LaunchApp(cmdline, false, false, &process);
+  }
+  return process;
+}
+
+base::ProcessHandle LaunchFirefox(const std::wstring& url) {
+  return LaunchExecutable(kFirefoxImageName, url);
+}
+
+base::ProcessHandle LaunchSafari(const std::wstring& url) {
+  return LaunchExecutable(kSafariImageName, url);
+}
+
+base::ProcessHandle LaunchChrome(const std::wstring& url) {
+  return LaunchExecutable(kChromeImageName,
+      StringPrintf(L"--%ls ", switches::kNoFirstRun) + url);
+}
+
+base::ProcessHandle LaunchOpera(const std::wstring& url) {
+  // NOTE: For Opera tests to work it must be configured to start up with
+  // a blank page.  There is an command line switch, -nosession, that's supposed
+  // to avoid opening up the previous session, but that switch is not working.
+  // TODO(tommi): Include a special ini file (opera6.ini) for opera and launch
+  //  with our required settings.  This file is by default stored here:
+  // "%USERPROFILE%\Application Data\Opera\Opera\profile\opera6.ini"
+  return LaunchExecutable(kOperaImageName, url);
+}
+
+base::ProcessHandle LaunchIEOnVista(const std::wstring& url) {
+  typedef HRESULT (WINAPI* IELaunchURLPtr)(
+      const wchar_t* url,
+      PROCESS_INFORMATION *pi,
+      VOID *info);
+
+  IELaunchURLPtr launch;
+  PROCESS_INFORMATION pi = {0};
+  IELAUNCHURLINFO  info = {sizeof info, 0};
+  HMODULE h = LoadLibrary(L"ieframe.dll");
+  if (!h)
+    return NULL;
+  launch = reinterpret_cast<IELaunchURLPtr>(GetProcAddress(h, "IELaunchURL"));
+  HRESULT hr = launch(url.c_str(), &pi, &info);
+  FreeLibrary(h);
+  if (SUCCEEDED(hr))
+    CloseHandle(pi.hThread);
+  return pi.hProcess;
+}
+
+base::ProcessHandle LaunchIE(const std::wstring& url) {
+  if (win_util::GetWinVersion() >= win_util::WINVERSION_VISTA) {
+    return LaunchIEOnVista(url);
+  } else {
+    return LaunchExecutable(kIEImageName, url);
+  }
+}
+
+int CloseAllIEWindows() {
+  int ret = 0;
+
+  ScopedComPtr<IShellWindows> windows;
+  HRESULT hr = ::CoCreateInstance(__uuidof(ShellWindows), NULL, CLSCTX_ALL,
+      IID_IShellWindows, reinterpret_cast<void**>(windows.Receive()));
+  DCHECK(SUCCEEDED(hr));
+
+  if (SUCCEEDED(hr)) {
+    long count = 0;  // NOLINT
+    windows->get_Count(&count);
+    VARIANT i = { VT_I4 };
+    for (i.lVal = 0; i.lVal < count; ++i.lVal) {
+      ScopedComPtr<IDispatch> folder;
+      windows->Item(i, folder.Receive());
+      if (folder != NULL) {
+        ScopedComPtr<IWebBrowser2> browser;
+        if (SUCCEEDED(browser.QueryFrom(folder))) {
+          browser->Quit();
+          ++ret;
+        }
+      }
+    }
+  }
+
+  return ret;
+}
+
+}  // namespace chrome_frame_test
diff --git a/chrome_frame/test/chrome_frame_test_utils.h b/chrome_frame/test/chrome_frame_test_utils.h
new file mode 100644
index 0000000..95e0c9b
--- /dev/null
+++ b/chrome_frame/test/chrome_frame_test_utils.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_TEST_CHROME_FRAME_TEST_UTILS_H_
+#define CHROME_FRAME_TEST_CHROME_FRAME_TEST_UTILS_H_
+
+#include <windows.h>
+
+#include "base/basictypes.h"
+#include "base/process_util.h"
+
+namespace chrome_frame_test {
+
+bool IsTopLevelWindow(HWND window);
+int CloseVisibleWindowsOnAllThreads(HANDLE process);
+bool ForceSetForegroundWindow(HWND window);
+bool EnsureProcessInForeground(base::ProcessId process_id);
+
+// Iterates through all the characters in the string and simulates
+// keyboard input.  The input goes to the currently active application.
+bool SendString(const wchar_t* s);
+
+// Sends a virtual key such as VK_TAB, VK_RETURN or a character that has been
+// translated to a virtual key.
+void SendVirtualKey(int16 key);
+
+// Translates a single char to a virtual key and calls SendVirtualKey.
+void SendChar(char c);
+
+// Sends an ascii string, char by char (calls SendChar for each).
+void SendString(const char* s);
+
+// Sends a keystroke to the currently active application with optional
+// modifiers set.
+bool SendMnemonic(WORD mnemonic_char, bool shift_pressed, bool control_pressed,
+                  bool alt_pressed);
+
+base::ProcessHandle LaunchFirefox(const std::wstring& url);
+base::ProcessHandle LaunchOpera(const std::wstring& url);
+base::ProcessHandle LaunchIE(const std::wstring& url);
+base::ProcessHandle LaunchSafari(const std::wstring& url);
+base::ProcessHandle LaunchChrome(const std::wstring& url);
+
+// Attempts to close all open IE windows.
+// The return value is the number of windows closed.
+// @note: this function requires COM to be initialized on the calling thread.
+// Since the caller might be running in either MTA or STA, the function does
+// not perform this initialization itself.
+int CloseAllIEWindows();
+
+extern const wchar_t kIEImageName[];
+extern const wchar_t kIEBrokerImageName[];
+extern const wchar_t kFirefoxImageName[];
+extern const wchar_t kOperaImageName[];
+extern const wchar_t kSafariImageName[];
+extern const wchar_t kChromeImageName[];
+
+}  // namespace chrome_frame_test
+
+#endif  // CHROME_FRAME_CHROMETAB_UNITTESTS_CF_TEST_UTILS_H_
diff --git a/chrome_frame/test/chrome_frame_unittests.cc b/chrome_frame/test/chrome_frame_unittests.cc
new file mode 100644
index 0000000..20826b1
--- /dev/null
+++ b/chrome_frame/test/chrome_frame_unittests.cc
@@ -0,0 +1,1510 @@
+// Copyright (c) 2006-2008 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.
+#include <windows.h>
+#include <stdarg.h>
+
+// IShellWindows includes.  Unfortunately we can't keep these in
+// alphabetic order since exdisp will bark if some interfaces aren't fully
+// defined.
+#include <mshtml.h>
+#include <exdisp.h>
+
+#include "base/basictypes.h"
+#include "base/file_version_info.h"
+#include "base/file_util.h"
+#include "base/scoped_bstr_win.h"
+#include "base/scoped_comptr_win.h"
+#include "base/scoped_variant_win.h"
+#include "base/sys_info.h"
+#include "gmock/gmock.h"
+#include "net/url_request/url_request_unittest.h"
+#include "chrome_frame/test/chrome_frame_unittests.h"
+#include "chrome_frame/chrome_frame_automation.h"
+#include "chrome_frame/chrome_frame_delegate.h"
+#include "chrome_frame/test/chrome_frame_test_utils.h"
+#include "chrome_frame/test/helper_gmock.h"
+#include "chrome_frame/test_utils.h"
+#include "chrome_frame/utils.h"
+#include "chrome_frame/vectored_handler-impl.h"
+#include "chrome/installer/util/install_util.h"
+#include "chrome/installer/util/helper.h"
+
+const wchar_t kDocRoot[] = L"chrome_frame\\test\\data";
+const int kLongWaitTimeout = 60 * 1000;
+const int kShortWaitTimeout = 25 * 1000;
+
+_ATL_FUNC_INFO WebBrowserEventSink::kNavigateErrorInfo = {

+  CC_STDCALL, VT_EMPTY, 5, {
+    VT_DISPATCH,
+    VT_VARIANT | VT_BYREF,
+    VT_VARIANT | VT_BYREF,
+    VT_VARIANT | VT_BYREF,
+    VT_BOOL | VT_BYREF,
+  }
+};
+
+_ATL_FUNC_INFO WebBrowserEventSink::kNavigateComplete2Info = {
+  CC_STDCALL, VT_EMPTY, 2, {
+    VT_DISPATCH,
+    VT_VARIANT | VT_BYREF
+  }
+};
+
+_ATL_FUNC_INFO WebBrowserEventSink::kBeforeNavigate2Info = {

+  CC_STDCALL, VT_EMPTY, 7, {
+    VT_DISPATCH,
+    VT_VARIANT | VT_BYREF,
+    VT_VARIANT | VT_BYREF,
+    VT_VARIANT | VT_BYREF,
+    VT_VARIANT | VT_BYREF,
+    VT_VARIANT | VT_BYREF,
+    VT_BOOL | VT_BYREF
+  }
+};
+
+
+
+void ChromeFrameTestWithWebServer::SetUp() {
+  server_.SetUp();
+  results_dir_ = server_.GetDataDir();
+  file_util::AppendToPath(&results_dir_, L"dump");
+}
+
+void ChromeFrameTestWithWebServer::TearDown() {
+  CloseBrowser();
+
+  // Web browsers tend to relaunch themselves in other processes, meaning the
+  // KillProcess stuff above might not have actually cleaned up all our browser
+  // instances, so make really sure browsers are dead.
+  base::KillProcesses(chrome_frame_test::kIEImageName, 0, NULL);
+  base::KillProcesses(chrome_frame_test::kIEBrokerImageName, 0, NULL);
+  base::KillProcesses(chrome_frame_test::kFirefoxImageName, 0, NULL);
+  base::KillProcesses(chrome_frame_test::kSafariImageName, 0, NULL);
+  base::KillProcesses(chrome_frame_test::kChromeImageName, 0, NULL);
+
+  server_.TearDown();
+}
+
+bool ChromeFrameTestWithWebServer::LaunchBrowser(BrowserKind browser,
+                                               const wchar_t* page) {
+  std::wstring url = UTF8ToWide(server_.Resolve(page).spec());
+  browser_ = browser;
+  if (browser == IE) {
+    browser_handle_.Set(chrome_frame_test::LaunchIE(url));
+  } else if (browser == FIREFOX) {
+    browser_handle_.Set(chrome_frame_test::LaunchFirefox(url));
+  } else if (browser == OPERA) {
+    browser_handle_.Set(chrome_frame_test::LaunchOpera(url));
+  } else if (browser == SAFARI) {
+    browser_handle_.Set(chrome_frame_test::LaunchSafari(url));
+  } else if (browser == CHROME) {
+    browser_handle_.Set(chrome_frame_test::LaunchChrome(url));
+  } else {
+    NOTREACHED();
+  }
+
+  return browser_handle_.IsValid();
+}
+
+void ChromeFrameTestWithWebServer::CloseBrowser() {
+  if (!browser_handle_.IsValid())
+    return;
+
+  int attempts = 0;
+  if (browser_ == IE) {
+    attempts = chrome_frame_test::CloseAllIEWindows();
+  } else {
+    attempts = chrome_frame_test::CloseVisibleWindowsOnAllThreads(
+                                                               browser_handle_);
+  }
+
+  if (attempts > 0) {
+    DWORD wait = ::WaitForSingleObject(browser_handle_, 20000);
+    if (wait == WAIT_OBJECT_0) {
+      browser_handle_.Close();
+    } else {
+      DLOG(ERROR) << "WaitForSingleObject returned " << wait;
+    }
+  } else {
+    DLOG(ERROR) << "No attempts to close browser windows";
+  }
+
+  if (browser_handle_.IsValid()) {
+    DWORD exit_code = 0;
+    if (!::GetExitCodeProcess(browser_handle_, &exit_code) ||
+        exit_code == STILL_ACTIVE) {
+      DLOG(ERROR) << L"Forcefully killing browser process. Exit:" << exit_code;
+      base::KillProcess(browser_handle_, 0, true);
+    }
+    browser_handle_.Close();
+  }
+}
+
+bool ChromeFrameTestWithWebServer::BringBrowserToTop() {
+  return chrome_frame_test::EnsureProcessInForeground(GetProcessId(
+                                                              browser_handle_));
+}
+
+bool ChromeFrameTestWithWebServer::WaitForTestToComplete(int milliseconds) {
+  return server_.WaitToFinish(milliseconds);
+}
+
+bool ChromeFrameTestWithWebServer::WaitForOnLoad(int milliseconds) {
+  DWORD start = ::GetTickCount();
+  std::string data;
+  while (!ReadResultFile(L"OnLoadEvent", &data) || data.length() == 0) {
+    DWORD now = ::GetTickCount();
+    if (start > now) {
+      // Very simple check for overflow. In that case we just restart the
+      // wait.
+      start = now;
+    } else if (static_cast<int>(now - start) > milliseconds) {
+      break;
+    }
+    Sleep(100);
+  }
+
+  return data.compare("loaded") == 0;
+}
+
+bool ChromeFrameTestWithWebServer::ReadResultFile(const std::wstring& file_name,
+                                                std::string* data) {
+  std::wstring full_path = results_dir_;
+  file_util::AppendToPath(&full_path, file_name);
+  return file_util::ReadFileToString(full_path, data);
+}
+
+bool ChromeFrameTestWithWebServer::CheckResultFile(
+    const std::wstring& file_name, const std::string& expected_result) {
+  std::string data;
+  bool ret = ReadResultFile(file_name, &data);
+  if (ret)
+    ret = (data == expected_result);
+
+  if (!ret) {
+    DLOG(ERROR) << "Error text: " << (data.empty() ? "<empty>" : data.c_str());
+  }
+
+  return ret;
+}
+
+void ChromeFrameTestWithWebServer::SimpleBrowserTest(BrowserKind browser,
+    const wchar_t* page, const wchar_t* result_file_to_check) {
+  EXPECT_TRUE(LaunchBrowser(browser, page));
+  ASSERT_TRUE(WaitForTestToComplete(kLongWaitTimeout));
+  ASSERT_TRUE(CheckResultFile(result_file_to_check, "OK"));
+}
+
+void ChromeFrameTestWithWebServer::OptionalBrowserTest(BrowserKind browser,
+    const wchar_t* page, const wchar_t* result_file_to_check) {
+  if (!LaunchBrowser(browser, page)) {
+    DLOG(ERROR) << "Failed to launch browser " << ToString(browser);
+  } else {
+    ASSERT_TRUE(WaitForTestToComplete(kLongWaitTimeout));
+    ASSERT_TRUE(CheckResultFile(result_file_to_check, "OK"));
+  }
+}
+
+void ChromeFrameTestWithWebServer::VersionTest(BrowserKind browser,
+    const wchar_t* page, const wchar_t* result_file_to_check) {
+  std::wstring plugin_path;
+  PathService::Get(base::DIR_MODULE, &plugin_path);
+  file_util::AppendToPath(&plugin_path, L"servers/npchrome_tab.dll");
+
+  static FileVersionInfo* version_info =
+      FileVersionInfo::CreateFileVersionInfo(plugin_path);
+
+  std::wstring version;
+  if (version_info)
+    version = version_info->product_version();
+
+  // If we can't find the npchrome_tab.dll in the src tree, we turn to
+  // the directory where chrome is installed.
+  if (!version_info) {
+    installer::Version* ver_system = InstallUtil::GetChromeVersion(true);
+    installer::Version* ver_user = InstallUtil::GetChromeVersion(false);
+    ASSERT_TRUE(ver_system || ver_user);
+
+    bool system_install = ver_system ? true : false;
+    std::wstring npchrome_path(installer::GetChromeInstallPath(system_install));
+    file_util::AppendToPath(&npchrome_path,
+        ver_system ? ver_system->GetString() : ver_user->GetString());
+    file_util::AppendToPath(&npchrome_path, L"npchrome_tab.dll");
+    version_info = FileVersionInfo::CreateFileVersionInfo(npchrome_path);
+    if (version_info)
+      version = version_info->product_version();
+  }
+
+  EXPECT_TRUE(version_info);
+  EXPECT_FALSE(version.empty());
+  EXPECT_TRUE(LaunchBrowser(browser, page));
+  ASSERT_TRUE(WaitForTestToComplete(kLongWaitTimeout));
+  ASSERT_TRUE(CheckResultFile(result_file_to_check, WideToUTF8(version)));
+}
+
+const wchar_t kPostMessageBasicPage[] = L"files/postmessage_basic_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_PostMessageBasic) {
+  SimpleBrowserTest(IE, kPostMessageBasicPage, L"PostMessage");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_PostMessageBasic) {
+  SimpleBrowserTest(FIREFOX, kPostMessageBasicPage, L"PostMessage");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_PostMessageBasic) {
+  OptionalBrowserTest(OPERA, kPostMessageBasicPage, L"PostMessage");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, FullTabIE_MIMEFilterBasic) {
+  const wchar_t kMIMEFilterBasicPage[] =
+      L"files/chrome_frame_mime_filter_test.html";
+
+  SimpleBrowserTest(IE, kMIMEFilterBasicPage, L"MIMEFilter");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_Resize) {
+  SimpleBrowserTest(IE, L"files/chrome_frame_resize.html", L"Resize");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_Resize) {
+  SimpleBrowserTest(FIREFOX, L"files/chrome_frame_resize.html", L"Resize");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_Resize) {
+  OptionalBrowserTest(OPERA, L"files/chrome_frame_resize.html", L"Resize");
+}
+
+const wchar_t kNavigateURLAbsolutePage[] =
+    L"files/navigateurl_absolute_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_NavigateURLAbsolute) {
+  SimpleBrowserTest(IE, kNavigateURLAbsolutePage, L"NavigateURL");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_NavigateURLAbsolute) {
+  SimpleBrowserTest(FIREFOX, kNavigateURLAbsolutePage, L"NavigateURL");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_NavigateURLAbsolute) {
+  OptionalBrowserTest(OPERA, kNavigateURLAbsolutePage, L"NavigateURL");
+}
+
+const wchar_t kNavigateURLRelativePage[] =
+    L"files/navigateurl_relative_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_NavigateURLRelative) {
+  SimpleBrowserTest(IE, kNavigateURLRelativePage, L"NavigateURL");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_NavigateURLRelative) {
+  SimpleBrowserTest(FIREFOX, kNavigateURLRelativePage, L"NavigateURL");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_NavigateURLRelative) {
+  OptionalBrowserTest(OPERA, kNavigateURLRelativePage, L"NavigateURL");
+}
+
+const wchar_t kNavigateSimpleObjectFocus[] = L"files/simple_object_focus.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_ObjectFocus) {
+  SimpleBrowserTest(FIREFOX, kNavigateSimpleObjectFocus, L"ObjectFocus");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_ObjectFocus) {
+  SimpleBrowserTest(IE, kNavigateSimpleObjectFocus, L"ObjectFocus");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_ObjectFocus) {
+  if (!LaunchBrowser(OPERA, kNavigateSimpleObjectFocus)) {
+    DLOG(ERROR) << "Failed to launch browser " << ToString(OPERA);
+  } else {
+    ASSERT_TRUE(WaitForOnLoad(kLongWaitTimeout));
+    BringBrowserToTop();
+    // Tab through a couple of times.  Once should be enough in theory
+    // but in practice activating the browser can take a few milliseconds more.
+    bool ok;
+    for (int i = 0;
+         i < 5 && (ok = CheckResultFile(L"ObjectFocus", "OK")) == false;
+         ++i) {
+      Sleep(300);
+      chrome_frame_test::SendMnemonic(VK_TAB, false, false, false);
+    }
+    ASSERT_TRUE(ok);
+  }
+}
+
+const wchar_t kiframeBasicPage[] = L"files/iframe_basic_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_iframeBasic) {
+  SimpleBrowserTest(IE, kiframeBasicPage, L"PostMessage");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_iframeBasic) {
+  SimpleBrowserTest(FIREFOX, kiframeBasicPage, L"PostMessage");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_iframeBasic) {
+  OptionalBrowserTest(OPERA, kiframeBasicPage, L"PostMessage");
+}
+
+const wchar_t kSrcPropertyTestPage[] = L"files/src_property_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_SrcProperty) {
+  SimpleBrowserTest(IE, kSrcPropertyTestPage, L"SrcProperty");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_SrcProperty) {
+  SimpleBrowserTest(FIREFOX, kSrcPropertyTestPage, L"SrcProperty");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_SrcProperty) {
+  OptionalBrowserTest(OPERA, kSrcPropertyTestPage, L"SrcProperty");
+}
+
+const wchar_t kCFInstanceBasicTestPage[] = L"files/CFInstance_basic_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceBasic) {
+  SimpleBrowserTest(IE, kCFInstanceBasicTestPage, L"CFInstanceBasic");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceBasic) {
+  SimpleBrowserTest(FIREFOX, kCFInstanceBasicTestPage, L"CFInstanceBasic");
+}
+
+const wchar_t kCFISingletonPage[] = L"files/CFInstance_singleton_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceSingleton) {
+  SimpleBrowserTest(IE, kCFISingletonPage, L"CFInstanceSingleton");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceSingleton) {
+  SimpleBrowserTest(FIREFOX, kCFISingletonPage, L"CFInstanceSingleton");
+}
+
+const wchar_t kCFIDelayPage[] = L"files/CFInstance_delay_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeIE_CFInstanceDelay) {
+  SimpleBrowserTest(IE, kCFIDelayPage, L"CFInstanceDelay");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeFF_CFInstanceDelay) {
+  SimpleBrowserTest(FIREFOX, kCFIDelayPage, L"CFInstanceDelay");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_CFInstanceDelay) {
+  OptionalBrowserTest(OPERA, kCFIDelayPage, L"CFInstanceDelay");
+}
+
+const wchar_t kCFIFallbackPage[] = L"files/CFInstance_fallback_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceFallback) {
+  SimpleBrowserTest(IE, kCFIFallbackPage, L"CFInstanceFallback");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceFallback) {
+  SimpleBrowserTest(FIREFOX, kCFIFallbackPage, L"CFInstanceFallback");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceFallback) {
+  OptionalBrowserTest(OPERA, kCFIFallbackPage, L"CFInstanceFallback");
+}
+
+const wchar_t kCFINoSrcPage[] = L"files/CFInstance_no_src_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceNoSrc) {
+  SimpleBrowserTest(IE, kCFINoSrcPage, L"CFInstanceNoSrc");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceNoSrc) {
+  SimpleBrowserTest(FIREFOX, kCFINoSrcPage, L"CFInstanceNoSrc");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceNoSrc) {
+  OptionalBrowserTest(OPERA, kCFINoSrcPage, L"CFInstanceNoSrc");
+}
+
+const wchar_t kCFIIfrOnLoadPage[] = L"files/CFInstance_iframe_onload_host.html";
+
+// disabled since it's unlikely that we care about this case
+TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeIE_CFInstanceIfrOnLoad) {
+  SimpleBrowserTest(IE, kCFIIfrOnLoadPage, L"CFInstanceIfrOnLoad");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceIfrOnLoad) {
+  SimpleBrowserTest(FIREFOX, kCFIIfrOnLoadPage, L"CFInstanceIfrOnLoad");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceIfrOnLoad) {
+  OptionalBrowserTest(OPERA, kCFIIfrOnLoadPage, L"CFInstanceIfrOnLoad");
+}
+
+const wchar_t kCFIZeroSizePage[] = L"files/CFInstance_zero_size_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceZeroSize) {
+  SimpleBrowserTest(IE, kCFIZeroSizePage, L"CFInstanceZeroSize");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceZeroSize) {
+  SimpleBrowserTest(FIREFOX, kCFIZeroSizePage, L"CFInstanceZeroSize");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceZeroSize) {
+  OptionalBrowserTest(OPERA, kCFIZeroSizePage, L"CFInstanceZeroSize");
+}
+
+const wchar_t kCFIIfrPostPage[] = L"files/CFInstance_iframe_post_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceIfrPost) {
+  SimpleBrowserTest(IE, kCFIIfrPostPage, L"CFInstanceIfrPost");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceIfrPost) {
+  SimpleBrowserTest(FIREFOX, kCFIIfrPostPage, L"CFInstanceIfrPost");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceIfrPost) {
+  OptionalBrowserTest(CHROME, kCFIIfrPostPage, L"CFInstanceIfrPost");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstanceIfrPost) {
+  OptionalBrowserTest(SAFARI, kCFIIfrPostPage, L"CFInstanceIfrPost");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceIfrPost) {
+  OptionalBrowserTest(OPERA, kCFIIfrPostPage, L"CFInstanceIfrPost");
+}
+
+const wchar_t kCFIPostPage[] = L"files/CFInstance_post_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstancePost) {
+  SimpleBrowserTest(IE, kCFIPostPage, L"CFInstancePost");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstancePost) {
+  SimpleBrowserTest(FIREFOX, kCFIPostPage, L"CFInstancePost");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstancePost) {
+  OptionalBrowserTest(CHROME, kCFIPostPage, L"CFInstancePost");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstancePost) {
+  OptionalBrowserTest(SAFARI, kCFIPostPage, L"CFInstancePost");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_CFInstancePost) {
+  OptionalBrowserTest(OPERA, kCFIPostPage, L"CFInstancePost");
+}
+
+const wchar_t kCFIRPCPage[] = L"files/CFInstance_rpc_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceRPC) {
+  SimpleBrowserTest(IE, kCFIRPCPage, L"CFInstanceRPC");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceRPC) {
+  SimpleBrowserTest(FIREFOX, kCFIRPCPage, L"CFInstanceRPC");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceRPC) {
+  OptionalBrowserTest(CHROME, kCFIRPCPage, L"CFInstanceRPC");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstanceRPC) {
+  OptionalBrowserTest(SAFARI, kCFIRPCPage, L"CFInstanceRPC");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_CFInstanceRPC) {
+  OptionalBrowserTest(OPERA, kCFIRPCPage, L"CFInstanceRPC");
+}
+
+const wchar_t kCFIRPCInternalPage[] =
+    L"files/CFInstance_rpc_internal_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceRPCInternal) {
+  SimpleBrowserTest(IE, kCFIRPCInternalPage, L"CFInstanceRPCInternal");
+}
+
+// Disabled: http://b/issue?id=2050201
+TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeFF_CFInstanceRPCInternal) {
+  SimpleBrowserTest(FIREFOX, kCFIRPCInternalPage, L"CFInstanceRPCInternal");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceRPCInternal) {
+  OptionalBrowserTest(CHROME, kCFIRPCInternalPage, L"CFInstanceRPCInternal");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstanceRPCInternal) {
+  OptionalBrowserTest(SAFARI, kCFIRPCInternalPage, L"CFInstanceRPCInternal");
+}
+
+const wchar_t kCFIDefaultCtorPage[] =
+    L"files/CFInstance_default_ctor_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceDefaultCtor) {
+  SimpleBrowserTest(IE, kCFIDefaultCtorPage, L"CFInstanceDefaultCtor");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceDefaultCtor) {
+  SimpleBrowserTest(FIREFOX, kCFIDefaultCtorPage, L"CFInstanceDefaultCtor");
+}
+
+// Class that mocks external call from VectoredHandlerT for testing purposes.
+class EMock : public VEHTraitsBase {
+ public:
+  static inline bool WriteDump(EXCEPTION_POINTERS* p) {
+    g_dump_made = true;
+    return true;
+  }
+
+  static inline void* Register(PVECTORED_EXCEPTION_HANDLER func,
+                               const void* module_start,
+                               const void* module_end) {
+    VEHTraitsBase::SetModule(module_start, module_end);
+    // Return some arbitrary number, expecting to get the same on Unregister()
+    return reinterpret_cast<void*>(4);
+  }
+
+  static inline ULONG Unregister(void* handle) {
+    EXPECT_EQ(handle, reinterpret_cast<void*>(4));
+    return 1;
+  }
+
+  static inline WORD RtlCaptureStackBackTrace(DWORD FramesToSkip,
+      DWORD FramesToCapture, void** BackTrace, DWORD* BackTraceHash) {
+    EXPECT_EQ(2, FramesToSkip);
+    EXPECT_LE(FramesToSkip + FramesToCapture,
+              VectoredHandlerBase::max_back_trace);
+    memcpy(BackTrace, g_stack, g_stack_entries * sizeof(BackTrace[0]));
+    return g_stack_entries;
+  }
+
+  static inline EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() {
+    return g_seh_chain;
+  }
+
+  // Test helpers
+
+  // Create fake SEH chain of random filters - with and without our module.
+  static void SetHaveSEHFilter() {
+    SetSEHChain(reinterpret_cast<const char*>(g_module_start) - 0x1000,
+                reinterpret_cast<const char*>(g_module_start) + 0x1000,
+                reinterpret_cast<const char*>(g_module_end) + 0x7127,
+                NULL);
+  }
+
+  static void SetNoSEHFilter() {
+    SetSEHChain(reinterpret_cast<const char*>(g_module_start) - 0x1000,
+                reinterpret_cast<const char*>(g_module_end) + 0x7127,
+                NULL);
+  }
+
+  // Create fake stack - with and without our module.
+  static void SetOnStack() {
+    SetStack(reinterpret_cast<const char*>(g_module_start) - 0x11283,
+             reinterpret_cast<const char*>(g_module_start) - 0x278361,
+             reinterpret_cast<const char*>(g_module_start) + 0x9171,
+             reinterpret_cast<const char*>(g_module_end) + 1231,
+             NULL);
+  }
+
+  static void SetNotOnStack() {
+    SetStack(reinterpret_cast<const char*>(g_module_start) - 0x11283,
+             reinterpret_cast<const char*>(g_module_start) - 0x278361,
+             reinterpret_cast<const char*>(g_module_end) + 1231,
+             NULL);
+  }
+
+  // Populate stack array
+  static void SetStack(const void* p, ...) {
+    va_list vl;
+    va_start(vl, p);
+    g_stack_entries = 0;
+    for (; p; ++g_stack_entries) {
+      CHECK(g_stack_entries < arraysize(g_stack));
+      g_stack[g_stack_entries] = p;
+      p = va_arg(vl, const void*);
+    }
+  }
+
+  static void SetSEHChain(const void* p, ...) {
+    va_list vl;
+    va_start(vl, p);
+    int i = 0;
+    for (; p; ++i) {
+      CHECK(i + 1 < arraysize(g_seh_chain));
+      g_seh_chain[i].Handler = const_cast<void*>(p);
+      g_seh_chain[i].Next = &g_seh_chain[i + 1];
+      p = va_arg(vl, const void*);
+    }
+
+    g_seh_chain[i].Next = EXCEPTION_CHAIN_END;
+  }
+
+  static EXCEPTION_REGISTRATION_RECORD g_seh_chain[25];
+  static const void* g_stack[VectoredHandlerBase::max_back_trace];
+  static WORD g_stack_entries;
+  static bool g_dump_made;
+};
+
+EXCEPTION_REGISTRATION_RECORD EMock::g_seh_chain[25];
+const void* EMock::g_stack[VectoredHandlerBase::max_back_trace];
+WORD EMock::g_stack_entries;
+bool EMock::g_dump_made;
+
+typedef VectoredHandlerT<EMock> VectoredHandlerMock;
+
+class ExPtrsHelper : public _EXCEPTION_POINTERS {
+ public:
+  ExPtrsHelper() {
+    ExceptionRecord = &er_;
+    ContextRecord = &ctx_;
+    ZeroMemory(&er_, sizeof(er_));
+    ZeroMemory(&ctx_, sizeof(ctx_));
+  }
+
+  void Set(DWORD code, void* address, DWORD flags) {
+    er_.ExceptionCode = code;
+    er_.ExceptionAddress = address;
+    er_.ExceptionFlags = flags;
+  }
+
+  EXCEPTION_RECORD er_;
+  CONTEXT ctx_;
+};
+
+
+TEST(ChromeFrame, ExceptionReport) {
+  char* s = reinterpret_cast<char*>(0x30000000);
+  char* e = s + 0x10000;
+  void* handler = VectoredHandlerMock::Register(s, e);
+  char* our_code = s + 0x1111;
+  char* not_our_code = s - 0x5555;
+  char* not_our_code2 = e + 0x5555;
+
+  ExPtrsHelper ex;
+  // Exception in our code, but we have SEH filter
+  ex.Set(STATUS_ACCESS_VIOLATION, our_code, 0);
+  EMock::SetHaveSEHFilter();
+  EMock::SetOnStack();
+  EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex));
+  EXPECT_EQ(1, VectoredHandlerMock::g_exceptions_seen);
+  EXPECT_FALSE(EMock::g_dump_made);
+
+  // RPC_E_DISCONNECTED (0x80010108) is "The object invoked has disconnected
+  // from its clients", shall not be caught since it's a warning only.
+  ex.Set(RPC_E_DISCONNECTED, our_code, 0);
+  EMock::SetHaveSEHFilter();
+  EMock::SetOnStack();
+  EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex));
+  EXPECT_EQ(1, VectoredHandlerMock::g_exceptions_seen);
+  EXPECT_FALSE(EMock::g_dump_made);
+
+
+  // Exception, not in our code, we do not have SEH and we are not in stack.
+  ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, not_our_code, 0);
+  EMock::SetNoSEHFilter();
+  EMock::SetNotOnStack();
+  EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex));
+  EXPECT_EQ(2, VectoredHandlerMock::g_exceptions_seen);
+  EXPECT_FALSE(EMock::g_dump_made);
+
+  // Exception, not in our code, no SEH, but we are on the stack.
+  ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, not_our_code2, 0);
+  EMock::SetNoSEHFilter();
+  EMock::SetOnStack();
+  EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex));
+  EXPECT_EQ(3, VectoredHandlerMock::g_exceptions_seen);
+  EXPECT_TRUE(EMock::g_dump_made);
+  EMock::g_dump_made = false;
+
+
+  // Exception, in our code, no SEH, not on stack (assume FPO screwed us)
+  ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, our_code, 0);
+  EMock::SetNoSEHFilter();
+  EMock::SetNotOnStack();
+  EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex));
+  EXPECT_EQ(4, VectoredHandlerMock::g_exceptions_seen);
+  EXPECT_TRUE(EMock::g_dump_made);
+  EMock::g_dump_made = false;
+
+  VectoredHandlerMock::Unregister();
+}
+
+const wchar_t kInitializeHiddenPage[] = L"files/initialize_hidden.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_InitializeHidden) {
+  SimpleBrowserTest(IE, kInitializeHiddenPage, L"InitializeHidden");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_InitializeHidden) {
+  SimpleBrowserTest(FIREFOX, kInitializeHiddenPage, L"InitializeHidden");
+}
+
+// Disabled due to a problem with Opera.
+// http://b/issue?id=1708275
+TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_InitializeHidden) {
+  OptionalBrowserTest(OPERA, kInitializeHiddenPage, L"InitializeHidden");
+}
+
+const wchar_t kInHeadPage[] = L"files/in_head.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_InHead) {
+  SimpleBrowserTest(FIREFOX, kInHeadPage, L"InHead");
+}
+
+const wchar_t kVersionPage[] = L"files/version.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_Version) {
+  VersionTest(IE, kVersionPage, L"version");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_Version) {
+  VersionTest(FIREFOX, kVersionPage, L"version");
+}
+
+const wchar_t kEventListenerPage[] = L"files/event_listener.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_EventListener) {
+  SimpleBrowserTest(IE, kEventListenerPage, L"EventListener");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_EventListener) {
+  SimpleBrowserTest(FIREFOX, kEventListenerPage, L"EventListener");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_EventListener) {
+  OptionalBrowserTest(OPERA, kEventListenerPage, L"EventListener");
+}
+
+const wchar_t kPrivilegedApisPage[] = L"files/privileged_apis_host.html";
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_PrivilegedApis) {
+  SimpleBrowserTest(IE, kPrivilegedApisPage, L"PrivilegedApis");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_PrivilegedApis) {
+  SimpleBrowserTest(FIREFOX, kPrivilegedApisPage, L"PrivilegedApis");
+}
+
+TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_PrivilegedApis) {
+  OptionalBrowserTest(OPERA, kPrivilegedApisPage, L"PrivilegedApis");
+}
+
+class ChromeFrameTestEnvironment: public testing::Environment {
+  public:
+    ~ChromeFrameTestEnvironment() {
+    }
+    
+    void SetUp() {
+      ScopedChromeFrameRegistrar::RegisterDefaults();
+    }
+
+    void TearDown() {
+    }
+};
+
+::testing::Environment* const chrome_frame_env =

+    ::testing::AddGlobalTestEnvironment(new ChromeFrameTestEnvironment);
+
+// TODO(stoyan): - Move everything below in separate file(s).
+struct LaunchDelegateMock : public ProxyFactory::LaunchDelegate {
+  MOCK_METHOD2(LaunchComplete, void(ChromeFrameAutomationProxy*,
+    AutomationLaunchResult));
+};
+
+TEST(ProxyFactoryTest, CreateDestroy) {
+  ProxyFactory f;
+  LaunchDelegateMock d;
+  EXPECT_CALL(d, LaunchComplete(testing::NotNull(), testing::_)).Times(1);
+  void* id = f.GetAutomationServer(0, L"Adam.N.Epilinter", L"", false, &d);
+  f.ReleaseAutomationServer(id);
+}
+
+TEST(ProxyFactoryTest, CreateSameProfile) {
+  ProxyFactory f;
+  LaunchDelegateMock d;
+  EXPECT_CALL(d, LaunchComplete(testing::NotNull(), testing::_)).Times(2);
+  void* i1 = f.GetAutomationServer(0, L"Dr. Gratiano Forbeson", L"", false, &d);
+  void* i2 = f.GetAutomationServer(0, L"Dr. Gratiano Forbeson", L"", false, &d);
+  EXPECT_EQ(i1, i2);
+  f.ReleaseAutomationServer(i2);
+  f.ReleaseAutomationServer(i1);
+}
+
+TEST(ProxyFactoryTest, CreateDifferentProfiles) {
+  ProxyFactory f;
+  LaunchDelegateMock d;
+  EXPECT_CALL(d, LaunchComplete(testing::NotNull(), testing::_)).Times(2);
+  void* i1 = f.GetAutomationServer(0, L"Adam.N.Epilinter", L"", false, &d);
+  void* i2 = f.GetAutomationServer(0, L"Dr. Gratiano Forbeson", L"", false, &d);
+  EXPECT_NE(i1, i2);
+  f.ReleaseAutomationServer(i2);
+  f.ReleaseAutomationServer(i1);
+}
+
+// ChromeFrameAutomationClient [CFAC] tests.
+struct MockCFDelegate : public ChromeFrameDelegateImpl {
+  MOCK_CONST_METHOD0(GetWindow, WindowType());
+  MOCK_METHOD1(GetBounds, void(RECT* bounds));
+  MOCK_METHOD0(GetDocumentUrl, std::string());
+  MOCK_METHOD2(ExecuteScript, bool(const std::string& script,
+                                   std::string* result));
+  MOCK_METHOD0(OnAutomationServerReady, void());
+  MOCK_METHOD2(OnAutomationServerLaunchFailed, void(
+      AutomationLaunchResult reason, const std::string& server_version));
+  // This remains in interface since we call it if Navigate()
+  // returns immediate error.
+  MOCK_METHOD2(OnLoadFailed, void(int error_code, const std::string& url));
+
+  // Do not mock this method. :) Use it as message demuxer and dispatcher
+  // to the following methods (which we mock)
+  // MOCK_METHOD1(OnMessageReceived, void(const IPC::Message&));
+
+
+  MOCK_METHOD2(OnNavigationStateChanged, void(int tab_handle, int flags));
+  MOCK_METHOD2(OnUpdateTargetUrl, void(int tab_handle,
+      const std::wstring& new_target_url));
+  MOCK_METHOD2(OnAcceleratorPressed, void(int tab_handle,
+      const MSG& accel_message));
+  MOCK_METHOD2(OnTabbedOut, void(int tab_handle, bool reverse));
+  MOCK_METHOD3(OnOpenURL, void(int tab_handle, const GURL& url,
+      int open_disposition));
+  MOCK_METHOD2(OnDidNavigate, void(int tab_handle,
+      const IPC::NavigationInfo& navigation_info));
+  MOCK_METHOD3(OnNavigationFailed, void(int tab_handle, int error_code,
+      const GURL& gurl));
+  MOCK_METHOD2(OnLoad, void(int tab_handle, const GURL& url));
+  MOCK_METHOD4(OnMessageFromChromeFrame, void(int tab_handle,
+      const std::string& message,
+      const std::string& origin,
+      const std::string& target));
+  MOCK_METHOD5(OnHandleContextMenu, void(int tab_handle, HANDLE menu_handle,
+      int x_pos, int y_pos, int align_flags));
+  MOCK_METHOD3(OnRequestStart, void(int tab_handle, int request_id,
+      const IPC::AutomationURLRequest& request));
+  MOCK_METHOD3(OnRequestRead, void(int tab_handle, int request_id,
+      int bytes_to_read));
+  MOCK_METHOD3(OnRequestEnd, void(int tab_handle, int request_id,
+      const URLRequestStatus& status));
+  MOCK_METHOD3(OnSetCookieAsync, void(int tab_handle, const GURL& url,
+      const std::string& cookie));
+
+  // Use for sending network responses
+  void SetAutomationSender(IPC::Message::Sender* automation) {
+    automation_ = automation;
+  }
+
+  // Set-expectation helpers
+  void SetOnNavigationStateChanged(int tab_handle) {
+    EXPECT_CALL(*this,
+        OnNavigationStateChanged(testing::Eq(tab_handle), testing::_))
+        .Times(testing::AnyNumber());
+  }
+
+  // Response sender helpers
+  void ReplyStarted(const IPC::AutomationURLResponse* response,
+                    int tab_handle, int request_id,
+                    const IPC::AutomationURLRequest& request) {
+    automation_->Send(new AutomationMsg_RequestStarted(0, tab_handle,
+        request_id, *response));
+  }
+
+  void ReplyData(const std::string* data, int tab_handle, int request_id,
+                 int bytes_to_read) {
+    automation_->Send(new AutomationMsg_RequestData(0, tab_handle,
+        request_id, *data));
+  }
+
+  void ReplyEOF(int tab_handle, int request_id) {
+    automation_->Send(new AutomationMsg_RequestEnd(0, tab_handle,
+        request_id, URLRequestStatus()));
+  }
+
+  void Reply404(int tab_handle, int request_id,
+                const IPC::AutomationURLRequest& request) {
+    const IPC::AutomationURLResponse notfound = {"", "HTTP/1.1 404\r\n\r\n"};
+    automation_->Send(new AutomationMsg_RequestStarted(0, tab_handle,
+        request_id, notfound));
+    automation_->Send(new AutomationMsg_RequestEnd(0, tab_handle,
+        request_id, URLRequestStatus()));
+  }
+
+  IPC::Message::Sender* automation_;
+};
+
+class MockProxyFactory : public ProxyFactory {
+ public:
+  MOCK_METHOD5(GetAutomationServer, void*(int, const std::wstring&,
+      const std::wstring& extra_argument, bool, ProxyFactory::LaunchDelegate*));
+  MOCK_METHOD1(ReleaseAutomationServer, bool(void* id));
+
+  MockProxyFactory() : thread_("mock factory worker") {
+    thread_.Start();
+    loop_ = thread_.message_loop();
+  }
+
+  // Fake implementation
+  void GetServerImpl(ChromeFrameAutomationProxy* pxy,
+                     AutomationLaunchResult result,
+                     int timeout,
+                     ProxyFactory::LaunchDelegate* d) {
+    Task* task = NewRunnableMethod(d,
+        &ProxyFactory::LaunchDelegate::LaunchComplete, pxy, result);
+    loop_->PostDelayedTask(FROM_HERE, task, timeout/2);
+  }
+
+  base::Thread thread_;
+  MessageLoop* loop_;
+};
+
+class MockAutomationProxy : public ChromeFrameAutomationProxy {
+ public:
+  MOCK_METHOD1(Send, bool(IPC::Message*));
+  MOCK_METHOD3(SendAsAsync, void(IPC::SyncMessage* msg, void* callback,
+                                 void* key));
+  MOCK_METHOD1(CancelAsync, void(void* key));
+  MOCK_METHOD1(CreateTabProxy, scoped_refptr<TabProxy>(int handle));
+  MOCK_METHOD0(server_version, std::string(void));
+  MOCK_METHOD1(SendProxyConfig, void(const std::string&));
+  MOCK_METHOD1(SetEnableExtensionAutomation, void(bool enable));
+
+  ~MockAutomationProxy() {}
+};
+
+struct MockAutomationMessageSender : public AutomationMessageSender {
+  MOCK_METHOD1(Send, bool(IPC::Message*));
+  MOCK_METHOD3(SendWithTimeout, bool(IPC::Message* , int , bool*));
+
+  void ForwardTo(MockAutomationProxy *p) {
+    proxy_ = p;
+    ON_CALL(*this, Send(testing::_))
+        .WillByDefault(testing::Invoke(proxy_, &MockAutomationProxy::Send));
+  }
+
+  MockAutomationProxy* proxy_;
+};
+
+template <> struct RunnableMethodTraits<ProxyFactory::LaunchDelegate> {
+  static void RetainCallee(ProxyFactory::LaunchDelegate* obj) {}
+  static void ReleaseCallee(ProxyFactory::LaunchDelegate* obj) {}
+};
+
+template <> struct RunnableMethodTraits<MockProxyFactory> {
+  static void RetainCallee(MockProxyFactory* obj) {}
+  static void ReleaseCallee(MockProxyFactory* obj) {}
+};
+
+template <> struct RunnableMethodTraits<ChromeFrameAutomationClient> {
+  static void RetainCallee(ChromeFrameAutomationClient* obj) {}
+  static void ReleaseCallee(ChromeFrameAutomationClient* obj) {}
+};
+
+// MessageLoopForUI wrapper that runs only for a limited time.
+// We need a UI message loop in the main thread.
+struct TimedMsgLoop {
+ public:
+  void RunFor(int seconds) {
+    loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask, 1000 * seconds);
+    loop_.MessageLoop::Run();
+  }
+
+  void Quit() {
+    loop_.PostTask(FROM_HERE, new MessageLoop::QuitTask);
+  }
+
+  MessageLoopForUI loop_;
+};
+
+template <> struct RunnableMethodTraits<TimedMsgLoop> {
+  static void RetainCallee(TimedMsgLoop* obj) {}
+  static void ReleaseCallee(TimedMsgLoop* obj) {}
+};
+
+// Saves typing. It's somewhat hard to create a wrapper around
+// testing::InvokeWithoutArgs since it returns a
+// non-public (testing::internal) type.
+#define QUIT_LOOP(loop) testing::InvokeWithoutArgs(TaskHolder(\
+    NewRunnableMethod(&loop, &TimedMsgLoop::Quit)))
+
+// We mock ChromeFrameDelegate only. The rest is with real AutomationProxy
+TEST(CFACWithChrome, CreateTooFast) {
+  MockCFDelegate cfd;
+  TimedMsgLoop loop;
+  int timeout = 0;  // Chrome cannot send Hello message so fast.
+  const std::wstring profile = L"Adam.N.Epilinter";
+
+  scoped_ptr<ChromeFrameAutomationClient> client;
+  client.reset(new ChromeFrameAutomationClient());
+
+  EXPECT_CALL(cfd, OnAutomationServerLaunchFailed(AUTOMATION_TIMEOUT,
+                                                  testing::_))
+      .Times(1)
+      .WillOnce(QUIT_LOOP(loop));
+
+  EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false));
+  loop.RunFor(10);
+  client->Uninitialize();
+}
+
+// This test may fail if Chrome take more that 10 seconds (timeout var) to
+// launch. In this case GMock shall print something like "unexpected call to
+// OnAutomationServerLaunchFailed". I'm yet to find out how to specify
+// that this is an unexpected call, and still to execute and action.
+TEST(CFACWithChrome, CreateNotSoFast) {
+  MockCFDelegate cfd;
+  TimedMsgLoop loop;
+  const std::wstring profile = L"Adam.N.Epilinter";
+  int timeout = 10000;
+
+  scoped_ptr<ChromeFrameAutomationClient> client;
+  client.reset(new ChromeFrameAutomationClient);
+
+  EXPECT_CALL(cfd, OnAutomationServerReady())
+      .Times(1)
+      .WillOnce(QUIT_LOOP(loop));
+
+  EXPECT_CALL(cfd, OnAutomationServerLaunchFailed(testing::_, testing::_))
+      .Times(0);
+
+  EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false));
+
+  loop.RunFor(11);
+  client->Uninitialize();
+  client.reset(NULL);
+}
+
+MATCHER_P(MsgType, msg_type, "IPC::Message::type()") {
+  const IPC::Message& m = arg;
+  return (m.type() == msg_type);
+}
+
+MATCHER_P(EqNavigationInfoUrl, url, "IPC::NavigationInfo matcher") {
+  if (url.is_valid() && url != arg.url)
+    return false;
+  // TODO: other members
+  return true;
+}
+
+TEST(CFACWithChrome, NavigateOk) {
+  MockCFDelegate cfd;
+  TimedMsgLoop loop;
+  const std::wstring profile = L"Adam.N.Epilinter";
+  const std::string url = "about:version";
+  int timeout = 10000;
+
+  scoped_ptr<ChromeFrameAutomationClient> client;
+  client.reset(new ChromeFrameAutomationClient);
+
+  EXPECT_CALL(cfd, OnAutomationServerReady())
+      .WillOnce(testing::InvokeWithoutArgs(TaskHolder(NewRunnableMethod(
+          client.get(), &ChromeFrameAutomationClient::InitiateNavigation,
+          url))));
+
+//  cfd.SetOnNavigationStateChanged();
+  EXPECT_CALL(cfd,
+      OnNavigationStateChanged(testing::_, testing::_))
+      .Times(testing::AnyNumber());
+
+  {
+    testing::InSequence s;
+
+    EXPECT_CALL(cfd, OnDidNavigate(testing::_, EqNavigationInfoUrl(GURL())))
+        .Times(1);
+
+    EXPECT_CALL(cfd, OnUpdateTargetUrl(testing::_, testing::_)).Times(1);
+
+    EXPECT_CALL(cfd, OnLoad(testing::_, testing::_))
+        .Times(1)
+        .WillOnce(QUIT_LOOP(loop));
+  }
+
+  EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false));
+  loop.RunFor(10);
+  client->Uninitialize();
+  client.reset(NULL);
+}
+
+// Bug: http://b/issue?id=2033644
+TEST(CFACWithChrome, DISABLED_NavigateFailed) {
+  MockCFDelegate cfd;
+  TimedMsgLoop loop;
+  const std::wstring profile = L"Adam.N.Epilinter";
+  const std::string url = "http://127.0.0.3:65412/";
+  int timeout = 10000;
+
+  scoped_ptr<ChromeFrameAutomationClient> client;
+  client.reset(new ChromeFrameAutomationClient);
+
+  EXPECT_CALL(cfd, OnAutomationServerReady())
+      .WillOnce(testing::InvokeWithoutArgs(TaskHolder(NewRunnableMethod(
+          client.get(), &ChromeFrameAutomationClient::InitiateNavigation,
+          url))));
+
+  EXPECT_CALL(cfd,
+    OnNavigationStateChanged(testing::_, testing::_))
+    .Times(testing::AnyNumber());
+
+  EXPECT_CALL(cfd, OnNavigationFailed(testing::_, testing::_, testing::_))
+      .Times(1);
+
+  EXPECT_CALL(cfd, OnUpdateTargetUrl(testing::_, testing::_))
+      .Times(testing::AnyNumber());
+
+  EXPECT_CALL(cfd, OnLoad(testing::_, testing::_))
+      .Times(0);
+
+  EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false));
+
+  loop.RunFor(10);
+  client->Uninitialize();
+  client.reset(NULL);
+}
+
+MATCHER_P(EqURLRequest, x, "IPC::AutomationURLRequest matcher") {
+  if (arg.url != x.url)
+    return false;
+  if (arg.method != x.method)
+    return false;
+  if (arg.referrer != x.referrer)
+    return false;
+  if (arg.extra_request_headers != x.extra_request_headers)
+    return false;
+  // TODO: uploaddata member
+  return true;
+}
+
+MATCHER_P(EqUrlGet, url, "Quick URL matcher for 'HTTP GET' request") {
+  if (arg.url != url)
+    return false;
+  if (arg.method != "GET")
+    return false;
+  return true;
+}
+
+TEST(CFACWithChrome, UseHostNetworkStack) {
+  MockCFDelegate cfd;
+  TimedMsgLoop loop;
+  const std::wstring profile = L"Adam.N.Epilinter";
+  const std::string url = "http://bongo.com";
+  int timeout = 10000;
+
+  scoped_ptr<ChromeFrameAutomationClient> client;
+  client.reset(new ChromeFrameAutomationClient);
+  client->set_use_chrome_network(false);
+  cfd.SetAutomationSender(client.get());
+
+  EXPECT_CALL(cfd, OnAutomationServerReady())
+      .WillOnce(testing::InvokeWithoutArgs(TaskHolder(NewRunnableMethod(
+          client.get(), &ChromeFrameAutomationClient::InitiateNavigation,
+          url))));
+
+  EXPECT_CALL(cfd, OnNavigationStateChanged(testing::_, testing::_))
+      .Times(testing::AnyNumber());
+
+  EXPECT_CALL(cfd, GetBounds(testing::_))
+      .Times(testing::AtMost(1));
+
+  EXPECT_CALL(cfd, OnUpdateTargetUrl(testing::_, testing::_))
+      .Times(testing::AnyNumber());
+
+  // Note slash appending to the url string, because of GURL inside chrome
+  const IPC::AutomationURLResponse found = {"", "HTTP/0.9 200\r\n\r\n\r\n", };
+
+  // Hard coded tab and request ids
+  static const int tab_id = 1;
+  int request_id = 2;
+
+  EXPECT_CALL(cfd, OnRequestStart(tab_id, request_id, EqUrlGet(url + '/')))
+      .Times(1)
+      .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::ReplyStarted,
+                                    &found)));
+
+  // Return some trivial page, that have a link to a "logo.gif" image
+  const std::string data = "<!DOCTYPE html><title>Hello</title>"
+                           "<img src=\"logo.gif\">";
+  EXPECT_CALL(cfd, OnRequestRead(tab_id, request_id, testing::Ge(0)))
+      .Times(2)
+      .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::ReplyData, &data)))
+      .WillOnce(testing::WithArgs<0, 1>(testing::Invoke(CBF(&cfd,
+                                                 &MockCFDelegate::ReplyEOF))));
+
+  EXPECT_CALL(cfd, OnDidNavigate(tab_id, EqNavigationInfoUrl(GURL(url))))
+      .Times(1);
+  EXPECT_CALL(cfd, OnLoad(tab_id, GURL(url)))
+      .Times(1);
+
+  // Expect request for logo.gif
+  request_id++;
+  EXPECT_CALL(cfd,
+      OnRequestStart(tab_id, request_id, EqUrlGet(url + "/logo.gif")))
+          .Times(1)
+          .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::Reply404)));
+
+  EXPECT_CALL(cfd, OnRequestRead(tab_id, request_id, testing::_))
+      .Times(testing::AtMost(1));
+
+  // Chrome makes a brave request for favicon.ico
+  request_id++;
+  EXPECT_CALL(cfd,
+      OnRequestStart(tab_id, request_id, EqUrlGet(url + "/favicon.ico")))
+          .Times(1)
+          .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::Reply404)));
+
+  EXPECT_CALL(cfd, OnRequestRead(tab_id, request_id, testing::_))
+      .Times(testing::AtMost(1));
+
+  bool incognito = true;
+  EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"",
+                                 incognito));
+
+  loop.RunFor(10);
+  client->Uninitialize();
+  client.reset(NULL);
+}
+
+
+// [CFAC] -- uses a ProxyFactory for creation of ChromeFrameAutomationProxy
+// -- uses ChromeFrameAutomationProxy
+// -- uses TabProxy obtained from ChromeFrameAutomationProxy
+// -- uses ChromeFrameDelegate as outgoing interface
+//
+// We mock ProxyFactory to return mock object (MockAutomationProxy) implementing
+// ChromeFrameAutomationProxy interface.
+// Since CFAC uses TabProxy for few calls and TabProxy is not easy mockable,
+// we create 'real' TabProxy but with fake AutomationSender (the one responsible
+// for sending messages over channel).
+// Additionally we have mock implementation ChromeFrameDelagate interface -
+// MockCFDelegate.
+
+// Test fixture, saves typing all of it's members.
+class CFACMockTest : public testing::Test {
+ public:
+  MockProxyFactory factory_;
+  MockCFDelegate   cfd_;
+  TimedMsgLoop loop_;
+  MockAutomationProxy proxy_;
+  scoped_ptr<AutomationHandleTracker> tracker_;
+  MockAutomationMessageSender dummy_sender_;
+  scoped_refptr<TabProxy> tab_;
+  scoped_ptr<ChromeFrameAutomationClient> client_;  // the victim of all tests
+
+  std::wstring profile_;
+  int timeout_;
+  void* id_;  // Automation server id we are going to return
+  int tab_handle_;   // Tab handle. Any non-zero value is Ok.
+
+  inline ChromeFrameAutomationProxy* get_proxy() {
+    return static_cast<ChromeFrameAutomationProxy*>(&proxy_);
+  }
+
+  inline void CreateTab() {
+    ASSERT_EQ(NULL, tab_.get());
+    tab_ = new TabProxy(&dummy_sender_, tracker_.get(), tab_handle_);
+  }
+
+  // Easy methods to set expectations.
+  void SetAutomationServerOk() {
+    EXPECT_CALL(factory_, GetAutomationServer(testing::Eq(timeout_),
+        testing::StrEq(profile_),
+        testing::_,
+        testing::_,
+        testing::NotNull()))
+    .Times(1)
+    .WillOnce(testing::DoAll(
+        testing::WithArgs<0, 4>(
+            testing::Invoke(CBF(&factory_, &MockProxyFactory::GetServerImpl,
+                                get_proxy(), AUTOMATION_SUCCESS))),
+        testing::Return(id_)));
+
+    EXPECT_CALL(factory_, ReleaseAutomationServer(testing::Eq(id_))).Times(1);
+  }
+
+  void Set_CFD_LaunchFailed(AutomationLaunchResult result) {
+    EXPECT_CALL(cfd_, OnAutomationServerLaunchFailed(
+        testing::Eq(result), testing::_))
+        .Times(1)
+        .WillOnce(QUIT_LOOP(loop_));
+  }
+
+ protected:
+  CFACMockTest() : tracker_(NULL), timeout_(500),
+      profile_(L"Adam.N.Epilinter") {
+    id_ = reinterpret_cast<void*>(5);
+    tab_handle_ = 3;
+  }
+
+  virtual void SetUp() {
+    dummy_sender_.ForwardTo(&proxy_);
+    tracker_.reset(new AutomationHandleTracker(&dummy_sender_));
+
+    client_.reset(new ChromeFrameAutomationClient);
+    client_->set_proxy_factory(&factory_);
+  }
+};
+
+// Could be implemented as MockAutomationProxy member (we have WithArgs<>!)
+ACTION_P3(HandleCreateTab, tab_handle, external_tab_container, tab_wnd) {
+  // arg0 - message
+  // arg1 - callback
+  // arg2 - key
+  CallbackRunner<Tuple3<HWND, HWND, int> >* c =
+      reinterpret_cast<CallbackRunner<Tuple3<HWND, HWND, int> >*>(arg1);
+  c->Run(external_tab_container, tab_wnd, tab_handle);
+  delete c;
+  delete arg0;
+}
+
+TEST_F(CFACMockTest, MockedCreateTabOk) {
+  int timeout = 500;
+  CreateTab();
+  SetAutomationServerOk();
+
+  EXPECT_CALL(proxy_, server_version()).Times(testing::AnyNumber())
+      .WillRepeatedly(testing::Return(""));
+
+  // We need some valid HWNDs, when responding to CreateExternalTab
+  HWND h1 = ::GetDesktopWindow();
+  HWND h2 = ::GetDesktopWindow();
+  EXPECT_CALL(proxy_, SendAsAsync(testing::Property(&IPC::SyncMessage::type,
+                                 AutomationMsg_CreateExternalTab__ID),
+                                 testing::NotNull(), testing::_))
+      .Times(1)
+      .WillOnce(HandleCreateTab(tab_handle_, h1, h2));
+
+  EXPECT_CALL(proxy_, CreateTabProxy(testing::Eq(tab_handle_)))
+      .WillOnce(testing::Return(tab_));
+
+  EXPECT_CALL(cfd_, OnAutomationServerReady())
+      .WillOnce(QUIT_LOOP(loop_));
+
+  // Here we go!
+  EXPECT_TRUE(client_->Initialize(&cfd_, timeout, false, profile_, L"", false));
+  loop_.RunFor(10);
+  client_->Uninitialize();
+}
+
+TEST_F(CFACMockTest, MockedCreateTabFailed) {
+  HWND null_wnd = NULL;
+  SetAutomationServerOk();
+
+  EXPECT_CALL(proxy_, server_version()).Times(testing::AnyNumber())
+      .WillRepeatedly(testing::Return(""));
+
+  EXPECT_CALL(proxy_, SendAsAsync(testing::Property(&IPC::SyncMessage::type,
+                                  AutomationMsg_CreateExternalTab__ID),
+                                  testing::NotNull(), testing::_))
+      .Times(1)
+      .WillOnce(HandleCreateTab(tab_handle_, null_wnd, null_wnd));
+
+  EXPECT_CALL(proxy_, CreateTabProxy(testing::_)).Times(0);
+
+  Set_CFD_LaunchFailed(AUTOMATION_CREATE_TAB_FAILED);
+
+  // Here we go!
+  EXPECT_TRUE(client_->Initialize(&cfd_, timeout_, false, profile_, L"",
+              false));
+  loop_.RunFor(4);
+  client_->Uninitialize();
+}
+
+const wchar_t kMetaTagPage[] = L"files/meta_tag.html";
+TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_MetaTag) {
+  SimpleBrowserTest(IE, kMetaTagPage, L"meta_tag");
+}
+
+const wchar_t kCFProtocolPage[] = L"files/chrome_frame_protocol.html";
+TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_CFProtocol) {
+  SimpleBrowserTest(IE, kCFProtocolPage, L"chrome_frame_protocol");
+}
+
+const wchar_t kPersistentCookieTest[] =
+    L"files/persistent_cookie_test_page.html";
+TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_PersistentCookieTest) {
+  SimpleBrowserTest(IE, kPersistentCookieTest, L"PersistentCookieTest");
+}
+
+const wchar_t kNavigateOutPage[] = L"files/navigate_out.html";
+TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_NavigateOut) {
+  SimpleBrowserTest(IE, kNavigateOutPage, L"navigate_out");
+}
+
+HRESULT LaunchIEAsComServer(IWebBrowser2** web_browser) {
+  if (!web_browser)
+    return E_INVALIDARG;
+
+  ScopedComPtr<IWebBrowser2> web_browser2;
+  HRESULT hr = CoCreateInstance(
+      CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER, IID_IWebBrowser2,
+      reinterpret_cast<void**>(web_browser2.Receive()));
+
+  if (SUCCEEDED(hr)) {
+    *web_browser = web_browser2.Detach();
+  }
+
+  return hr;
+}
+
+TEST(ChromeFrameTest, FullTabModeIE_DisallowedUrls) {
+  int major_version = 0;
+  int minor_version = 0;
+  int bugfix_version = 0;
+
+  base::SysInfo::OperatingSystemVersionNumbers(&major_version, &minor_version,
+                                               &bugfix_version);
+  if (major_version > 5) {
+    DLOG(INFO) << __FUNCTION__ << " Not running test on Windows version: "
+               << major_version;
+    return;
+  }
+
+  IEVersion ie_version = GetIEVersion();
+  if (ie_version == IE_8) {
+    DLOG(INFO) << __FUNCTION__ << " Not running test on IE8";
+    return;
+  }
+
+  HRESULT hr = CoInitialize(NULL);
+  bool should_uninit = SUCCEEDED(hr);
+
+  ScopedComPtr<IWebBrowser2> web_browser2;
+  EXPECT_TRUE(S_OK == LaunchIEAsComServer(web_browser2.Receive()));
+  web_browser2->put_Visible(VARIANT_TRUE);
+
+  CComObject<WebBrowserEventSink>* web_browser_sink = NULL;
+  CComObject<WebBrowserEventSink>::CreateInstance(&web_browser_sink);
+
+  // Pass the main thread id to the browser sink so that it can notify
+  // us about test completion.
+  web_browser_sink->set_main_thread_id(GetCurrentThreadId());
+
+  hr = web_browser_sink->DispEventAdvise(web_browser2,
+                                         &DIID_DWebBrowserEvents2);
+  EXPECT_TRUE(hr == S_OK);
+
+  VARIANT empty = ScopedVariant::kEmptyVariant;
+  ScopedVariant url;
+  url.Set(L"cf:file:///C:/");
+
+  TimedMsgLoop loop;
+
+  hr = web_browser2->Navigate2(url.AsInput(), &empty, &empty, &empty, &empty);
+  EXPECT_TRUE(hr == S_OK);
+
+  loop.RunFor(10);
+
+  EXPECT_TRUE(web_browser_sink->navigation_failed());
+
+  hr = web_browser_sink->DispEventUnadvise(web_browser2);
+  EXPECT_TRUE(hr == S_OK);
+
+  web_browser2.Release();
+  chrome_frame_test::CloseAllIEWindows();
+
+  if (should_uninit) {
+    CoUninitialize();
+  }
+}
+
diff --git a/chrome_frame/test/chrome_frame_unittests.h b/chrome_frame/test/chrome_frame_unittests.h
new file mode 100644
index 0000000..98b5985
--- /dev/null
+++ b/chrome_frame/test/chrome_frame_unittests.h
@@ -0,0 +1,164 @@
+// Copyright (c) 2006-2008 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.
+#ifndef CHROME_FRAME_TEST_CHROME_FRAME_UNITTESTS_H_
+#define CHROME_FRAME_TEST_CHROME_FRAME_UNITTESTS_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <string>
+#include <exdisp.h>
+#include <exdispid.h>
+
+#include "base/ref_counted.h"
+#include "base/scoped_handle_win.h"
+#include "googleurl/src/gurl.h"
+#include "chrome_frame/test/http_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Class that:
+// 1) Starts the local webserver,
+// 2) Supports launching browsers - Internet Explorer and Firefox with local url
+// 3) Wait the webserver to finish. It is supposed the test webpage to shutdown
+//    the server by navigating to "kill" page
+// 4) Supports read the posted results from the test webpage to the "dump"
+//    webserver directory
+class ChromeFrameTestWithWebServer: public testing::Test {
+ protected:
+  enum BrowserKind { INVALID, IE, FIREFOX, OPERA, SAFARI, CHROME };
+
+  bool LaunchBrowser(BrowserKind browser, const wchar_t* url);
+  bool WaitForTestToComplete(int milliseconds);
+  // Waits for the page to notify us of the window.onload event firing.
+  // Note that the milliseconds value is only approximate.
+  bool WaitForOnLoad(int milliseconds);
+  bool ReadResultFile(const std::wstring& file_name, std::string* data);
+
+  // Launches the specified browser and waits for the test to complete
+  // (see WaitForTestToComplete).  Then checks that the outcome is OK.
+  // This function uses EXPECT_TRUE and ASSERT_TRUE for all steps performed
+  // hence no return value.
+  void SimpleBrowserTest(BrowserKind browser, const wchar_t* page,
+                         const wchar_t* result_file_to_check);
+
+  // Same as SimpleBrowserTest but if the browser isn't installed (LaunchBrowser
+  // fails), the function will print out a warning but not treat the test
+  // as failed.
+  // Currently this is how we run Opera tests.
+  void OptionalBrowserTest(BrowserKind browser, const wchar_t* page,
+                           const wchar_t* result_file_to_check);
+
+  // Test if chrome frame correctly reports its version.
+  void VersionTest(BrowserKind browser, const wchar_t* page,
+                   const wchar_t* result_file_to_check);
+
+  void CloseBrowser();
+
+  // Ensures (well, at least tries to ensure) that the browser window has focus.
+  bool BringBrowserToTop();
+
+  // Returns true iff the specified result file contains 'expected result'.
+  bool CheckResultFile(const std::wstring& file_name,
+                       const std::string& expected_result);
+
+  virtual void SetUp();
+  virtual void TearDown();
+
+  // Important: kind means "sheep" in Icelandic. ?:-o
+  const char* ToString(BrowserKind kind) {
+    switch (kind) {
+      case IE:
+        return "IE";
+      case FIREFOX:
+        return "Firefox";
+      case OPERA:
+        return "Opera";
+      case CHROME:
+        return "Chrome";
+      case SAFARI:
+        return "Safari";
+      default:
+        NOTREACHED();
+        break;
+    }
+    return "";
+  }
+
+  BrowserKind browser_;
+  std::wstring results_dir_;
+  ScopedHandle browser_handle_;
+  ChromeFrameHTTPServer server_;
+};
+
+// This class sets up event sinks to the IWebBrowser interface. Currently it
+// subscribes to the following events:-
+// 1. DISPID_BEFORENAVIGATE2
+// 2. DISPID_NAVIGATEERROR
+// 3. DISPID_NAVIGATECOMPLETE2 
+// Other events can be subscribed to on an if needed basis.
+class WebBrowserEventSink
+    : public CComObjectRootEx<CComSingleThreadModel>,
+      public IDispEventSimpleImpl<0, WebBrowserEventSink,
+                                  &DIID_DWebBrowserEvents2> {
+ public:
+  WebBrowserEventSink()
+      : navigation_failed_(false),
+        main_thread_id_(0) {
+  }
+
+BEGIN_COM_MAP(WebBrowserEventSink)
+END_COM_MAP()
+
+BEGIN_SINK_MAP(WebBrowserEventSink)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2,
+                 OnBeforeNavigate2, &kBeforeNavigate2Info)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2,
+                 OnNavigateComplete2, &kNavigateComplete2Info)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NAVIGATEERROR,
+                 OnNavigateError, &kNavigateErrorInfo)
+END_SINK_MAP()
+
+  STDMETHOD_(void, OnNavigateError)(IDispatch* dispatch, VARIANT* url,
+                                    VARIANT* frame_name, VARIANT* status_code,
+                                    VARIANT* cancel) {
+    navigation_failed_ = true;
+  }
+
+  STDMETHOD(OnBeforeNavigate2)(IDispatch* dispatch, VARIANT* url, VARIANT*
+                               flags, VARIANT* target_frame_name,
+                               VARIANT* post_data, VARIANT* headers,
+                               VARIANT_BOOL* cancel) {
+    DLOG(INFO) << __FUNCTION__;
+    // If a navigation fails then IE issues a navigation to an interstitial
+    // page. Catch this to track navigation errors as the NavigateError
+    // notification does not seem to fire reliably.
+    GURL crack_url(url->bstrVal);
+    if (crack_url.scheme() == "res") {
+      navigation_failed_ = true;
+    }
+    return S_OK;
+  }
+
+  STDMETHOD_(void, OnNavigateComplete2)(IDispatch* dispatch, VARIANT* url) {
+    DLOG(INFO) << __FUNCTION__;
+  }
+
+  bool navigation_failed() const {
+    return navigation_failed_;
+  }
+
+  void set_main_thread_id(DWORD thread_id) {
+    main_thread_id_ = thread_id;
+  }
+
+ protected:
+  bool navigation_failed_;
+
+  static _ATL_FUNC_INFO kBeforeNavigate2Info;
+  static _ATL_FUNC_INFO kNavigateComplete2Info;
+  static _ATL_FUNC_INFO kNavigateErrorInfo;
+  DWORD main_thread_id_;
+};
+
+#endif  // CHROME_FRAME_TEST_CHROME_FRAME_UNITTESTS_H_
+
diff --git a/chrome_frame/test/chrometab_unittests.vsprops b/chrome_frame/test/chrometab_unittests.vsprops
new file mode 100644
index 0000000..d5375c99
--- /dev/null
+++ b/chrome_frame/test/chrometab_unittests.vsprops
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="Windows-1252"?>

+<VisualStudioPropertySheet

+	ProjectType="Visual C++"

+	Version="8.00"

+	Name="chrometab_unittests"

+	InheritedPropertySheets="$(SolutionDir)..\tools\grit\build\using_generated_resources.vsprops"

+	>

+  <Tool

+    Name="VCCLCompilerTool"	

+    PreprocessorDefinitions="_ATL_APARTMENT_THREADED;_ATL_CSTRING_EXPLICIT_CONSTRUCTORS"

+    AdditionalIncludeDirectories=""

+  />

+

+</VisualStudioPropertySheet>

diff --git a/chrome_frame/test/com_message_event_unittest.cc b/chrome_frame/test/com_message_event_unittest.cc
new file mode 100644
index 0000000..f850c20
--- /dev/null
+++ b/chrome_frame/test/com_message_event_unittest.cc
@@ -0,0 +1,325 @@
+// Copyright (c) 2009 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.

+

+#include "chrome_frame/com_message_event.h"

+

+#include <atlbase.h>

+#include <atlcom.h>

+

+#include "testing/gtest/include/gtest/gtest.h"

+

+// To allow the unit test read-only access to check protected member variables.

+class FriendlyComMessageEvent : public ComMessageEvent {

+ public:

+  inline IHTMLEventObj* basic_event() { return basic_event_; }

+};

+

+class ATL_NO_VTABLE MockDumbContainer :

+    public CComObjectRoot,

+    public IOleContainer {

+ public:

+  DECLARE_NOT_AGGREGATABLE(MockDumbContainer)

+  BEGIN_COM_MAP(MockDumbContainer)

+    COM_INTERFACE_ENTRY(IParseDisplayName)

+    COM_INTERFACE_ENTRY(IOleContainer)

+  END_COM_MAP()

+

+  STDMETHOD(ParseDisplayName)(IBindCtx*, LPOLESTR, ULONG*, IMoniker**) {

+    NOTREACHED();

+    return E_NOTIMPL;

+  }

+  STDMETHOD(EnumObjects)(DWORD, IEnumUnknown**) {

+    NOTREACHED();

+    return E_NOTIMPL;

+  }  

+  STDMETHOD(LockContainer)(BOOL) {

+    NOTREACHED();

+    return E_NOTIMPL;

+  }

+};

+

+TEST(ComMessageEvent, WithDumbContainer) {

+  CComObject<MockDumbContainer>* container_obj = NULL;

+  CComObject<MockDumbContainer>::CreateInstance(&container_obj);

+  ScopedComPtr<IOleContainer> container(container_obj);

+  EXPECT_FALSE(!container);

+

+  CComObject<FriendlyComMessageEvent>* event_obj = NULL;

+  CComObject<FriendlyComMessageEvent>::CreateInstance(&event_obj);

+  ScopedComPtr<IUnknown> event_ref(event_obj);

+

+  bool result = event_obj->Initialize(container, "hi", "http://www.foo.com/",

+                                      "message");

+  EXPECT_TRUE(result);

+  EXPECT_TRUE(!event_obj->basic_event());

+}

+

+// Mock object to mimic a "smart" container, e.g. IE, that will

+// be able to return an IHTMLDocument2 and 4, and from which you

+// can get an IHTMLEventObj implementation.  Doubles as a mock

+// IHTMLEventObj implementation.

+class ATL_NO_VTABLE MockSmartContainer :

+    public CComObjectRoot,

+    public IOleContainer,

+    public IHTMLDocument2,

+    public IHTMLDocument4,

+    public IHTMLEventObj {

+ public:

+  DECLARE_NOT_AGGREGATABLE(MockSmartContainer)

+  BEGIN_COM_MAP(MockSmartContainer)

+    COM_INTERFACE_ENTRY_IID(IID_IDispatch, IHTMLEventObj)

+    COM_INTERFACE_ENTRY(IParseDisplayName)

+    COM_INTERFACE_ENTRY(IOleContainer)

+    COM_INTERFACE_ENTRY(IHTMLDocument)

+    COM_INTERFACE_ENTRY(IHTMLDocument2)

+    COM_INTERFACE_ENTRY(IHTMLDocument4)

+    COM_INTERFACE_ENTRY(IHTMLEventObj)

+  END_COM_MAP()

+

+  static const DISPID kDispId = 424242;

+  static const long kResultValue = 42;

+

+  // Only method we actually implement from IHTMLDocument4, to give

+  // out the mock IHTMLEventObj.

+  STDMETHOD(createEventObject)(VARIANT*, IHTMLEventObj** event_obj) {

+    return GetUnknown()->QueryInterface(event_obj);

+  }

+

+  // Dummy IDispatch implementation for unit testing, to validate

+  // passthrough semantics.

+  STDMETHOD(GetIDsOfNames)(REFIID iid, LPOLESTR* names, UINT num_names, 

+                           LCID lcid, DISPID* disp_ids) {

+    DCHECK(num_names == 1);

+    disp_ids[0] = kDispId;

+    return S_OK;

+  }

+  

+  STDMETHOD(Invoke)(DISPID id, REFIID iid, LCID lcid, WORD flags, 

+                    DISPPARAMS* disp_params, VARIANT* var_result, 

+                    EXCEPINFO* excep_info, UINT* arg_error) {

+    var_result->vt = VT_I4;

+    var_result->lVal = kResultValue;

+    return S_OK;

+  }

+  

+

+  // Do-nothing implementation of the rest of the interface methods.

+  // To make this less verbose, define a macro here and undefine it

+  // at the end of the list.

+#define STDMETHODNOTIMP(method, parameters) \

+    STDMETHOD(method) parameters { \

+      NOTREACHED(); \

+      return E_NOTIMPL; \

+    }

+

+  // IDispatch

+  STDMETHODNOTIMP(GetTypeInfoCount, (UINT*));

+  STDMETHODNOTIMP(GetTypeInfo, (UINT, LCID, ITypeInfo**));

+

+  // IParseDisplayName

+  STDMETHODNOTIMP(ParseDisplayName, (IBindCtx*, LPOLESTR, ULONG*, IMoniker**));

+  // IOleContainer

+  STDMETHODNOTIMP(EnumObjects, (DWORD, IEnumUnknown**));

+  STDMETHODNOTIMP(LockContainer, (BOOL));

+  // IHTMLDocument

+  STDMETHODNOTIMP(get_Script, (IDispatch**));

+  // IHTMLDocument2

+  STDMETHODNOTIMP(get_all, (IHTMLElementCollection**));

+  STDMETHODNOTIMP(get_body, (IHTMLElement**));

+  STDMETHODNOTIMP(get_activeElement, (IHTMLElement**));

+  STDMETHODNOTIMP(get_images, (IHTMLElementCollection**));

+  STDMETHODNOTIMP(get_applets, (IHTMLElementCollection**));

+  STDMETHODNOTIMP(get_links, (IHTMLElementCollection**));

+  STDMETHODNOTIMP(get_forms, (IHTMLElementCollection**));

+  STDMETHODNOTIMP(get_anchors, (IHTMLElementCollection**));

+  STDMETHODNOTIMP(put_title, (BSTR));

+  STDMETHODNOTIMP(get_title, (BSTR*));

+  STDMETHODNOTIMP(get_scripts, (IHTMLElementCollection**));

+  STDMETHODNOTIMP(put_designMode, (BSTR));

+  STDMETHODNOTIMP(get_designMode, (BSTR*));

+  STDMETHODNOTIMP(get_selection, (IHTMLSelectionObject**));

+  STDMETHODNOTIMP(get_readyState, (BSTR*));

+  STDMETHODNOTIMP(get_frames, (IHTMLFramesCollection2**));

+  STDMETHODNOTIMP(get_embeds, (IHTMLElementCollection**));

+  STDMETHODNOTIMP(get_plugins, (IHTMLElementCollection**));

+  STDMETHODNOTIMP(put_alinkColor, (VARIANT));

+  STDMETHODNOTIMP(get_alinkColor, (VARIANT*));

+  STDMETHODNOTIMP(put_bgColor, (VARIANT));

+  STDMETHODNOTIMP(get_bgColor, (VARIANT*));

+  STDMETHODNOTIMP(put_fgColor, (VARIANT));

+  STDMETHODNOTIMP(get_fgColor, (VARIANT*));

+  STDMETHODNOTIMP(put_linkColor, (VARIANT));

+  STDMETHODNOTIMP(get_linkColor, (VARIANT*));

+  STDMETHODNOTIMP(put_vlinkColor, (VARIANT));

+  STDMETHODNOTIMP(get_vlinkColor, (VARIANT*));

+  STDMETHODNOTIMP(get_referrer, (BSTR*));

+  STDMETHODNOTIMP(get_location, (IHTMLLocation**));

+  STDMETHODNOTIMP(get_lastModified, (BSTR*));

+  STDMETHODNOTIMP(put_URL, (BSTR));

+  STDMETHODNOTIMP(get_URL, (BSTR*));

+  STDMETHODNOTIMP(put_domain, (BSTR));

+  STDMETHODNOTIMP(get_domain, (BSTR*));

+  STDMETHODNOTIMP(put_cookie, (BSTR));

+  STDMETHODNOTIMP(get_cookie, (BSTR*));

+  STDMETHODNOTIMP(put_expando, (VARIANT_BOOL));

+  STDMETHODNOTIMP(get_expando, (VARIANT_BOOL*));

+  STDMETHODNOTIMP(put_charset, (BSTR));

+  STDMETHODNOTIMP(get_charset, (BSTR*));

+  STDMETHODNOTIMP(put_defaultCharset, (BSTR));

+  STDMETHODNOTIMP(get_defaultCharset, (BSTR*));

+  STDMETHODNOTIMP(get_mimeType, (BSTR*));

+  STDMETHODNOTIMP(get_fileSize, (BSTR*));

+  STDMETHODNOTIMP(get_fileCreatedDate, (BSTR*));

+  STDMETHODNOTIMP(get_fileModifiedDate, (BSTR*));

+  STDMETHODNOTIMP(get_fileUpdatedDate, (BSTR*));

+  STDMETHODNOTIMP(get_security, (BSTR*));

+  STDMETHODNOTIMP(get_protocol, (BSTR*));

+  STDMETHODNOTIMP(get_nameProp, (BSTR*));

+  STDMETHODNOTIMP(write, (SAFEARRAY*));

+  STDMETHODNOTIMP(writeln, (SAFEARRAY*));

+  STDMETHODNOTIMP(open, (BSTR, VARIANT, VARIANT, VARIANT, IDispatch**));

+  STDMETHODNOTIMP(close, ());

+  STDMETHODNOTIMP(clear, ());

+  STDMETHODNOTIMP(queryCommandSupported, (BSTR, VARIANT_BOOL*));

+  STDMETHODNOTIMP(queryCommandEnabled, (BSTR, VARIANT_BOOL*));

+  STDMETHODNOTIMP(queryCommandState, (BSTR, VARIANT_BOOL*));

+  STDMETHODNOTIMP(queryCommandIndeterm, (BSTR, VARIANT_BOOL*));

+  STDMETHODNOTIMP(queryCommandText, (BSTR, BSTR*));

+  STDMETHODNOTIMP(queryCommandValue, (BSTR, VARIANT*));

+  STDMETHODNOTIMP(execCommand, (BSTR, VARIANT_BOOL, VARIANT, VARIANT_BOOL*));

+  STDMETHODNOTIMP(execCommandShowHelp, (BSTR, VARIANT_BOOL*));

+  STDMETHODNOTIMP(createElement, (BSTR, IHTMLElement**));

+  STDMETHODNOTIMP(put_onhelp, (VARIANT));

+  STDMETHODNOTIMP(get_onhelp, (VARIANT*));

+  STDMETHODNOTIMP(put_onclick, (VARIANT));

+  STDMETHODNOTIMP(get_onclick, (VARIANT*));

+  STDMETHODNOTIMP(put_ondblclick, (VARIANT));

+  STDMETHODNOTIMP(get_ondblclick, (VARIANT*));

+  STDMETHODNOTIMP(put_onkeyup, (VARIANT));

+  STDMETHODNOTIMP(get_onkeyup, (VARIANT*));

+  STDMETHODNOTIMP(put_onkeydown, (VARIANT));

+  STDMETHODNOTIMP(get_onkeydown, (VARIANT*));

+  STDMETHODNOTIMP(put_onkeypress, (VARIANT));

+  STDMETHODNOTIMP(get_onkeypress, (VARIANT*));

+  STDMETHODNOTIMP(put_onmouseup, (VARIANT));

+  STDMETHODNOTIMP(get_onmouseup, (VARIANT*));

+  STDMETHODNOTIMP(put_onmousedown, (VARIANT));

+  STDMETHODNOTIMP(get_onmousedown, (VARIANT*));

+  STDMETHODNOTIMP(put_onmousemove, (VARIANT));

+  STDMETHODNOTIMP(get_onmousemove, (VARIANT*));

+  STDMETHODNOTIMP(put_onmouseout, (VARIANT));

+  STDMETHODNOTIMP(get_onmouseout, (VARIANT*));

+  STDMETHODNOTIMP(put_onmouseover, (VARIANT));

+  STDMETHODNOTIMP(get_onmouseover, (VARIANT*));

+  STDMETHODNOTIMP(put_onreadystatechange, (VARIANT));

+  STDMETHODNOTIMP(get_onreadystatechange, (VARIANT*));

+  STDMETHODNOTIMP(put_onafterupdate, (VARIANT));

+  STDMETHODNOTIMP(get_onafterupdate, (VARIANT*));

+  STDMETHODNOTIMP(put_onrowexit, (VARIANT));

+  STDMETHODNOTIMP(get_onrowexit, (VARIANT*));

+  STDMETHODNOTIMP(put_onrowenter, (VARIANT));

+  STDMETHODNOTIMP(get_onrowenter, (VARIANT*));

+  STDMETHODNOTIMP(put_ondragstart, (VARIANT));

+  STDMETHODNOTIMP(get_ondragstart, (VARIANT*));

+  STDMETHODNOTIMP(put_onselectstart, (VARIANT));

+  STDMETHODNOTIMP(get_onselectstart, (VARIANT*));

+  STDMETHODNOTIMP(elementFromPoint, (long, long, IHTMLElement**));

+  STDMETHODNOTIMP(get_parentWindow, (IHTMLWindow2**));

+  STDMETHODNOTIMP(get_styleSheets, (IHTMLStyleSheetsCollection**));

+  STDMETHODNOTIMP(put_onbeforeupdate, (VARIANT));

+  STDMETHODNOTIMP(get_onbeforeupdate, (VARIANT*));

+  STDMETHODNOTIMP(put_onerrorupdate, (VARIANT));

+  STDMETHODNOTIMP(get_onerrorupdate, (VARIANT*));

+  STDMETHODNOTIMP(toString, (BSTR*));

+  STDMETHODNOTIMP(createStyleSheet, (BSTR, long, IHTMLStyleSheet**));

+  // IHTMLDocument4

+  STDMETHODNOTIMP(focus, ());

+  STDMETHODNOTIMP(hasFocus, (VARIANT_BOOL*));

+  STDMETHODNOTIMP(put_onselectionchange, (VARIANT));

+  STDMETHODNOTIMP(get_onselectionchange, (VARIANT*));

+  STDMETHODNOTIMP(get_namespaces, (IDispatch**));

+  STDMETHODNOTIMP(createDocumentFromUrl, (BSTR, BSTR, IHTMLDocument2**));

+  STDMETHODNOTIMP(put_media, (BSTR));

+  STDMETHODNOTIMP(get_media, (BSTR*));

+  STDMETHODNOTIMP(fireEvent, (BSTR, VARIANT*, VARIANT_BOOL*));

+  STDMETHODNOTIMP(createRenderStyle, (BSTR, IHTMLRenderStyle**));

+  STDMETHODNOTIMP(put_oncontrolselect, (VARIANT));

+  STDMETHODNOTIMP(get_oncontrolselect, (VARIANT*));

+  STDMETHODNOTIMP(get_URLUnencoded, (BSTR*));

+  // IHTMLEventObj

+  STDMETHODNOTIMP(get_srcElement, (IHTMLElement**))

+  STDMETHODNOTIMP(get_altKey, (VARIANT_BOOL*));

+  STDMETHODNOTIMP(get_ctrlKey, (VARIANT_BOOL*));

+  STDMETHODNOTIMP(get_shiftKey, (VARIANT_BOOL*));

+  STDMETHODNOTIMP(put_returnValue, (VARIANT));

+  STDMETHODNOTIMP(get_returnValue, (VARIANT*));

+  STDMETHODNOTIMP(put_cancelBubble, (VARIANT_BOOL));

+  STDMETHODNOTIMP(get_cancelBubble, (VARIANT_BOOL*));

+  STDMETHODNOTIMP(get_fromElement, (IHTMLElement**));

+  STDMETHODNOTIMP(get_toElement, (IHTMLElement**));

+  STDMETHODNOTIMP(put_keyCode, (long));

+  STDMETHODNOTIMP(get_keyCode, (long*));

+  STDMETHODNOTIMP(get_button, (long*));

+  STDMETHODNOTIMP(get_type, (BSTR*));

+  STDMETHODNOTIMP(get_qualifier, (BSTR*));

+  STDMETHODNOTIMP(get_reason, (long*));

+  STDMETHODNOTIMP(get_x, (long*));

+  STDMETHODNOTIMP(get_y, (long*));

+  STDMETHODNOTIMP(get_clientX, (long*));

+  STDMETHODNOTIMP(get_clientY, (long*));

+  STDMETHODNOTIMP(get_offsetX, (long*));

+  STDMETHODNOTIMP(get_offsetY, (long*));

+  STDMETHODNOTIMP(get_screenX, (long*));

+  STDMETHODNOTIMP(get_screenY, (long*));

+  STDMETHODNOTIMP(get_srcFilter, (IDispatch**));

+#undef STDMETHODNOTIMP

+};

+

+TEST(ComMessageEvent, WithSmartContainer) {

+  CComObject<MockSmartContainer>* container_obj = NULL;

+  CComObject<MockSmartContainer>::CreateInstance(&container_obj);

+  ScopedComPtr<IOleContainer> container(container_obj);

+  EXPECT_FALSE(!container);

+

+  CComObject<FriendlyComMessageEvent>* event_obj = NULL;

+  CComObject<FriendlyComMessageEvent>::CreateInstance(&event_obj);

+  ScopedComPtr<IUnknown> event_ref(event_obj);

+

+  bool succeeded = event_obj->Initialize(container, "hi", 

+                                         "http://www.foo.com/", "message");

+  EXPECT_TRUE(succeeded);

+  EXPECT_FALSE(!event_obj->basic_event());

+

+  // Name handled natively by CF's ComMessageEvent.

+  DISPID dispid = -1;

+  LPOLESTR name = L"data";

+  HRESULT hr = event_obj->GetIDsOfNames(IID_IDispatch, &name, 1,

+                                        LOCALE_USER_DEFAULT, &dispid);

+  EXPECT_HRESULT_SUCCEEDED(hr);

+  EXPECT_EQ(dispid, ComMessageEvent::DISPID_MESSAGE_EVENT_DATA);

+

+  // Name not handled by CF's ComMessageEvent.

+  dispid = -1;

+  name = L"nothandledatallbyanyone";

+  hr = event_obj->GetIDsOfNames(IID_IDispatch, &name, 1,

+                                LOCALE_USER_DEFAULT, &dispid);

+  EXPECT_HRESULT_SUCCEEDED(hr);

+  EXPECT_EQ(dispid, MockSmartContainer::kDispId);

+

+  // Invoke function handled by ComMessageEvent.

+  CComDispatchDriver dispatcher(event_obj);

+  CComVariant result;

+  hr = dispatcher.GetProperty(ComMessageEvent::DISPID_MESSAGE_EVENT_DATA,

+                              &result);

+  EXPECT_HRESULT_SUCCEEDED(hr);

+  EXPECT_EQ(result.vt, VT_BSTR);

+  EXPECT_EQ(wcscmp(result.bstrVal, L"hi"), 0);

+

+  // And now check passthrough.

+  result.Clear();

+  hr = dispatcher.GetProperty(MockSmartContainer::kDispId, &result);

+  EXPECT_HRESULT_SUCCEEDED(hr);

+  EXPECT_EQ(result.vt, VT_I4);

+  EXPECT_EQ(result.lVal, MockSmartContainer::kResultValue);

+}

diff --git a/chrome_frame/test/data/CFInstance_basic_frame.html b/chrome_frame/test/data/CFInstance_basic_frame.html
new file mode 100644
index 0000000..11938ee3
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_basic_frame.html
@@ -0,0 +1,8 @@
+<html>
+  <head>
+    <title></title>
+  </head>
+  <body>
+    <h1>do nothing</h1>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/CFInstance_basic_host.html b/chrome_frame/test/data/CFInstance_basic_host.html
new file mode 100644
index 0000000..6bad61c
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_basic_host.html
@@ -0,0 +1,55 @@
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="parent">
+      <div id="prev">before</div><div id="toBeReplaced"> 
+        fallback content goes here 
+      </div><div id="after">after</div>
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstanceBasic";
+      (function(){
+        try{
+          var cf = new CFInstance({
+            src: "CFInstance_basic_frame.html",
+            node: "toBeReplaced"
+          });
+
+          if (document.getElementById("parent") != cf.parentNode ) {
+            onFailure(testName, 1, "parent node mismatch");
+            return;
+          }
+
+          if (document.getElementById("prev").nextSibling != cf) {
+            onFailure(testName, 1, "sibling node mismatch");
+            return;
+          }
+
+          if (document.getElementById("after").previousSibling != cf) {
+            onFailure(testName, 1, "sibling node mismatch");
+            return;
+          }
+
+          onSuccess(testName, 1);
+
+        } catch (e) {
+          onFailure(testName, 1,
+                    "CFInstance constructor failed with error: "+e);
+        } 
+      })();
+    </script>
+    <p>Tests ChromeFrame Navigation</p>
+  </body>
+</html>
+
diff --git a/chrome_frame/test/data/CFInstance_default_ctor_host.html b/chrome_frame/test/data/CFInstance_default_ctor_host.html
new file mode 100644
index 0000000..744c6de
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_default_ctor_host.html
@@ -0,0 +1,45 @@
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="parent">
+      <div id="prev">before</div><div id="toBeReplaced"> 
+        fallback content goes here 
+      </div><div id="after">after</div>
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstanceDefaultCtor";
+      (function(){
+        try{
+          var cf = new CFInstance();
+          cf.src = "CFInstance_basic_frame.html";
+          var node = document.getElementById("toBeReplaced");
+          node.parentNode.replaceChild(cf, node);
+          var timer = setTimeout(function() {
+            onFailure(testName, 1, "CFInstance navigation timeout");
+          }, 15000);
+          cf.listen("load", function() {
+            clearTimeout(timer);
+            onSuccess(testName, 1);
+          });
+          cf.src = "CFInstance_basic_frame.html";
+        } catch (e) {
+          onFailure(testName, 1,
+                    "CFInstance constructor failed with error: "+e);
+        } 
+      })();
+    </script>
+    <p>Tests Chrome Frame constructor without arguments</p>
+  </body>
+</html>
+
diff --git a/chrome_frame/test/data/CFInstance_delay_host.html b/chrome_frame/test/data/CFInstance_delay_host.html
new file mode 100644
index 0000000..f6d9e02
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_delay_host.html
@@ -0,0 +1,47 @@
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="parent">
+      <div id="prev">before</div><div id="toBeReplaced"> 
+        fallback content goes here 
+      </div><div id="after">after</div>
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstanceDelay";
+      (function(){
+        try{
+          var cf = new CFInstance({
+            onload: function() {
+              onSuccess(testName, 1);
+            },
+            src: "CFInstance_basic_frame.html"
+          });
+
+          setTimeout(function() {
+            var replNode = document.getElementById("toBeReplaced");
+            // impedence matching between new and old CFInstance.js
+            var node = cf["plugin"] ? cf.plugin : cf;
+            replNode.parentNode.replaceChild(node, replNode);
+          }, 100);
+
+        } catch (e) {
+          onFailure(testName, 1,
+                    "CFInstance constructor failed with error: "+e);
+        } 
+      })();
+    </script>
+    <p>Tests ChromeFrame Navigation when placed in the document on a delay</p>
+  </body>
+</html>
+
diff --git a/chrome_frame/test/data/CFInstance_fallback_host.html b/chrome_frame/test/data/CFInstance_fallback_host.html
new file mode 100644
index 0000000..5939180
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_fallback_host.html
@@ -0,0 +1,44 @@
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="parent">
+      <div id="prev">before</div><div id="toBeReplaced"> 
+        fallback content goes here 
+      </div><div id="after">after</div>
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstanceFallback";
+      (function() {
+        try {
+          var cf = new CFInstance({
+            node: "toBeReplaced",
+            src: "CFInstance_basic_frame.html",
+            requirements: []
+          });
+
+          if (cf.tagName.toLowerCase() == "iframe") {
+            onSuccess(testName, 1);
+          } else {
+            onFailure(testName, 1, "expected tagName mismatch");
+          }
+        } catch (e) {
+          onFailure(testName, 1,
+                    "CFInstance constructor failed with error: "+e);
+        } 
+      })();
+    </script>
+    <p>Tests ChromeFrame fallback</p>
+  </body>
+</html>
+
diff --git a/chrome_frame/test/data/CFInstance_iframe_onload_host.html b/chrome_frame/test/data/CFInstance_iframe_onload_host.html
new file mode 100644
index 0000000..913c462
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_iframe_onload_host.html
@@ -0,0 +1,41 @@
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="parent">
+      <div id="prev">before</div><div id="toBeReplaced"> 
+        fallback content goes here 
+      </div><div id="after">after</div>
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstanceIfrOnload";
+      (function() {
+        try {
+          var cf = new CFInstance({
+            node: "toBeReplaced",
+            src: "CFInstance_basic_frame.html",
+            onload: function() {
+              onSuccess(testName, 1);
+            },
+            requirements: [] // always use an iframe
+          });
+        } catch (e) {
+          onFailure(testName, 1,
+                    "CFInstance constructor failed with error: "+e);
+        } 
+      })();
+    </script>
+    <p>Tests CFInstance event handling on iframes</p>
+  </body>
+</html>
+
diff --git a/chrome_frame/test/data/CFInstance_iframe_post_host.html b/chrome_frame/test/data/CFInstance_iframe_post_host.html
new file mode 100644
index 0000000..f503522
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_iframe_post_host.html
@@ -0,0 +1,50 @@
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="parent">
+      <div id="prev">before</div><div id="toBeReplaced"> 
+        fallback content goes here 
+      </div><div id="after">after</div>
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstanceIfrPost";
+      (function() {
+        if (!CFInstance.contentTests.postMessage()) {
+          onSuccess(testName, 1);
+        } else {
+          try {
+            var cf = new CFInstance({
+              node: "toBeReplaced",
+              src: "CFInstance_post_frame.html",
+              onload: function() {
+                cf.postMessage("howdy!");
+              },
+              onmessage: function(evt) {
+                if (evt.data == 'hola!') {
+                  onSuccess(testName, 1);
+                }
+              },
+              requirements: [] // always use an iframe
+            });
+          } catch (e) {
+            onFailure(testName, 1,
+                      "CFInstance constructor failed with error: "+e);
+          } 
+        }
+      })();
+    </script>
+    <p>Tests CFInstance event handling on iframes</p>
+  </body>
+</html>
+
diff --git a/chrome_frame/test/data/CFInstance_no_src_host.html b/chrome_frame/test/data/CFInstance_no_src_host.html
new file mode 100644
index 0000000..379e26d
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_no_src_host.html
@@ -0,0 +1,43 @@
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="parent">
+      <div id="prev">before</div><div id="toBeReplaced"> 
+        fallback content goes here 
+      </div><div id="after">after</div>
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstanceNoSrc";
+      (function() {
+        try {
+          var cf = new CFInstance({
+            src: "",
+            node: "toBeReplaced"
+          });
+
+          // check that we got "src" set to "about:blank" by CFInstance
+          if (cf.src == "about:blank") {
+              onSuccess(testName, 1);
+          } else {
+              onFailure(testName, 1, "blank URL mismatch");
+          }
+        } catch (e) {
+          onFailure(testName, 1,
+                    "CFInstance constructor failed with error: "+e);
+        } 
+      })();
+    </script>
+    <p>Tests ChromeFrame with blank src</p>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/CFInstance_post_frame.html b/chrome_frame/test/data/CFInstance_post_frame.html
new file mode 100644
index 0000000..8055cb2e
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_post_frame.html
@@ -0,0 +1,26 @@
+<!-- saved from url=(0014)about:internet -->
+<html>
+<!-- This page is meant to load inside ChromeFrame -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+    <script>
+      var cf = CFInstance;
+
+      cf.listen("load", function() {
+        cf.postMessage("hola!");
+      })
+    </script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <p>ChromeFrame + CFInstance PostMessage Test
+    <br>Test for PostMessage from the host browser to iframe and back</p>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/CFInstance_post_host.html b/chrome_frame/test/data/CFInstance_post_host.html
new file mode 100644
index 0000000..09402ac
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_post_host.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="parent">
+      <div id="prev">before</div><div id="toBeReplaced"> 
+        fallback content goes here 
+      </div><div id="after">after</div>
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstancePost";
+      (function() {
+        try {
+          var cf = new CFInstance({
+            node: "toBeReplaced",
+            src: "CFInstance_post_frame.html",
+            onload: function() {
+              cf.postMessage("howdy!");
+            },
+            onmessage: function(evt) {
+              if (evt.data == "hola!") {
+                onSuccess(testName, 1);
+              }
+            }
+          });
+        } catch (e) {
+          onFailure(testName, 1,
+                    "CFInstance constructor failed with error: " + e);
+        } 
+      })();
+    </script>
+    <p>Tests CFInstance event handling on iframes</p>
+  </body>
+</html>
+
diff --git a/chrome_frame/test/data/CFInstance_rpc_frame.html b/chrome_frame/test/data/CFInstance_rpc_frame.html
new file mode 100644
index 0000000..a7dbfd7
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_rpc_frame.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+
+<html>
+<!-- This page is meant to load inside ChromeFrame -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+    <script>
+      var cf = CFInstance;
+      cf.rpc.expose("rpcHandler", function(arg) {
+        cf.rpc.callRemote("handleCallback", ["hola!"]);
+      });
+      cf.rpc.init();
+    </script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <p>ChromeFrame + CFInstance PostMessage Test
+    <br>Test for PostMessage from the host browser to iframe and back</p>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/CFInstance_rpc_host.html b/chrome_frame/test/data/CFInstance_rpc_host.html
new file mode 100644
index 0000000..60680cf
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_rpc_host.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="parent">
+      <div id="prev">before</div><div id="toBeReplaced"> 
+        fallback content goes here 
+      </div><div id="after">after</div>
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstanceRPC";
+      (function() {
+        try {
+          var cf = new CFInstance({
+            node: "toBeReplaced",
+            src: "CFInstance_rpc_frame.html"
+          });
+
+          var handleCallback = function(arg) {
+            // alert(arg);
+            if (arg == "hola!") {
+              onSuccess(testName, 1);
+            }
+          };
+
+          cf.rpc.expose("handleCallback", handleCallback);
+          cf.rpc.init();
+
+          cf.rpc.callRemote("rpcHandler", ["whatcho talkin 'bout, willis!?"]);
+        } catch (e) {
+          onFailure(testName, 1,
+                    "CFInstance constructor failed with error: " + e);
+        } 
+      })();
+    </script>
+    <p>Tests CFInstance event handling on iframes</p>
+  </body>
+</html>
+
diff --git a/chrome_frame/test/data/CFInstance_rpc_internal_frame.html b/chrome_frame/test/data/CFInstance_rpc_internal_frame.html
new file mode 100644
index 0000000..8208269
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_rpc_internal_frame.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+
+<html>
+<!-- This page is meant to load inside ChromeFrame -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+    <script>
+      setTimeout(rpcCall, 10000);
+      function rpcCall() {
+        var cf = CFInstance;
+        cf.rpc.callRemote("callback");
+        cf.rpc.init();
+      }
+    </script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <p>ChromeFrame + CFInstance PostMessage Test
+    <br>Test for PostMessage from the host browser to iframe and back</p>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/CFInstance_rpc_internal_host.html b/chrome_frame/test/data/CFInstance_rpc_internal_host.html
new file mode 100644
index 0000000..f350871
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_rpc_internal_host.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="parent">
+      <div id="prev">before</div><div id="toBeReplaced"> 
+        fallback content goes here 
+      </div><div id="after">after</div>
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstanceRPCInternal";
+      (function() {
+        try {
+          var cf = new CFInstance({
+            node: "toBeReplaced",
+            src: "CFInstance_rpc_internal_frame.html"
+          });
+
+
+          cf.rpc.expose("callback", function(arg) {
+            onSuccess(testName, 1);
+          });
+          cf.rpc.init();
+        } catch (e) {
+          onFailure(testName, 1,
+                    "CFInstance constructor failed with error: " + e);
+        } 
+      })();
+    </script>
+    <p>Tests CFInstance event handling on iframes</p>
+  </body>
+</html>
+
diff --git a/chrome_frame/test/data/CFInstance_singleton_frame.html b/chrome_frame/test/data/CFInstance_singleton_frame.html
new file mode 100644
index 0000000..1ab03025
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_singleton_frame.html
@@ -0,0 +1,20 @@
+<html>
+  <head>
+    <title>talk to me...</title>
+    <script type="text/javascript" src="CFInstance.js"></script>
+    <script type="text/javascript">
+      CFInstance.listen("load", function() {
+        document.body.innerHTML = "sending 'foo'";
+        CFInstance.postMessage("foo");
+        document.body.innerHTML = "...waiting...";
+      });
+      CFInstance.listen("message", function(evt) {
+        document.body.innerHTML = "sending 'baz'";
+        CFInstance.postMessage("baz");
+      });
+    </script>
+  </head>
+  <body>
+    <h1>sends a message to the parent</h1>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/CFInstance_singleton_host.html b/chrome_frame/test/data/CFInstance_singleton_host.html
new file mode 100644
index 0000000..3eda108
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_singleton_host.html
@@ -0,0 +1,44 @@
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" src="chrome_frame_tester_helpers.js">
+    </script>
+    <script type="text/javascript" src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="toBeReplaced"> 
+      fallback content goes here 
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstanceSingleton";
+      (function() {
+        try{
+          var cf = new CFInstance({
+            src: "CFInstance_singleton_frame.html",
+            node: "toBeReplaced"
+          });
+
+          // test a call/response set of actions driven by the CF content
+          cf.listen("message", function(evt) {
+            if (evt.data == "foo") {
+              cf.postMessage("bar");
+            } else if(evt.data == "baz") {
+              onSuccess(testName, 1);
+            }
+          });
+
+        } catch (e) {
+          onFailure(testName, 1,
+                    "CFInstance constructor failed with error: " + e);
+        } 
+      })();
+    </script>
+    <p>Tests ChromeFrame Navigation</p>
+  </body>
+</html>
+
diff --git a/chrome_frame/test/data/CFInstance_zero_size_host.html b/chrome_frame/test/data/CFInstance_zero_size_host.html
new file mode 100644
index 0000000..94b35c0
--- /dev/null
+++ b/chrome_frame/test/data/CFInstance_zero_size_host.html
@@ -0,0 +1,41 @@
+<html>
+  <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) -->
+  <head>
+    <script type="text/javascript" 
+            src="chrome_frame_tester_helpers.js"></script>
+    <script type="text/javascript" 
+            src="CFInstance.js"></script>
+  </head>
+
+  <body>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <div id="parent">
+      <div id="prev">before</div><div id="toBeReplaced"> 
+        fallback content goes here 
+      </div><div id="after">after</div>
+    </div>
+    <script type="text/javascript">
+      var testName = "CFInstanceZeroSize";
+      (function() {
+        try {
+          var cf = new CFInstance({
+            node: "toBeReplaced",
+            src: "CFInstance_basic_frame.html",
+            cssText: "width: 0px; height: 0px;",
+            onload: function() {
+              onSuccess(testName, 1);
+            }
+          });
+        } catch (e) {
+          onFailure(testName, 1,
+                    "CFInstance constructor failed with error: "+e);
+        } 
+      })();
+    </script>
+    <p>Tests CFInstance event handling on iframes</p>
+  </body>
+</html>
+
diff --git a/chrome_frame/test/data/back_to_ie.html b/chrome_frame/test/data/back_to_ie.html
new file mode 100644
index 0000000..ad4b61d
--- /dev/null
+++ b/chrome_frame/test/data/back_to_ie.html
@@ -0,0 +1,21 @@
+<html>

+  <head><title>Back to IE</title>

+    <script type="text/javascript"

+        src="chrome_frame_tester_helpers.js"></script>

+

+    <script type="text/javascript">

+      function test() {

+        var test_name = 'navigate_out';

+        if (isRunningInMSIE()) {

+          onSuccess(test_name, 1);

+        } else {

+          onFailure(test_name, 1, 'Failed');

+        }

+      }

+    </script>

+  </head>

+  <body onLoad="test();">

+    <h2>Redirected!</h2>

+    <p>This page should have loaded in IE</p>

+  </body>

+</html>

diff --git a/chrome_frame/test/data/cf_protocol.html b/chrome_frame/test/data/cf_protocol.html
new file mode 100644
index 0000000..0036555
--- /dev/null
+++ b/chrome_frame/test/data/cf_protocol.html
@@ -0,0 +1,20 @@
+<html>

+  <head><title>cf: protocol test</title>

+    <script type="text/javascript"

+        src="chrome_frame_tester_helpers.js"></script>

+

+    <script type="text/javascript">

+      function test() {

+        if (isRunningInMSIE()) {

+          reloadUsingCFProtocol();

+        } else {

+          onSuccess("chrome_frame_protocol", 1);

+        }

+      }

+    </script>

+  </head>

+  <body onLoad="setTimeout(test, 100);">

+    <h2>Prepare to be redirected!</h2>

+    <p>Redirects the same page to its 'cf:' version</p>

+  </body>

+</html>

diff --git a/chrome_frame/test/data/chrome_frame_mime_filter_test.html b/chrome_frame/test/data/chrome_frame_mime_filter_test.html
new file mode 100644
index 0000000..7520758
--- /dev/null
+++ b/chrome_frame/test/data/chrome_frame_mime_filter_test.html
@@ -0,0 +1,32 @@
+<!-- saved from url=(0014)about:internet -->

+<!-- Note that for the above Mark of the Web to work, the comment must

+     be followed by a CR/LF ending, so please do not change the line endings. -->

+<html>

+<!-- This page is meant to load inside a host browser like IE/FF -->

+<head>

+<meta http-equiv="X-UA-Compatible" content="chrome=1"/>

+<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script>

+<script type="text/javascript">

+

+function TestIfRunningInChrome() {

+  var is_chrome = /chrome/.test(navigator.userAgent.toLowerCase());

+  if (is_chrome) {

+    onSuccess("MIMEFilter", "MIME filter worked!");

+  } else {

+    onFailure("MIMEFilter", "MIME filter failed :-(",

+              "User agent = " + navigator.userAgent.toLowerCase());

+  }

+}

+

+</script>

+</head>

+

+<body onload="TestIfRunningInChrome();">

+<div id="statusPanel" style="border: 1px solid red; width: 100%">

+Test running....

+</div>

+

+<p>

+

+</body>

+</html>
\ No newline at end of file
diff --git a/chrome_frame/test/data/chrome_frame_resize.html b/chrome_frame/test/data/chrome_frame_resize.html
new file mode 100644
index 0000000..afba53b
--- /dev/null
+++ b/chrome_frame/test/data/chrome_frame_resize.html
@@ -0,0 +1,138 @@
+<!-- saved from url=(0014)about:internet -->

+<!-- Please preserve the CR/LF at the end of the previous line. -->

+<html>

+<!-- This page is meant to load inside the host browser like IE/FF -->

+<head>

+<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script>

+<script type="text/javascript">

+function onLoad() {

+  var chromeFrame = GetChromeFrame();

+  chromeFrame.onmessage = OnChromeFrameResize; 

+  setTimeout(NavigateToURL, 100);

+}

+

+function NavigateToURL() {

+  var chromeFrame = GetChromeFrame();

+  chromeFrame.src = "chrome_frame_resize_hosted.html";

+  setTimeout(CheckIfNavigationFailed, 15000);

+}

+

+var navigation_success = false;

+

+function CheckIfNavigationFailed() {

+  if (!navigation_success) {

+    onFailure("Resize", 1, "ChromeFrame Navigation failed");

+  }

+}

+

+function OnNavigationSucceeded() {

+  navigation_success = true;

+  appendStatus("ChromeFrame hosted page loaded, beginning test...");

+  setTimeout(ResizeChromeFrame, 100);

+}

+

+var resize_step = 0;

+

+function ResizeChromeFrame() {

+  var chromeFrame = GetChromeFrame();

+

+  if (resize_step == 0) {

+    appendStatus("Setting chromeFrame to 100x100");

+    resize_step = 1;

+    chromeFrame.width = 100;

+    setTimeout("OnResizeFailure(0)", 2000);

+  } else if (resize_step == 1) {

+    resize_step = 2;

+    chromeFrame.height = 100;

+    setTimeout("OnResizeFailure(1)", 2000);

+  } else if (resize_step == 2) {

+    appendStatus("Setting chromeFrame to 10x10");

+    resize_step = 3;

+    chromeFrame.width = 10;

+    setTimeout("OnResizeFailure(0)", 2000);

+  } else if (resize_step == 3) {

+    resize_step = 4;

+    chromeFrame.height = 10;

+    setTimeout("OnResizeFailure(1)", 2000);

+  }

+

+  // Note that setting the ChromeFrame window to 0x0 (or < 2x2 if we have the

+  // WS_BORDER style defined on our window) currently results

+  // in a check failure from the child chrome.exe process.

+  // TODO(robertshield): Figure out why and fix it.

+}

+

+var resize_step_received = 0;

+

+function OnChromeFrameResize(evt) {

+  resize_step_received++;

+  appendStatus("ChromeFrame resized: " + evt.data + "step=" + 

+               resize_step_received);

+

+  if (resize_step == 4) {

+    onSuccess("Resize", 1);

+  } else {

+    setTimeout(ResizeChromeFrame, 100);

+  }

+}

+

+function OnResizeFailure(step) {

+  // It turns out that the hosted page gets two calls to onresize()

+  // every time a single size parameter (i.e. width OR height) is changed.

+  // As such this check doesn't quite guarantee success, but if it fails,

+  // then we should fail the unit test.

+  if (step >= resize_step_received) {

+    onFailure("Resize", 1, "Did not receive resize reply back from frame.");

+  }

+}

+

+function GetChromeFrame() {

+  return window.document.ChromeFrame;

+}

+

+var debug_counter = 0;

+

+function DebugResizeChromeFrame(delta) {

+  var chromeFrame = GetChromeFrame();  

+  var newWidth = chromeFrame.clientWidth + delta;

+  var newHeight = chromeFrame.clientHeight + delta;

+

+  appendStatus(debug_counter + ". DEBUG resizing CF to (" + newWidth + "," +

+               newHeight + ")");

+  

+  debug_counter++;

+  

+  chromeFrame.width = newWidth;

+  chromeFrame.height = newHeight;

+}

+

+</script>

+</head>

+

+<body onload="onLoad();">

+<div id="description" style="border: 2px solid black; width: 100%">

+  Test for resizing the chrome frame control.

+</div>

+<div id="statusPanel" style="border: 1px solid red; width: 100%">

+Test running....

+</div>

+

+<object id="ChromeFrame" codebase="http://www.google.com"

+        classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"

+        style="border: 1px solid blue">

+  <param name="onload" value="return OnNavigationSucceeded();" />

+    <embed id="ChromeFramePlugin" name="ChromeFrame"

+        onload="return OnNavigationSucceeded();"

+        type="application/chromeframe"

+        style="border: 1px solid green">

+    </embed>

+</object>

+<br />

+<br />

+

+<button onclick="javascript:DebugResizeChromeFrame(20);">Bigger</button>

+<button onclick="javascript:DebugResizeChromeFrame(-20);">Smaller</button>

+

+</body>

+</html>

+

diff --git a/chrome_frame/test/data/chrome_frame_resize_hosted.html b/chrome_frame/test/data/chrome_frame_resize_hosted.html
new file mode 100644
index 0000000..95528ec
--- /dev/null
+++ b/chrome_frame/test/data/chrome_frame_resize_hosted.html
@@ -0,0 +1,48 @@
+<html>

+<!-- This page is meant to load inside ChromeFrame -->

+<head>

+

+<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script>

+<script type="text/javascript">

+

+function onLoad() {

+  var host = window.externalHost;

+  if (host) {

+    host.postMessage(

+        "ChromeFrame navigated to: " + window.location);

+  } else {

+    appendStatus("Running in non-hosted mode");

+  }

+}

+

+var resize_event_counter = 0;

+

+function OnResizeEvent() {

+  width = window.innerWidth;

+  height = window.innerHeight;

+

+  appendStatus(resize_event_counter + ". Resized to (" + width +

+               "," + height + ")");

+

+  var host = window.externalHost;

+  if (host) {

+    host.postMessage(

+        resize_event_counter + ":(" + width + "," + height + ")");

+  } else {

+    appendStatus("window.externalHost is null!");

+  }

+}

+</script>

+</head>

+

+<body onload="onLoad();" bgcolor="#999999" onresize="OnResizeEvent();">

+<div id="description" style="border: 2px solid black; width: 100%">

+  Hosted resize test component.

+</div>

+

+<div id="statusPanel" style="border: 1px solid red; width: 100%">

+Test running....

+</div>

+

+</body>

+</html>

diff --git a/chrome_frame/test/data/chrome_frame_tester_helpers.js b/chrome_frame/test/data/chrome_frame_tester_helpers.js
new file mode 100644
index 0000000..1c914ee
--- /dev/null
+++ b/chrome_frame/test/data/chrome_frame_tester_helpers.js
@@ -0,0 +1,142 @@
+//
+// This script provides some mechanics for testing ChromeFrame
+//
+function onSuccess(name, id) {
+  onFinished(name, id, "OK");
+}
+
+function onFailure(name, id, status) {
+  onFinished(name, id, status);
+}
+
+function getXHRObject(){
+  var XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP',
+                         'Msxml2.XMLHTTP.4.0'];
+  var http = null;
+  try { 
+    http = new XMLHttpRequest(); 
+  } catch(e) {
+  }
+  
+  if (http)
+    return http;
+  
+  for (var i = 0; i < 3; ++i) {
+    var progid = XMLHTTP_PROGIDS[i];
+    try {
+      http = new ActiveXObject(progid);
+    } catch(e) {
+    }
+  
+    if (http)
+      break;
+  }
+  return http;
+}
+
+var reportURL = "/writefile/";
+
+function shutdownServer() {
+  var xhr = getXHRObject();
+  if(!xhr)
+    return;
+
+  xhr.open("POST", "/kill", false);
+  try {
+    xhr.send(null);
+  } catch(e) {
+    appendStatus("XHR send failed. Error: " + e.description);
+  }
+}
+
+// Optionally send the server a notification that onload was fired.
+// To be called from within window.onload.
+function sendOnLoadEvent() {
+  writeToServer("OnLoadEvent", "loaded");
+}
+
+function writeToServer(name, result) {
+  var xhr = getXHRObject();
+  if(!xhr)
+    return;
+
+  // synchronously POST the results
+  xhr.open("POST", reportURL + name, false);
+  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+  try {
+    xhr.send(result);
+  } catch(e) {
+    appendStatus("XHR send failed. Error: " + e.description);
+  }
+}
+  
+function postResult(name, result) {
+  writeToServer(name, result);
+  // NOTE:
+  //  not watching for failure or return status issues. What should we do here?
+  shutdownServer();
+}
+
+// Finish running a test by setting the status 
+// and the cookie.
+function onFinished(name, id, result) {
+  appendStatus(result);
+
+  // set a cookie to report the results...
+  var cookie = name + "." + id + ".status=" + result + "; path=/";
+  document.cookie = cookie;
+  
+  // ...and POST the status back to the server
+  postResult(name, result);
+}
+
+function appendStatus(message) {
+  var statusPanel = document.getElementById("statusPanel");
+  if (statusPanel) {
+    statusPanel.innerHTML += '<BR>' + message;
+  }
+}
+
+function readCookie(name) {
+  var cookie_name = name + "=";
+  var ca = document.cookie.split(';');
+
+  for(var i = 0 ; i < ca.length ; i++) {
+    var c = ca[i];
+    while (c.charAt(0) == ' ') {
+      c = c.substring(1,c.length);
+    }
+    if (c.indexOf(cookie_name) == 0) {
+      return c.substring(cookie_name.length, c.length);
+    }
+  }
+  return null;
+}
+
+function createCookie(name,value,days) {
+  var expires = "";
+  if (days) {
+    var date = new Date();
+    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
+    expires = "; expires=" + date.toGMTString();
+  }
+  document.cookie = name+"="+value+expires+"; path=/";
+}
+
+function eraseCookie(name) {
+  createCookie(name, "", -1);
+}
+
+function isRunningInMSIE() {
+  if (/MSIE (\d+\.\d+);/.test(navigator.userAgent))
+      return true;
+
+  return false;
+}
+
+function reloadUsingCFProtocol() {
+  var redirect_location = "cf:";
+  redirect_location += window.location;
+  window.location = redirect_location;
+}
+
diff --git a/chrome_frame/test/data/event_listener.html b/chrome_frame/test/data/event_listener.html
new file mode 100644
index 0000000..6fbd1586
--- /dev/null
+++ b/chrome_frame/test/data/event_listener.html
@@ -0,0 +1,42 @@
+<html>
+<!-- This page is meant to load inside the host browser like IE/FF -->
+<head>
+  <script type="text/javascript" src="chrome_frame_tester_helpers.js"></script>
+  <script language="javascript">
+  var g_test_name = 'EventListener';
+
+  function onChromeFrameLoaded() {
+    appendStatus('Chrome frame loaded.');
+    onSuccess(g_test_name, 1);
+  }
+
+  function onEventNotFired() {
+    onFailure(g_test_name, 1, 'Did not receive onload event');
+  }
+
+  function onDocumentLoad() {
+    appendStatus('document loaded');
+    var cf = getCf();
+    cf.addEventListener("load", onChromeFrameLoaded, false);
+    setTimeout(onEventNotFired, 10000)
+    cf.src = "CFInstance_basic_frame.html";
+  }
+
+  function getCf() {
+    return window.document.ChromeFrame;
+  }
+  </script>
+</head>
+<body onload="onDocumentLoad();">
+  <object id="ChromeFrame" width="500" height ="300"
+      codebase="http://www.google.com"
+      classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+    <embed id="ChromeFramePlugin" name="ChromeFrame" width="500"
+        height="500" type="application/chromeframe">
+    </embed>
+  </object>
+  <div id="statusPanel" style="border: 1px solid red; width: 100%">
+    Test running....
+  </div>
+</body>
+</html>
diff --git a/chrome_frame/test/data/iframe_basic_host.html b/chrome_frame/test/data/iframe_basic_host.html
new file mode 100644
index 0000000..f9a4c0c3
--- /dev/null
+++ b/chrome_frame/test/data/iframe_basic_host.html
@@ -0,0 +1,13 @@
+<!-- saved from url=(0014)about:internet -->
+<html>
+<!-- This page is meant to load inside the host browser like IE/FF -->
+<head></head>
+<body>
+  <iframe src ="postmessage_basic_host.html" width="50%" height="600">

+  </iframe>
+
+  <br>
+  <br>
+  Test for ChromeFrame inside iframe
+</body>
+</html>
diff --git a/chrome_frame/test/data/in_head.html b/chrome_frame/test/data/in_head.html
new file mode 100644
index 0000000..a093c717
--- /dev/null
+++ b/chrome_frame/test/data/in_head.html
@@ -0,0 +1,62 @@
+<html>
+  <!-- This page is meant to load inside the host browser like IE/FF -->
+  <head><title>Initialize hidden chrome frame</title>
+    <script type="text/javascript" src="chrome_frame_tester_helpers.js">
+    </script>
+    <script type="text/javascript">
+      var g_failure_timeout = null;
+      var g_test_id = 1;
+      var g_test_name = 'InHead';
+      var g_cf3_loaded = false;
+
+      function OnNavigationFailed() {
+        onFailure(g_test_name, g_test_id, 'ChromeFrame Navigation failed');
+      }
+
+      function OnObjectFocusFailed() {
+        appendStatus('chrome frame focus failed');
+        onFailure(g_test_name, g_test_id, 'Embed in head test failed');
+      }
+
+      function OnFrameMessage(evt) {
+        appendStatus('Chrome frame visible and focused');
+        if (evt.data == 'btnOnFocus') {
+          window.clearTimeout(g_failure_timeout);
+          g_failure_timeout = null;
+          appendStatus('Chrome frame visible and focused');
+
+          onSuccess(g_test_name, g_test_id);
+        }
+      }
+
+    function OnFrameLoad() {
+      document.ChromeFrame.focus();
+    }
+
+    function OnLoad() {
+      g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000);
+    }
+    </script>
+    <object id="ChromeFrame" width="300" height="80" tabindex="0"
+            codebase="http://www.google.com"
+            classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+      <param name="src" value="simple_object_focus_cf.html">
+      <param name="onload" value="OnFrameLoad();">
+      <param name="onloaderror" value="OnNavigationFailed();">
+      <param name="onmessage" value="OnFrameMessage(arguments[0]);">
+      <embed width="300" height="80" name="ChromeFrame"
+             type="application/chromeframe"
+             src="simple_object_focus_cf.html"
+             onload="OnFrameLoad();"
+             onloaderror="OnNavigationFailed();"
+             onmessage="OnFrameMessage(arguments[0]);">
+      </embed>
+    </object>
+  </head>
+  <body onload = "OnLoad();">
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+    Test running....
+    </div>
+    <div id = "dummy"> </div>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/initialize_hidden.html b/chrome_frame/test/data/initialize_hidden.html
new file mode 100644
index 0000000..2da0917
--- /dev/null
+++ b/chrome_frame/test/data/initialize_hidden.html
@@ -0,0 +1,120 @@
+<html>
+  <!-- This page is meant to load inside the host browser like IE/FF -->
+  <head><title>Initialize hidden chrome frame</title>
+    <script type="text/javascript" src="chrome_frame_tester_helpers.js">
+    </script>
+    <script type="text/javascript">
+      var g_failure_timeout = null;
+      var g_test_id = 1;
+      var g_test_name = 'InitializeHidden';
+      var g_cf3_loaded = false;
+
+      function OnNavigationFailed() {
+        onFailure(g_test_name, g_test_id, 'ChromeFrame Navigation failed');
+      }
+
+      function OnObjectFocusFailed() {
+        appendStatus('chrome frame focus failed');
+        onFailure(g_test_name, g_test_id, 'Visibility test failed');
+      }
+
+      function OnCF1Loaded() {
+        appendStatus('Chrome frame 1 loaded, not visible yet.');
+        try {
+          // Make chrome frame visible
+          var cf1 = document.getElementById('CFDiv1');
+          cf1.style.visibility = 'visible';
+          appendStatus('Chrome frame 1 visibility - ' + cf1.style.visibility);
+          // Set focus to chrome frame.  This should set focus to the
+          // first element inside the frame, which a script inside the
+          // page will detect and notify us back by sending us a message.
+          document.ChromeFrame1.focus();
+          g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000);
+        } catch(e) {
+          appendStatus('Error setting focus to CF1. Error: ' + e.description);
+          onFailure(g_test_name, g_test_id, 'CF1 focus() error');
+        }
+      }
+
+      function OnCF1Message(evt) {
+        if (evt.data == 'btnOnFocus') {
+          window.clearTimeout(g_failure_timeout);
+          g_failure_timeout = null;
+          appendStatus('CF1 visible and focused');
+
+          // Now make second chrome frame instance visible
+          document.getElementById('CFDiv2').style.display = 'block';
+          appendStatus('Chrome frame 2 visible, should start loading now');
+          g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000);
+        }
+      }
+
+      function OnCF2Loaded() {
+        appendStatus('Chrome frame 2 loaded');
+        try {
+          // Set focus to chrome frame.  This should set focus to the
+          // first element inside the frame, which a script inside the
+          // page will detect and notify us back by sending us a message.
+          // We set focus on a timeout as on IE it takes a while for the window
+          // to become visible.
+          setTimeout(SetFocusToCF2, 100);
+        } catch(e) {
+          appendStatus('Error setting focus to CF2. Error: ' + e.description);
+          onFailure(g_test_name, g_test_id, 'CF2 focus() error');
+        }
+      }
+      
+      function SetFocusToCF2() {
+        document.ChromeFrame2.focus();
+      }
+
+      function OnCF2Message(evt) {
+        if (evt.data == 'btnOnFocus') {
+          window.clearTimeout(g_failure_timeout);
+          g_failure_timeout = null;
+          appendStatus('CF2 visible and focused');
+          onSuccess(g_test_name, g_test_id);
+        }
+      }
+    </script>
+  </head>
+  <body>
+    <div id="CFDiv1" style="visibility: hidden;">
+      <object id="ChromeFrame1" width="300" height="80" tabindex="0"
+              codebase="http://www.google.com"
+              classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+        <param name="src" value="simple_object_focus_cf.html">
+        <param name="onload" value="OnCF1Loaded();">
+        <param name="onloaderror" value="OnNavigationFailed();">
+        <param name="onmessage" value="OnCF1Message(arguments[0]);">
+        <embed width="300" height="80" name="ChromeFrame1"
+               type="application/chromeframe"
+               src="simple_object_focus_cf.html"
+               onload="OnCF1Loaded();"
+               onloaderror="OnNavigationFailed();"
+               onmessage="OnCF1Message(arguments[0]);">
+        </embed>
+      </object>
+    </div>
+    <div id="CFDiv2" style="display: none;">
+      <object id="ChromeFrame2" width="300" height="80" tabindex="1"
+              codebase="http://www.google.com"
+              classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+        <param name="src" value="simple_object_focus_cf.html">
+        <param name="onload" value="OnCF2Loaded();">
+        <param name="onloaderror" value="OnNavigationFailed();">
+        <param name="onmessage" value="OnCF2Message(arguments[0]);">
+        <embed width="300" height="80" name="ChromeFrame2"
+               type="application/chromeframe"
+               src="simple_object_focus_cf.html"
+               onload="OnCF2Loaded();"
+               onloaderror="OnNavigationFailed();"
+               onmessage="OnCF2Message(arguments[0]);">
+        </embed>
+      </object>
+    </div>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+    Test running....
+    </div>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/meta_tag.html b/chrome_frame/test/data/meta_tag.html
new file mode 100644
index 0000000..9b174062
--- /dev/null
+++ b/chrome_frame/test/data/meta_tag.html
@@ -0,0 +1,21 @@
+<html>

+  <head>

+    <meta http-equiv="x-ua-compatible" content="chrome=1" />
+    <title>Load chrome frame using meta tag</title>

+    <script type="text/javascript"

+        src="chrome_frame_tester_helpers.js"></script>

+

+    <script type="text/javascript">

+      function test() {

+        if (isRunningInMSIE()) {

+          onFailure("meta_tag", 1, "Failed");

+        } else {

+          onSuccess("meta_tag", 1);

+        }

+      }

+    </script>

+  </head>

+  <body onLoad="setTimeout(test, 100);">
+    chrome trame in tab mode
+  </body>
+</html>
diff --git a/chrome_frame/test/data/navigate_out.html b/chrome_frame/test/data/navigate_out.html
new file mode 100644
index 0000000..7b910b4
--- /dev/null
+++ b/chrome_frame/test/data/navigate_out.html
@@ -0,0 +1,20 @@
+<html>

+  <head><title>Test to make sure that navigations sent back to IE</title>

+    <script type="text/javascript"

+        src="chrome_frame_tester_helpers.js"></script>

+

+    <script type="text/javascript">

+      function test() {

+        if (isRunningInMSIE()) {

+          reloadUsingCFProtocol();

+        } else {

+          window.location = "back_to_ie.html";

+        }

+      }

+    </script>

+  </head>

+  <body onLoad="setTimeout(test, 100);">

+    <h2>Prepare to be redirected!</h2>

+    <p>Redirects the same page to its 'cf:' version and </p>

+  </body>

+</html>

diff --git a/chrome_frame/test/data/navigateurl_absolute_host.html b/chrome_frame/test/data/navigateurl_absolute_host.html
new file mode 100644
index 0000000..03e1de2
--- /dev/null
+++ b/chrome_frame/test/data/navigateurl_absolute_host.html
@@ -0,0 +1,64 @@
+<html>
+<!-- This page is meant to load inside the host browser like IE/FF -->
+  <head>
+    <script type="text/javascript" src="chrome_frame_tester_helpers.js">
+    </script>
+    <script type="text/javascript">
+      function onLoad() {
+        var chromeFrame = GetChromeFrame();
+        chromeFrame.onloaderror = OnNavigationFailed;
+        setTimeout(NavigateToURL, 100);
+      }
+
+      function NavigateToURL() {
+        var frame_location = new String(window.location);
+        frame_location = frame_location.replace(
+            /navigateurl_absolute_host.html/, "navigateurl_basic_frame.html");
+        var chromeFrame = GetChromeFrame();
+        chromeFrame.src = frame_location;
+        setTimeout(OnNavigationTimeout, 10000);
+      }
+
+      var navigation_success = 0;
+
+      function OnNavigationFailed(msg) {
+        if (!navigation_success) {
+          onFailure("NavigateURL", 1, 'ChromeFrame Navigation failed: ' + msg);
+        }
+      }
+      
+      function OnNavigationTimeout() {
+        OnNavigationFailed('TIMEOUT');
+      }
+
+      function OnChromeFrameLoaded() {
+        navigation_success = 1;
+        onSuccess("NavigateURL", 1);
+      }
+
+      function GetChromeFrame() {
+        return window.document.ChromeFrame;
+      }
+    </script>
+  </head>
+
+  <body onload="onLoad();">
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+    Test running....
+    </div>
+    <object id="ChromeFrame" width="500" height="500"
+        codebase="http://www.google.com"
+        classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+        <param name="onload" value="return OnChromeFrameLoaded();">
+        <embed id="ChromeFramePlugin" width="500" height="500"
+               name="ChromeFrame" onload="return OnChromeFrameLoaded();"
+               type="application/chromeframe">
+        </embed>
+    </OBJECT>
+    <br />
+    <br />
+
+    <p>Tests ChromeFrame Navigation</p>
+
+  </body>
+</html>
diff --git a/chrome_frame/test/data/navigateurl_basic_frame.html b/chrome_frame/test/data/navigateurl_basic_frame.html
new file mode 100644
index 0000000..4d99b43
--- /dev/null
+++ b/chrome_frame/test/data/navigateurl_basic_frame.html
@@ -0,0 +1,12 @@
+<html>
+<!-- This page is meant to load inside ChromeFrame -->
+
+<body>
+<div id="statusPanel" style="border: 1px solid red; width: 100%">
+Test running....
+</div>
+
+<p>ChromeFrame NavigateURL Test<br>
+Tests ChromeFrame Navigation</p>
+</body>
+</html>
diff --git a/chrome_frame/test/data/navigateurl_relative_host.html b/chrome_frame/test/data/navigateurl_relative_host.html
new file mode 100644
index 0000000..06ec63e9
--- /dev/null
+++ b/chrome_frame/test/data/navigateurl_relative_host.html
@@ -0,0 +1,60 @@
+<html>
+  <!-- This page is meant to load inside the host browser like IE/FF -->
+  <head>
+    <script type="text/javascript" src="chrome_frame_tester_helpers.js">
+    </script>
+    <script type="text/javascript">
+      function onLoad() {
+        var chromeFrame = GetChromeFrame();
+        chromeFrame.onloaderror = OnNavigationFailed;
+        setTimeout(NavigateToURL, 100);
+      }
+
+      function NavigateToURL() {
+        var chromeFrame = GetChromeFrame();
+        chromeFrame.src = "navigateurl_basic_frame.html";
+        setTimeout(OnNavigationTimeout, 10000);
+      }
+
+      var navigation_complete = 0;
+
+      function OnNavigationFailed(msg) {
+        if (!navigation_complete) {
+          onFailure("NavigateURL", 1, 'ChromeFrame Navigation failed: ' + msg);
+        }
+      }
+      
+      function OnNavigationTimeout() {
+        OnNavigationFailed('TIMEOUT');
+      }
+
+      function OnChromeFrameLoaded() {
+        navigation_success = 1;
+        onSuccess("NavigateURL", 1);
+      }
+
+      function GetChromeFrame() {
+        return window.document.ChromeFrame;
+      }
+    </script>
+  </head>
+
+  <body onload="onLoad();">
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+    Test running....
+    </div>
+    <object id="ChromeFrame" width="500" height="500"
+        codebase="http://www.google.com"
+        classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+        <param name="onload" value="return OnChromeFrameLoaded();">
+        <embed id="ChromeFramePlugin" width="500" height="500"
+            name="ChromeFrame" onload="return OnChromeFrameLoaded();"
+            type="application/chromeframe">
+        </embed>
+    </OBJECT>
+    <br />
+    <br />
+
+    <p>Tests ChromeFrame Navigation</p>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/persistent_cookie_test_page.html b/chrome_frame/test/data/persistent_cookie_test_page.html
new file mode 100644
index 0000000..ea56262
--- /dev/null
+++ b/chrome_frame/test/data/persistent_cookie_test_page.html
@@ -0,0 +1,36 @@
+<html>

+  <head><title>Persistent host browser chrome frame cookie test</title>

+    <script type="text/javascript"

+    src="chrome_frame_tester_helpers.js"></script>

+

+    <script type="text/javascript">

+      function validatePersistentCookie() {

+        if (readCookie("PersistentCookie1") != "Cookie1" ||

+            readCookie("PersistentCookie2") != "Cookie2") {

+          onFailure("PersistentCookieTest", 1, "Failed");

+        } else {

+          onSuccess("PersistentCookieTest", 1);

+        }

+        eraseCookie("PersistentCookie1");

+        eraseCookie("PersistentCookie2");

+      }

+

+      function setPersistentCookieAndRedirect() {

+        if (isRunningInMSIE()) {

+          eraseCookie("PersistentCookie1");

+          eraseCookie("PersistentCookie2");

+          createCookie("PersistentCookie1", "Cookie1", 365);

+          createCookie("PersistentCookie2", "Cookie2", 365);

+          reloadUsingCFProtocol();

+        } else {

+          validatePersistentCookie();

+        }

+      }

+    </script>

+  </head>

+  <body onLoad="setTimeout(setPersistentCookieAndRedirect, 100);">

+    <h2>Prepare to be redirected!</h2>

+    <p>Sets two persistent cookies in the host and redirects ChromeFrame <br />

+       to the same page </p>

+  </body>

+</html>

diff --git a/chrome_frame/test/data/postmessage_basic_frame.html b/chrome_frame/test/data/postmessage_basic_frame.html
new file mode 100644
index 0000000..76f8cb3
--- /dev/null
+++ b/chrome_frame/test/data/postmessage_basic_frame.html
@@ -0,0 +1,27 @@
+<!-- saved from url=(0014)about:internet -->

+<html>

+<!-- This page is meant to load inside ChromeFrame -->

+  <head>

+    <script type="text/javascript" src="chrome_frame_tester_helpers.js">
+    </script>
+    <script type="text/javascript">

+      function OnLoad() {
+        externalHost.onmessage = OnHostMessage;
+      }

+

+      function OnHostMessage(evt) {

+        appendStatus('Host message: ' + evt.data);
+        externalHost.postMessage("Hello from ChromeFrame");

+      }

+    </script>

+  </head>

+

+  <body onload="OnLoad();">

+    <div id="statusPanel" style="border: 1px solid red; width: 100%">

+      Test running....

+    </div>

+

+    <p>ChromeFrame PostMessage Test

+    <br>Test for PostMessage from the host browser to ChromeFrame and back</p>

+  </body>

+</html>

diff --git a/chrome_frame/test/data/postmessage_basic_host.html b/chrome_frame/test/data/postmessage_basic_host.html
new file mode 100644
index 0000000..e5ecef93
--- /dev/null
+++ b/chrome_frame/test/data/postmessage_basic_host.html
@@ -0,0 +1,69 @@
+<!-- saved from url=(0014)about:internet -->
+<html>
+<!-- This page is meant to load inside the host browser like IE/FF -->
+  <head>
+    <script type="text/javascript" src="chrome_frame_tester_helpers.js">
+    </script>
+    <script type="text/javascript">
+      var post_message_reply_received = 0;
+
+      function onChromeFrameLoaded() {
+        appendStatus('Chrome frame loaded...');
+        document.ChromeFrame.postMessage('Hello from host');
+        setTimeout(onPostMessageFailure, 10000);
+      }
+
+      function onNavigationFailed(msg) {
+        onFailure('PostMessage', 1, 'ChromeFrame Navigation failed: ' + msg);
+      }
+
+      function onChromeFrameMessage(evt) {
+        try {
+          var d = new String(evt.data);
+          appendStatus('Message: ' + d);
+          if (d == 'Hello from ChromeFrame') {
+            post_message_reply_received = 1;
+            onSuccess('PostMessage', 1);
+          } else {
+            onFailure('PostMessage', 1, 'unexpected data');
+          }
+        } catch (e) {
+          onFailure('PostMessage', 1, 'exception in onChromeFrameMessage');
+        }
+      }
+
+      function onPostMessageFailure() {
+        if (!post_message_reply_received) {
+          onFailure('PostMessage', 1, 'Did not receive reply back from frame');
+        }
+      }
+    </script>
+  </head>
+
+  <body>
+    <object id="ChromeFrame" width="500" height ="300"
+        codebase="http://www.google.com"
+        classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+        <param name="src" value="postmessage_basic_frame.html">
+        <param name="onload" value="onChromeFrameLoaded();">
+        <param name="onloaderror" value="onNavigationFailed();">
+        <param name="onmessage" value="onChromeFrameMessage(arguments[0]);">
+        <embed id="ChromeFramePlugin" name="ChromeFrame"
+            width="500" height="500"
+            src="postmessage_basic_frame.html"
+            type="application/chromeframe"
+            onload="onChromeFrameLoaded();"
+            onloaderror="onNavigationFailed();"
+            onmessage="onChromeFrameMessage(arguments[0]);">
+        </embed>
+    </object>
+    <br>
+    <br>
+    <p>Test for PostMessage from the host browser to ChromeFrame and back</p>
+    <button onclick="document.ChromeFrame.postMessage('Message from button');">
+      Send message to frame</button>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/privileged_apis_frame.html b/chrome_frame/test/data/privileged_apis_frame.html
new file mode 100644
index 0000000..9e51152d
--- /dev/null
+++ b/chrome_frame/test/data/privileged_apis_frame.html
@@ -0,0 +1,33 @@
+<!-- saved from url=(0014)about:internet -->
+<html>
+<!-- This page is meant to load inside ChromeFrame -->
+  <head>
+    <script type="text/javascript" src="chrome_frame_tester_helpers.js">
+    </script>
+    <script type="text/javascript">
+      function OnLoad() {
+        externalHost.onmessage = OnHostMessage;
+      }
+
+      function OnHostMessage(evt) {
+        // Any time we receive a message, we reflect it back both
+        // with a nonsensical target, and with "*".
+        appendStatus('Host message: ' + evt.data);
+        externalHost.postMessage(evt.data,
+                                 "privileged_target");
+        appendStatus('After postMessage(' + evt.data + ', "privileged_target)"');
+        externalHost.postMessage(evt.data);
+        appendStatus('After postMessage(' + evt.data + '")');
+      }
+    </script>
+  </head>
+
+  <body onload="OnLoad();">
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+
+    <p>ChromeFrame PrivilegeApis Test
+    <br>Tests that private messaging is not available to regular web pages</p>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/privileged_apis_host.html b/chrome_frame/test/data/privileged_apis_host.html
new file mode 100644
index 0000000..72617043
--- /dev/null
+++ b/chrome_frame/test/data/privileged_apis_host.html
@@ -0,0 +1,85 @@
+<html>
+  <head><title>Privileged Apis test</title>
+    <script type='text/javascript' src='chrome_frame_tester_helpers.js'>
+    </script>
+    <script type='text/javascript'>
+      var testName = 'PrivilegedApis';
+      function OnNavigationFailed(msg) {
+        onFailure(testName, 1, 'ChromeFrame Navigation failed: ' + msg);
+      }
+
+      function OnPrivateMessage() {
+        onFailure(testName, 1, 'OnPrivateMessage should not execute');
+      }
+
+      function OnChromeFrameMessage(evt) {
+        try {
+          var d = new String(evt.data);
+          appendStatus('Message: ' + d);
+          if (d == 'succeed') {
+            onSuccess(testName, 1);
+          } else {
+            onFailure(testName, 1, 'unexpected data');
+          }
+        } catch (e) {
+          onFailure(testName, 1, 'exception in OnChromeFrameMessage');
+        }
+      }
+      
+      function OnChromeFrameLoaded(url) {
+        var cf = GetChromeFrame();
+        
+        try {
+          // Any message received by this listener is a failure.
+          // This succeeds in FF, but throws an exception in IE.
+          cf.addEventListener('onprivatemessage', OnPrivateMessage, false);
+        } catch(e) {
+          cf.onprivatemessage = 
+          appendStatus('addEventListener onprivatemessage threw exception')
+        }
+
+        // If this invocation succeeds, then 'fail' is reflected by the frame
+        // and we fail in the OnChromeFrameMessage handler above.
+        try {
+          cf.postPrivateMessage('fail', String(document.location), '*');
+          onFailure(testName, 1, 'postPrivateMessage should throw');
+        } catch(e) {
+        }
+        appendStatus('After postPrivateMessage')
+        // The frame reflects this twice, first to a bogus target 
+        // and again to the default target '*'. We succeed if we 
+        // get the reflected message to OnChromeFrameMessage and not to
+        // OnPrivateMessage.
+        cf.postMessage('succeed');
+      }
+
+      function GetChromeFrame() {
+        return window.document.ChromeFrame;
+      }
+    </script>
+  </head>
+  <body>
+      <div id='statusPanel' style='border: 1px solid red; width: 100%'>
+      Test running....
+      </div>
+
+      <!-- TODO(siggi): Test setting onprivatemessage in these params -->
+      <object id='ChromeFrame' width='500' height='500'
+          codebase='http://www.google.com'
+          classid='CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A'>
+          <param name='src' value='privileged_apis_frame.html'>
+          <param name='onload' value='OnChromeFrameLoaded(arguments[0]);'>
+          <param name='onloaderror' value='OnNavigationFailed();'>
+          <param name='onmessage' value='OnChromeFrameMessage(arguments[0]);'>
+          <embed id='ChromeFramePlugin' width='500' height='500' name='ChromeFrame'
+              src='privileged_apis_frame.html'
+              type='application/chromeframe'
+              onload='OnChromeFrameLoaded(arguments[0]);'
+              onloaderror='OnNavigationFailed();'
+              onmessage='return OnChromeFrameMessage(arguments[0]);'
+              privileged_mode='1'
+          </embed>
+      </object>
+      <p>Tests that privileged apis are unavailable from regular pages</p>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/simple_object_focus.html b/chrome_frame/test/data/simple_object_focus.html
new file mode 100644
index 0000000..138ffa5
--- /dev/null
+++ b/chrome_frame/test/data/simple_object_focus.html
@@ -0,0 +1,95 @@
+<!-- saved from url=(0014)about:internet -->
+<html>
+<!-- This page is meant to load inside the host browser like IE/FF -->
+<head>
+<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script>
+<script type="text/javascript">
+var g_failure_timeout = null;
+var g_test_id = 1;
+var g_test_name = "ObjectFocus";
+
+function onLoad() {
+  status("onload");
+
+  try {
+    var cf = getCf();
+    cf.onmessage = OnChromeFrameMessage;
+    window.setTimeout(NavigateToURL, 100);
+  } catch(e) {
+    status("error: onload");
+    onFailure(g_test_name, g_test_id, "error in onload");
+  }
+
+  sendOnLoadEvent();
+}
+
+function NavigateToURL() {
+  try {
+    status("Navigate to URL");
+    var cf = getCf();
+    cf.src = "simple_object_focus_cf.html";
+    g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000);
+  } catch(e) {
+    status("error: NavigateToURL");
+    onFailure(g_test_name, g_test_id, "NavigateToURL error");
+  }
+}
+
+function OnObjectFocusFailed() {
+  status("OnNavigationFailed");
+  onFailure(g_test_name, g_test_id, "focus test failed");
+}
+
+function OnChromeFrameLoaded() {
+  status("OnChromeFrameLoaded");
+  try {
+    // Set focus to chrome frame.  This should set focus to the first element
+    // inside the frame, which a script inside the page will detect and notify
+    // us back by sending us a message.
+    getCf().focus();
+  } catch(e) {
+    status("error: can't set focus");
+    onFailure(g_test_name, g_test_id, "focus() error");
+  }
+}
+
+function OnChromeFrameMessage(evt) {
+  if (evt.data != "btnOnFocus") {
+    status("unexpected message: " + evt.data + " from " + evt.origin);
+  } else {
+    window.clearTimeout(g_failure_timeout);
+    g_failure_timeout = null;
+    status("success");
+  }
+  onSuccess(g_test_name, g_test_id);
+}
+
+function getCf() {
+  // Fetching chrome frame with getElementById doesn't work in Firefox.
+  // Most likely due to object vs embed.
+  return document.ChromeFrame;
+}
+
+// Useful while writing and debugging the unit test.
+function status(s) {
+  var panel = document.getElementById("status_panel");
+  panel.innerHTML = s;
+}
+
+</script>
+</head>
+<body onload="onLoad();">
+<div id="status_panel" style="border: 1px solid red; width: 100%">
+Test running....
+</div>
+<object id="ChromeFrame" width="300" height="60" tabindex="0"
+        codebase="http://www.google.com"
+        classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+  <param name="onload" value="return OnChromeFrameLoaded();">

+  <embed width="300" height="60" name="ChromeFrame"
+         onload="return OnChromeFrameLoaded();"
+         type="application/chromeframe">
+  </embed>
+</object>
+</body>
+</html>
diff --git a/chrome_frame/test/data/simple_object_focus_cf.html b/chrome_frame/test/data/simple_object_focus_cf.html
new file mode 100644
index 0000000..9b067111
--- /dev/null
+++ b/chrome_frame/test/data/simple_object_focus_cf.html
@@ -0,0 +1,10 @@
+<html>
+<!-- This page is meant to load inside Chrome Frame -->
+  <body>
+    <button onfocus="externalHost.postMessage('btnOnFocus');">
+      hello world</button>
+    <div id="statusPanel" style="border: 1px solid green; width: 100%">
+    Inside Chrome Frame....
+    </div>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/src_property_frame1.html b/chrome_frame/test/data/src_property_frame1.html
new file mode 100644
index 0000000..1eaa3cf
--- /dev/null
+++ b/chrome_frame/test/data/src_property_frame1.html
@@ -0,0 +1,13 @@
+<html>

+  <head><title>src property test - page 1</title>

+    <script type="text/javascript">

+      function redirect(){

+        window.location = "src_property_frame2.html";

+      }

+    </script>

+  </head>

+  <body onLoad="setTimeout(redirect, 100);">

+    <h2>Prepare to be redirected!</h2>

+    <p>Redirecting to a new page within frame...</p>

+  </body>

+</html>
\ No newline at end of file
diff --git a/chrome_frame/test/data/src_property_frame2.html b/chrome_frame/test/data/src_property_frame2.html
new file mode 100644
index 0000000..c5c0364
--- /dev/null
+++ b/chrome_frame/test/data/src_property_frame2.html
@@ -0,0 +1,8 @@
+<html>

+  <head><title>src property test - page 2</title>

+  </head>

+  <body>

+    <h2>Redirected!</h2>

+    <p>All finished.</p>

+  </body>

+</html>
\ No newline at end of file
diff --git a/chrome_frame/test/data/src_property_host.html b/chrome_frame/test/data/src_property_host.html
new file mode 100644
index 0000000..7b7b358
--- /dev/null
+++ b/chrome_frame/test/data/src_property_host.html
@@ -0,0 +1,65 @@
+<html>

+  <head><title>src property test</title>
+    <script type="text/javascript" src="chrome_frame_tester_helpers.js">
+    </script>
+    <script type="text/javascript">
+      function OnNavigationFailed() {
+        onFailure("ChromeFrame_SrcTest", 1, "ChromeFrame Navigation failed");
+      }
+
+      var load_count = 2;
+
+      function OnChromeFrameLoaded(url) {
+        url = url.data;
+
+        var chromeFrame = GetChromeFrame();
+        var frame_url = chromeFrame.src;
+        
+        appendStatus("Loaded URL: " + url + " Frame url: " + frame_url);
+        load_count--;
+
+        if (load_count) {
+          // For the first load, the URLs should match.
+          if (frame_url != url) {
+            onFailure("SrcProperty", 1, "Url: " + url);
+          }
+        } else {
+          // Previous versions changed the frame URL when internal navigation
+          // was performed. This does not match how iframes behave, and so we
+          // report success only in the case that they continue to match, even
+          // though the "internal" URL is different (and not visible) to the
+          // external host.
+          if (frame_url == url) {
+            onFailure("SrcProperty", 1, "Url: " + url);
+          } else {
+            onSuccess("SrcProperty", 1);
+          }
+        }
+      }
+
+      function GetChromeFrame() {
+        return window.document.ChromeFrame;
+      }
+    </script>
+  </head>
+  <body>
+      <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+      </div>
+
+      <object id="ChromeFrame" width="500" height="500"
+          codebase="http://www.google.com"
+          classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+          <param name="src" value="src_property_frame1.html">
+          <param name="onload" value="return OnChromeFrameLoaded(arguments[0]);">
+          <param name="onloaderror" value="return OnNavigationFailed(arguments[0]);">
+          <embed id="ChromeFramePlugin" width="500" height="500" name="ChromeFrame"
+              src="src_property_frame1.html"
+              type="application/chromeframe"
+              onload="return OnChromeFrameLoaded(arguments[0]);"
+              onloaderror="return OnNavigationFailed(arguments[0]);">
+          </embed>
+      </object>
+      <p>Tests ChromeFrame Navigation</p>
+  </body>
+</html>
diff --git a/chrome_frame/test/data/version.html b/chrome_frame/test/data/version.html
new file mode 100644
index 0000000..b585a6d
--- /dev/null
+++ b/chrome_frame/test/data/version.html
@@ -0,0 +1,29 @@
+<html>
+<!-- This page is meant to load inside the host browser like IE/FF -->
+  <head>
+    <script type="text/javascript" src="chrome_frame_tester_helpers.js">
+    </script>
+    <script type="text/javascript">
+    function onLoad() {
+      appendStatus('Chrome frame version: ' + document.ChromeFrame.version);
+      onFinished('version', 1, document.ChromeFrame.version);
+    }
+    </script>
+  </head>
+
+  <body onload="onLoad();">
+    <object id="ChromeFrame"
+        codebase="http://www.google.com"
+        classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
+        <embed id="ChromeFramePlugin" name="ChromeFrame"
+            type="application/chromeframe"
+        </embed>
+    </object>
+    <br>
+    <br>
+    <p>Test for Chrome frame version</p>
+    <div id="statusPanel" style="border: 1px solid red; width: 100%">
+      Test running....
+    </div>
+  </body>
+</html>
diff --git a/chrome_frame/test/function_stub_unittest.cc b/chrome_frame/test/function_stub_unittest.cc
new file mode 100644
index 0000000..6ef6f36
--- /dev/null
+++ b/chrome_frame/test/function_stub_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/function_stub.h"
+
+#include "chrome_frame/test/chrome_frame_unittests.h"
+
+#define NO_INLINE __declspec(noinline)
+
+namespace {
+
+typedef int (__stdcall* FooPrototype)();
+
+NO_INLINE int __stdcall Foo() {
+  return 1;
+}
+
+NO_INLINE int __stdcall PatchedFoo(FooPrototype original) {
+  return original() + 1;
+}
+
+}  // end namespace
+
+TEST(PatchTests, FunctionStub) {
+  EXPECT_EQ(Foo(), 1);
+  // Create a function stub that calls PatchedFoo and supplies it with
+  // a pointer to Foo.
+  FunctionStub* stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(&Foo),
+                                            &PatchedFoo);
+  EXPECT_TRUE(stub != NULL);
+  // Call the stub as it were Foo().  The call should get forwarded to Foo().
+  FooPrototype patch = reinterpret_cast<FooPrototype>(stub->code());
+  EXPECT_EQ(patch(), 2);
+  // Now neutralize the stub so that it calls Foo() directly without touching
+  // PatchedFoo().
+  // stub->BypassStub(&Foo);
+  stub->BypassStub(reinterpret_cast<void*>(stub->argument()));
+  EXPECT_EQ(patch(), 1);
+  // We're done with the stub.
+  FunctionStub::Destroy(stub);
+}
+
+// Basic tests to check the validity of a stub.
+TEST(PatchTests, FunctionStubCompare) {
+  EXPECT_EQ(Foo(), 1);
+
+  // Detect the absence of a stub
+  FunctionStub* stub = reinterpret_cast<FunctionStub*>(&Foo);
+  EXPECT_FALSE(stub->is_valid());
+
+  stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(&Foo), &PatchedFoo);
+  EXPECT_TRUE(stub != NULL);
+  EXPECT_TRUE(stub->is_valid());
+
+  FooPrototype patch = reinterpret_cast<FooPrototype>(stub->code());
+  EXPECT_EQ(patch(), 2);
+
+  // See if we can get the correct absolute pointer to the hook function
+  // back from the stub.
+  EXPECT_EQ(stub->absolute_target(), reinterpret_cast<uintptr_t>(&PatchedFoo));
+
+  // Also verify that the argument being passed to the hook function is indeed
+  // the pointer to the original function (again, absolute not relative).
+  EXPECT_EQ(stub->argument(), reinterpret_cast<uintptr_t>(&Foo));
+}
diff --git a/chrome_frame/test/helper_gmock.h b/chrome_frame/test/helper_gmock.h
new file mode 100644
index 0000000..7f6d0a7
--- /dev/null
+++ b/chrome_frame/test/helper_gmock.h
@@ -0,0 +1,597 @@
+// Copyright (c) 2006-2009 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.
+#ifndef CHROME_FRAME_TEST_HELPER_GMOCK_H_
+#define CHROME_FRAME_TEST_HELPER_GMOCK_H_
+// This intention of this file is to make possible gmock WithArgs<> in
+// Chromium code base.
+// MutantImpl is like CallbackImpl, but also has prebound arguments (like Task)
+// There is also functor wrapper around it that should be used with
+// testing::Invoke, for example:
+// testing::WithArgs<0, 2>(
+//    testing::Invoke(CBF(&mock_object, &MockObject::Something, &tmp_obj, 12)));
+// This will invoke MockObject::Something(tmp_obj, 12, arg_0, arg_2)
+
+// DispatchToMethod supporting two sets of arguments -
+// prebound (P) and calltime (C)
+// 1 - 1
+template <class ObjT, class Method, class P1, class C1>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple1<P1>& p,
+                             const Tuple1<C1>& c) {
+  (obj->*method)(p.a, c.a);
+}
+// 2 - 1
+template <class ObjT, class Method, class P1, class P2, class C1>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple2<P1, P2>& p,
+                             const Tuple1<C1>& c) {
+  (obj->*method)(p.a, p.b, c.a);
+}
+// 3 - 1
+template <class ObjT, class Method, class P1, class P2, class P3, class C1>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple3<P1, P2, P3>& p,
+                             const Tuple1<C1>& c) {
+  (obj->*method)(p.a, p.b, p.c, c.a);
+}
+// 4 - 1
+template <class ObjT, class Method, class P1, class P2, class P3,
+          class P4, class C1>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple4<P1, P2, P3, P4>& p,
+                             const Tuple1<C1>& c) {
+  (obj->*method)(p.a, p.b, p.c, p.d, c.a);
+}
+
+// 1 - 2
+template <class ObjT, class Method, class P1, class C1, class C2>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple1<P1>& p,
+                             const Tuple2<C1, C2>& c) {
+  (obj->*method)(p.a, c.a, c.b);
+}
+
+// 2 - 2
+template <class ObjT, class Method, class P1, class P2, class C1, class C2>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple2<P1, P2>& p,
+                             const Tuple2<C1, C2>& c) {
+  (obj->*method)(p.a, p.b, c.a, c.b);
+}
+
+// 3 - 2
+template <class ObjT, class Method, class P1, class P2, class P3, class C1,
+          class C2>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple3<P1, P2, P3>& p,
+                             const Tuple2<C1, C2>& c) {
+  (obj->*method)(p.a, p.b, p.c, c.a, c.b);
+}
+
+// 4 - 2
+template <class ObjT, class Method, class P1, class P2, class P3, class P4,
+          class C1, class C2>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple4<P1, P2, P3, P4>& p,
+                             const Tuple2<C1, C2>& c) {
+  (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b);
+}
+
+// 1 - 3
+template <class ObjT, class Method, class P1, class C1, class C2, class C3>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple1<P1>& p,
+                             const Tuple3<C1, C2, C3>& c) {
+  (obj->*method)(p.a, c.a, c.b, c.c);
+}
+
+// 2 - 3
+template <class ObjT, class Method, class P1, class P2, class C1, class C2,
+          class C3>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple2<P1, P2>& p,
+                             const Tuple3<C1, C2, C3>& c) {
+  (obj->*method)(p.a, p.b, c.a, c.b, c.c);
+}
+
+// 3 - 3
+template <class ObjT, class Method, class P1, class P2, class P3, class C1,
+          class C2, class C3>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple3<P1, P2, P3>& p,
+                             const Tuple3<C1, C2, C3>& c) {
+  (obj->*method)(p.a, p.b, p.c, c.a, c.b, c.c);
+}
+
+// 4 - 3
+template <class ObjT, class Method, class P1, class P2, class P3, class P4,
+          class C1, class C2, class C3>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple4<P1, P2, P3, P4>& p,
+                             const Tuple3<C1, C2, C3>& c) {
+  (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b, c.c);
+}
+
+// 1 - 4
+template <class ObjT, class Method, class P1, class C1, class C2, class C3,
+          class C4>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple1<P1>& p,
+                             const Tuple4<C1, C2, C3, C4>& c) {
+  (obj->*method)(p.a, c.a, c.b, c.c, c.d);
+}
+
+// 2 - 4
+template <class ObjT, class Method, class P1, class P2, class C1, class C2,
+          class C3, class C4>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple2<P1, P2>& p,
+                             const Tuple4<C1, C2, C3, C4>& c) {
+  (obj->*method)(p.a, p.b, c.a, c.b, c.c, c.d);
+}
+
+// 3 - 4
+template <class ObjT, class Method, class P1, class P2, class P3,
+          class C1, class C2, class C3, class C4>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple3<P1, P2, P3>& p,
+                             const Tuple4<C1, C2, C3, C4>& c) {
+  (obj->*method)(p.a, p.b, p.c, c.a, c.b, c.c, c.d);
+}
+
+// 4 - 4
+template <class ObjT, class Method, class P1, class P2, class P3, class P4,
+          class C1, class C2, class C3, class C4>
+inline void DispatchToMethod(ObjT* obj, Method method,
+                             const Tuple4<P1, P2, P3, P4>& p,
+                             const Tuple4<C1, C2, C3, C4>& c) {
+  (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b, c.c, c.d);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Like CallbackImpl but has prebound arguments (like Task)
+template <class T, typename Method, typename PreBound, typename Params>
+class MutantImpl : public CallbackStorage<T, Method>,
+  public CallbackRunner<Params> {
+ public:
+  MutantImpl(T* obj, Method meth, const PreBound& pb)
+    : CallbackStorage<T, Method>(obj, meth),
+      pb_(pb) {
+  }
+
+  virtual void RunWithParams(const Params& params) {
+    // use "this->" to force C++ to look inside our templatized base class; see
+    // Effective C++, 3rd Ed, item 43, p210 for details.
+    DispatchToMethod(this->obj_, this->meth_, pb_, params);
+  }
+
+  PreBound pb_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Mutant creation simplification
+// 1 - 1
+template <class T, typename P1, typename A1>
+inline typename Callback1<A1>::Type* NewMutant(T* obj,
+                                               void (T::*method)(P1, A1),
+                                               P1 p1) {
+  return new MutantImpl<T, void (T::*)(P1, A1), P1, A1>(obj, method,
+      MakeTuple(p1));
+}
+
+// 1 - 2
+template <class T, typename P1, typename A1, typename A2>
+inline typename Callback2<A1, A2>::Type* NewMutant(T* obj,
+                                                  void (T::*method)(P1, A1, A2),
+                                                  P1 p1) {
+  return new MutantImpl<T, void (T::*)(P1, A1, A2), Tuple1<P1>, Tuple2<A1, A2> >
+      (obj, method, MakeTuple(p1));
+}
+
+// 1 - 3
+template <class T, typename P1, typename A1, typename A2, typename A3>
+inline typename Callback3<A1, A2, A3>::Type*
+NewMutant(T* obj, void (T::*method)(P1, A1, A2, A3), P1 p1) {
+  return new MutantImpl<T, void (T::*)(P1, A1, A2, A3), Tuple1<P1>,
+      Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1));
+}
+
+// 1 - 4
+template <class T, typename P1, typename A1, typename A2, typename A3,
+          typename A4>
+inline typename Callback4<A1, A2, A3, A4>::Type*
+NewMutant(T* obj, void (T::*method)(P1, A1, A2, A3, A4), P1 p1) {
+  return new MutantImpl<T, void (T::*)(P1, A1, A2, A3, A4), Tuple1<P1>,
+      Tuple4<A1, A2, A3, A4> >(obj, method, MakeTuple(p1));
+}
+
+
+// 2 - 1
+template <class T, typename P1, typename P2, typename A1>
+inline typename Callback1<A1>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, A1), P1 p1, P2 p2) {
+  return new MutantImpl<T, void (T::*)(P1, P2, A1), Tuple2<P1, P2>,
+      Tuple1<A1> >(obj, method, MakeTuple(p1, p2));
+}
+
+// 2 - 2
+template <class T, typename P1, typename P2, typename A1, typename A2>
+inline typename Callback2<A1, A2>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, A1, A2), P1 p1, P2 p2) {
+  return new MutantImpl<T, void (T::*)(P1, P2, A1, A2), Tuple2<P1, P2>,
+      Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2));
+}
+
+// 2 - 3
+template <class T, typename P1, typename P2, typename A1, typename A2,
+          typename A3>
+inline typename Callback3<A1, A2, A3>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, A1, A2, A3), P1 p1, P2 p2) {
+  return new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3), Tuple2<P1, P2>,
+      Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1, p2));
+}
+
+// 2 - 4
+template <class T, typename P1, typename P2, typename A1, typename A2,
+          typename A3, typename A4>
+inline typename Callback4<A1, A2, A3, A4>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, A1, A2, A3, A4), P1 p1, P2 p2) {
+  return new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3, A4), Tuple2<P1, P2>,
+      Tuple3<A1, A2, A3, A4> >(obj, method, MakeTuple(p1, p2));
+}
+
+// 3 - 1
+template <class T, typename P1, typename P2, typename P3, typename A1>
+inline typename Callback1<A1>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1), P1 p1, P2 p2, P3 p3) {
+  return new MutantImpl<T, void (T::*)(P1, P2, P3, A1), Tuple3<P1, P2, P3>,
+      Tuple1<A1> >(obj, method, MakeTuple(p1, p2, p3));
+}
+
+// 3 - 2
+template <class T, typename P1, typename P2, typename P3, typename A1,
+          typename A2>
+inline typename Callback2<A1, A2>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1, A2), P1 p1, P2 p2, P3 p3) {
+  return new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2), Tuple3<P1, P2, P3>,
+      Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2, p3));
+}
+
+// 3 - 3
+template <class T, typename P1, typename P2, typename P3, typename A1,
+          typename A2, typename A3>
+inline typename Callback3<A1, A2, A3>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3), P1 p1, P2 p2,
+          P3 p3) {
+  return new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3),
+      Tuple3<P1, P2, P3>, Tuple3<A1, A2, A3> >(obj, method,
+      MakeTuple(p1, p2, p3));
+}
+
+// 3 - 4
+template <class T, typename P1, typename P2, typename P3, typename A1,
+          typename A2, typename A3, typename A4>
+inline typename Callback4<A1, A2, A3, A4>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3, A4), P1 p1, P2 p2,
+          P3 p3) {
+  return new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3, A4),
+      Tuple3<P1, P2, P3>, Tuple3<A1, A2, A3, A4> >(obj, method,
+      MakeTuple(p1, p2, p3));
+}
+
+// 4 - 1
+template <class T, typename P1, typename P2, typename P3, typename P4,
+          typename A1>
+inline typename Callback1<A1>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1), P1 p1, P2 p2, P3 p3,
+          P4 p4) {
+  return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1),
+      Tuple4<P1, P2, P3, P4>, Tuple1<A1> >(obj, method,
+      MakeTuple(p1, p2, p3, p4));
+}
+
+// 4 - 2
+template <class T, typename P1, typename P2, typename P3, typename P4,
+          typename A1, typename A2>
+inline typename Callback2<A1, A2>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2), P1 p1, P2 p2,
+          P3 p3, P4 p4) {
+  return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2),
+    Tuple4<P1, P2, P3, P4>, Tuple2<A1, A2> >(obj, method,
+    MakeTuple(p1, p2, p3, p4));
+}
+
+// 4 - 3
+template <class T, typename P1, typename P2, typename P3, typename P4,
+          typename A1, typename A2, typename A3>
+inline typename Callback3<A1, A2, A3>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3), P1 p1, P2 p2,
+          P3 p3, P4 p4) {
+  return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3),
+    Tuple4<P1, P2, P3, P4>, Tuple3<A1, A2, A3> >(obj, method,
+    MakeTuple(p1, p2, p3, p4));
+}
+
+// 4 - 4
+template <class T, typename P1, typename P2, typename P3, typename P4,
+          typename A1, typename A2, typename A3, typename A4>
+inline typename Callback4<A1, A2, A3, A4>::Type*
+NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3, A4),
+          P1 p1, P2 p2, P3 p3, P4 p4) {
+  return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3, A4),
+      Tuple4<P1, P2, P3, P4>, Tuple3<A1, A2, A3, A4> >(obj, method,
+      MakeTuple(p1, p2, p3, p4));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Simple callback wrapper acting as a functor.
+// Redirects operator() to CallbackRunner<Params>::Run
+// We cannot delete the inner impl_ in object's destructor because
+// this object is copied few times inside from GMock machinery.
+template <typename Params>
+struct CallbackFunctor {
+  explicit CallbackFunctor(CallbackRunner<Params>*  cb) : impl_(cb) {}
+
+  template <typename Arg1>
+  inline void operator()(const Arg1& a) {
+    impl_->Run(a);
+    delete impl_;
+    impl_ = NULL;
+  }
+
+  template <typename Arg1, typename Arg2>
+  inline void operator()(const Arg1& a, const Arg2& b) {
+    impl_->Run(a, b);
+    delete impl_;
+    impl_ = NULL;
+  }
+
+  template <typename Arg1, typename Arg2, typename Arg3>
+  inline void operator()(const Arg1& a, const Arg2& b, const Arg3& c) {
+    impl_->Run(a, b, c);
+    delete impl_;
+    impl_ = NULL;
+  }
+
+  template <typename Arg1, typename Arg2, typename Arg3, typename Arg4>
+  inline void operator()(const Arg1& a, const Arg2& b, const Arg3& c,
+                         const Arg4& d) {
+    impl_->Run(a, b, c, d);
+    delete impl_;
+    impl_ = NULL;
+  }
+
+ private:
+  CallbackFunctor();
+  CallbackRunner<Params>* impl_;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CallbackFunctors creation
+
+// 0 - 1
+template <class T, typename A1>
+inline CallbackFunctor<Tuple1<A1> >
+CBF(T* obj, void (T::*method)(A1)) {
+  return CallbackFunctor<Tuple1<A1> >(NewCallback(obj, method));
+}
+
+// 0 - 2
+template <class T, typename A1, typename A2>
+inline CallbackFunctor<Tuple2<A1, A2> >
+CBF(T* obj, void (T::*method)(A1, A2)) {
+  return CallbackFunctor<Tuple2<A1, A2> >(NewCallback(obj, method));
+}
+
+// 0 - 3
+template <class T, typename A1, typename A2, typename A3>
+inline CallbackFunctor<Tuple3<A1, A2, A3> >
+CBF(T* obj, void (T::*method)(A1, A2, A3)) {
+  return CallbackFunctor<Tuple3<A1, A2, A3> >(NewCallback(obj, method));
+}
+
+// 0 - 4
+template <class T, typename A1, typename A2, typename A3, typename A4>
+inline CallbackFunctor<Tuple4<A1, A2, A3, A4> >
+CBF(T* obj, void (T::*method)(A1, A2, A3, A4)) {
+  return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(NewCallback(obj, method));
+}
+
+// 1 - 1
+template <class T, typename P1, typename A1>
+inline CallbackFunctor<Tuple1<A1> >
+CBF(T* obj, void (T::*method)(P1, A1), P1 p1) {
+  Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, A1), Tuple1<P1>,
+      Tuple1<A1> >(obj, method, MakeTuple(p1));
+  return CallbackFunctor<Tuple1<A1> >(t);
+}
+
+// 1 - 2
+template <class T, typename P1, typename A1, typename A2>
+inline CallbackFunctor<Tuple2<A1, A2> >
+CBF(T* obj, void (T::*method)(P1, A1, A2), P1 p1) {
+  Callback2<A1, A2>::Type* t = new MutantImpl<T, void (T::*)(P1, A1, A2),
+      Tuple1<P1>, Tuple2<A1, A2> >(obj, method, MakeTuple(p1));
+  return CallbackFunctor<Tuple2<A1, A2> >(t);
+}
+
+// 1 - 3
+template <class T, typename P1, typename A1, typename A2, typename A3>
+inline CallbackFunctor<Tuple3<A1, A2, A3> >
+CBF(T* obj, void (T::*method)(P1, A1, A2, A3), P1 p1) {
+  Callback3<A1, A2, A3>::Type* t =
+      new MutantImpl<T, void (T::*)(P1, A1, A2, A3), Tuple1<P1>,
+      Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1));
+  return CallbackFunctor<Tuple3<A1, A2, A3> >(t);
+}
+
+// 1 - 4
+template <class T, typename P1, typename A1, typename A2, typename A3,
+          typename A4>
+inline CallbackFunctor<Tuple4<A1, A2, A3, A4> >
+CBF(T* obj, void (T::*method)(P1, A1, A2, A3, A4), P1 p1) {
+  Callback4<A1, A2, A3>::Type* t =
+      new MutantImpl<T, void (T::*)(P1, A1, A2, A3, A4), Tuple1<P1>,
+      Tuple4<A1, A2, A3, A4> >(obj, method, MakeTuple(p1));
+  return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t);
+}
+
+// 2 - 1
+template <class T, typename P1, typename P2, typename A1>
+inline CallbackFunctor<Tuple1<A1> >
+CBF(T* obj, void (T::*method)(P1, P2, A1), P1 p1, P2 p2) {
+  Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, A1),
+      Tuple2<P1, P2>, Tuple1<A1> >(obj, method, MakeTuple(p1, p2));
+  return CallbackFunctor<Tuple1<A1> >(t);
+}
+
+// 2 - 2
+template <class T, typename P1, typename P2, typename A1, typename A2>
+inline CallbackFunctor<Tuple2<A1, A2> >
+CBF(T* obj, void (T::*method)(P1, P2, A1, A2), P1 p1, P2 p2) {
+  Callback2<A1, A2>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, A1, A2),
+      Tuple2<P1, P2>, Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2));
+  return CallbackFunctor<Tuple2<A1, A2> >(t);
+}
+
+// 2 - 3
+template <class T, typename P1, typename P2, typename A1, typename A2,
+          typename A3> inline CallbackFunctor<Tuple3<A1, A2, A3> >
+CBF(T* obj, void (T::*method)(P1, P2, A1, A2, A3), P1 p1, P2 p2) {
+  Callback3<A1, A2, A3>::Type* t =
+      new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3), Tuple2<P1, P2>,
+      Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1, p2));
+  return CallbackFunctor<Tuple3<A1, A2, A3> >(t);
+}
+
+// 2 - 4
+template <class T, typename P1, typename P2, typename A1, typename A2,
+          typename A3, typename A4>
+inline CallbackFunctor<Tuple4<A1, A2, A3, A4> >
+CBF(T* obj, void (T::*method)(P1, P2, A1, A2, A3, A4), P1 p1, P2 p2) {
+  Callback4<A1, A2, A3>::Type* t =
+      new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3, A4), Tuple2<P1, P2>,
+      Tuple4<A1, A2, A3, A4> >(obj, method, MakeTuple(p1, p2));
+  return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t);
+}
+
+
+// 3 - 1
+template <class T, typename P1, typename P2, typename P3, typename A1>
+inline CallbackFunctor<Tuple1<A1> >
+CBF(T* obj, void (T::*method)(P1, P2, P3, A1), P1 p1, P2 p2, P3 p3) {
+  Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, P3, A1),
+      Tuple3<P1, P2, P3>, Tuple1<A1> >(obj, method, MakeTuple(p1, p2, p3));
+  return CallbackFunctor<Tuple1<A1> >(t);
+}
+
+
+// 3 - 2
+template <class T, typename P1, typename P2, typename P3, typename A1,
+          typename A2> inline CallbackFunctor<Tuple2<A1, A2> >
+CBF(T* obj, void (T::*method)(P1, P2, P3, A1, A2), P1 p1, P2 p2, P3 p3) {
+  Callback2<A1, A2>::Type* t =
+      new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2), Tuple3<P1, P2, P3>,
+      Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2, p3));
+  return CallbackFunctor<Tuple2<A1, A2> >(t);
+}
+
+// 3 - 3
+template <class T, typename P1, typename P2, typename P3, typename A1,
+          typename A2, typename A3>
+inline CallbackFunctor<Tuple3<A1, A2, A3> >
+CBF(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3), P1 p1, P2 p2, P3 p3) {
+  Callback3<A1, A2, A3>::Type* t =
+      new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3), Tuple3<P1, P2, P3>,
+      Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1, p2, p3));
+  return CallbackFunctor<Tuple3<A1, A2, A3> >(t);
+}
+
+// 3 - 4
+template <class T, typename P1, typename P2, typename P3, typename A1,
+          typename A2, typename A3, typename A4>
+inline CallbackFunctor<Tuple4<A1, A2, A3, A4> >
+CBF(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3, A4),
+    P1 p1, P2 p2, P3 p3) {
+  Callback4<A1, A2, A3>::Type* t =
+      new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3, A4),
+      Tuple3<P1, P2, P3>, Tuple4<A1, A2, A3, A4> >(obj, method,
+      MakeTuple(p1, p2, p3));
+  return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t);
+}
+
+
+
+// 4 - 1
+template <class T, typename P1, typename P2, typename P3, typename P4,
+          typename A1>
+inline CallbackFunctor<Tuple1<A1> >
+CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1), P1 p1, P2 p2, P3 p3, P4 p4) {
+  Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1),
+      Tuple4<P1, P2, P3, P4>, Tuple1<A1> >
+      (obj, method, MakeTuple(p1, p2, p3, p4));
+  return CallbackFunctor<Tuple1<A1> >(t);
+}
+
+
+// 4 - 2
+template <class T, typename P1, typename P2, typename P3, typename P4,
+          typename A1, typename A2>
+inline CallbackFunctor<Tuple2<A1, A2> >
+CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2),
+            P1 p1, P2 p2, P3 p3, P4 p4) {
+  Callback2<A1, A2>::Type* t =
+      new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2),
+      Tuple4<P1, P2, P3, P4>,
+      Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2, p3, p4));
+  return CallbackFunctor<Tuple2<A1, A2> >(t);
+}
+
+// 4 - 3
+template <class T, typename P1, typename P2, typename P3, typename P4,
+          typename A1, typename A2, typename A3>
+inline CallbackFunctor<Tuple3<A1, A2, A3> >
+CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3),
+    P1 p1, P2 p2, P3 p3, P4 p4) {
+  Callback3<A1, A2, A3>::Type* t =
+      new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3),
+      Tuple4<P1, P2, P3, P4>, Tuple3<A1, A2, A3> >
+      (obj, method, MakeTuple(p1, p2, p3, p4));
+  return CallbackFunctor<Tuple3<A1, A2, A3> >(t);
+}
+
+// 4 - 4
+template <class T, typename P1, typename P2, typename P3, typename P4,
+          typename A1, typename A2, typename A3, typename A4>
+inline CallbackFunctor<Tuple4<A1, A2, A3, A4> >
+CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3, A4),
+    P1 p1, P2 p2, P3 p3, P4 p4) {
+  Callback4<A1, A2, A3>::Type* t =
+      new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3, A4),
+      Tuple4<P1, P2, P3, P4>, Tuple4<A1, A2, A3, A4> >(obj, method,
+      MakeTuple(p1, p2, p3, p4));
+  return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t);
+}
+
+
+// Simple task wrapper acting as a functor.
+// Redirects operator() to Task::Run. We cannot delete the inner impl_ object
+// in object's destructor because this object is copied few times inside
+// from GMock machinery.
+struct TaskHolder {
+  explicit TaskHolder(Task* impl) : impl_(impl) {}
+  void operator()() {
+    impl_->Run();
+    delete impl_;
+    impl_ = NULL;
+  }
+ private:
+  TaskHolder();
+  Task* impl_;
+};
+
+#endif  // CHROME_FRAME_TEST_HELPER_GMOCK_H_
diff --git a/chrome_frame/test/html_util_test_data/basic_test.html b/chrome_frame/test/html_util_test_data/basic_test.html
new file mode 100644
index 0000000..f0cd17a
--- /dev/null
+++ b/chrome_frame/test/html_util_test_data/basic_test.html
@@ -0,0 +1,11 @@
+<HTML>

+  

+  <HEAD>

+    <!-- Note the capitalization in CONtent to test the 

+         case-insensitiveness -->

+    <META http-equiv="X-UA-Compatible" CONtent="chrome=1" />

+  </HEAD>

+  <BODY>

+    

+    Wooo!

+  </BODY></HTML>

diff --git a/chrome_frame/test/html_util_test_data/degenerate_cases_test.html b/chrome_frame/test/html_util_test_data/degenerate_cases_test.html
new file mode 100644
index 0000000..d527496a
--- /dev/null
+++ b/chrome_frame/test/html_util_test_data/degenerate_cases_test.html
@@ -0,0 +1,7 @@
+<><foo ">

+</head>

+<"foo">

+<!-- Note that the meta tag shouldn't be picked up since we are still

+     inside a quote block. -->

+<META http-equiv="X-UA-Compatible" CONtent="chrome=1" />

+<fee>

diff --git a/chrome_frame/test/html_util_test_data/multiple_tags.html b/chrome_frame/test/html_util_test_data/multiple_tags.html
new file mode 100644
index 0000000..9bd5cea
--- /dev/null
+++ b/chrome_frame/test/html_util_test_data/multiple_tags.html
@@ -0,0 +1,17 @@
+<HTML><HEAD>

+  

+  <META http-equiv="X-UA-Compatible" CONtent="chrome=1" />

+  <META http-equiv="X-UA-Compatible" content="chrome=1" />

+  <METAman http-equiv="X-UA-Compatible" CONtent="notchrome=1" />

+  <transMETA http-equiv="X-UA-Compatible" CONtent="notchrome=1" />

+  <IMETAGIRL http-equiv="X-UA-Compatible" CONtent="notchrome=1" />

+  <metA http-equiv="X-UA-Compatible" content="chrome=1" />

+  <!-- shouldn't pick up commented meta tags! -->

+  <!-- <metA http-equiv="X-UA-Compatible" content="chrome=1" /> -->

+  

+  <!-- The following cases should also not be matched -->

+  <meta http-equiv="X-UA-Compatibleeee" content="chrome=1" />

+  <meta http-equiv="X-UA-Compatible!" content="chrome=1" />

+  <meta http-equiv="!X-UA-Compatible" content="chrome=1" />

+  <meta http-equiv="\"X-UA-Compatible\"" content="chrome=1" />

+<fee>
\ No newline at end of file
diff --git a/chrome_frame/test/html_util_test_data/quotes_test.html b/chrome_frame/test/html_util_test_data/quotes_test.html
new file mode 100644
index 0000000..03ce96d
--- /dev/null
+++ b/chrome_frame/test/html_util_test_data/quotes_test.html
@@ -0,0 +1,10 @@
+<HTML>

+  

+  <HEAD>

+    <DANGER red="herring>'" testing   =   "do'><><>quotes\"\\'work?">

+    <META http-equiv=X-UA-Compatible CONtent="chrome=1" />

+  </HEAD>

+  <BODY>

+    

+    Wooo!

+  </BODY></HTML>

diff --git a/chrome_frame/test/html_util_unittests.cc b/chrome_frame/test/html_util_unittests.cc
new file mode 100644
index 0000000..131b185
--- /dev/null
+++ b/chrome_frame/test/html_util_unittests.cc
@@ -0,0 +1,215 @@
+// Copyright (c) 2006-2009 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.

+

+#include <windows.h>

+#include <atlsecurity.h>

+#include <shellapi.h>

+

+#include "base/basictypes.h"

+#include "base/file_util.h"

+#include "base/message_loop.h"

+#include "base/path_service.h"

+#include "base/process_util.h"

+#include "base/ref_counted.h"

+#include "base/scoped_handle.h"

+#include "base/task.h"

+#include "base/win_util.h"

+#include "net/base/net_util.h"

+

+#include "chrome_frame/test/chrome_frame_unittests.h"

+#include "chrome_frame/chrome_frame_automation.h"

+#include "chrome_frame/chrome_frame_delegate.h"

+#include "chrome_frame/html_utils.h"

+#include "testing/gtest/include/gtest/gtest.h"

+

+class HtmlUtilUnittest : public testing::Test {

+ protected:

+  // Constructor

+  HtmlUtilUnittest() {}

+

+  // Returns the test path given a test case.

+  virtual bool GetTestPath(const std::wstring& test_case, std::wstring* path) {

+    if (!path) {

+      NOTREACHED();

+      return false;

+    }

+

+    std::wstring test_path;

+    if (!PathService::Get(base::DIR_SOURCE_ROOT, &test_path)) {

+      NOTREACHED();

+      return false;

+    }

+

+    file_util::AppendToPath(&test_path, L"chrome_frame");

+    file_util::AppendToPath(&test_path, L"test");

+    file_util::AppendToPath(&test_path, L"html_util_test_data");

+    file_util::AppendToPath(&test_path, test_case);

+

+    *path = test_path;

+    return true;

+  }

+

+  virtual bool GetTestData(const std::wstring& test_case, std::wstring* data) {

+    if (!data) {

+      NOTREACHED();

+      return false;

+    }

+

+    std::wstring path;

+    if (!GetTestPath(test_case, &path)) {

+      NOTREACHED();

+      return false;

+    }

+

+    std::string raw_data;

+    file_util::ReadFileToString(path, &raw_data);

+

+    // Convert to wide using the "best effort" assurance described in

+    // string_util.h

+    data->assign(UTF8ToWide(raw_data));

+    return true;

+  }

+};

+

+TEST_F(HtmlUtilUnittest, BasicTest) {

+  std::wstring test_data;

+  GetTestData(L"basic_test.html", &test_data);

+

+  HTMLScanner scanner(test_data.c_str());

+

+  // Grab the meta tag from the document and ensure that we get exactly one.

+  HTMLScanner::StringRangeList tag_list;

+  scanner.GetTagsByName(L"meta", &tag_list, L"body");

+  ASSERT_EQ(1, tag_list.size());

+

+  // Pull out the http-equiv attribute and check its value:

+  HTMLScanner::StringRange attribute_value;

+  EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value));

+  EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible"));

+

+  // Pull out the content attribute and check its value:

+  EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value));

+  EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));

+}

+

+TEST_F(HtmlUtilUnittest, QuotesTest) {

+  std::wstring test_data;

+  GetTestData(L"quotes_test.html", &test_data);

+

+  HTMLScanner scanner(test_data.c_str());

+

+  // Grab the meta tag from the document and ensure that we get exactly one.

+  HTMLScanner::StringRangeList tag_list;

+  scanner.GetTagsByName(L"meta", &tag_list, L"body");

+  ASSERT_EQ(1, tag_list.size());

+

+  // Pull out the http-equiv attribute and check its value:

+  HTMLScanner::StringRange attribute_value;

+  EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value));

+  EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible"));

+

+  // Pull out the content attribute and check its value:

+  EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value));

+  EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));

+}

+

+TEST_F(HtmlUtilUnittest, DegenerateCasesTest) {

+  std::wstring test_data;

+  GetTestData(L"degenerate_cases_test.html", &test_data);

+

+  HTMLScanner scanner(test_data.c_str());

+

+  // Scan for meta tags in the document. We expect not to pick up the one

+  // that appears to be there since it is technically inside a quote block.

+  HTMLScanner::StringRangeList tag_list;

+  scanner.GetTagsByName(L"meta", &tag_list, L"body");

+  EXPECT_TRUE(tag_list.empty());

+}

+

+TEST_F(HtmlUtilUnittest, MultipleTagsTest) {

+  std::wstring test_data;

+  GetTestData(L"multiple_tags.html", &test_data);

+

+  HTMLScanner scanner(test_data.c_str());

+

+  // Grab the meta tag from the document and ensure that we get exactly three.

+  HTMLScanner::StringRangeList tag_list;

+  scanner.GetTagsByName(L"meta", &tag_list, L"body");

+  EXPECT_EQ(7, tag_list.size());

+

+  // Pull out the content attribute for each tag and check its value:

+  HTMLScanner::StringRange attribute_value;

+  HTMLScanner::StringRangeList::const_iterator tag_list_iter(

+      tag_list.begin());

+  int valid_tag_count = 0;

+  for (; tag_list_iter != tag_list.end(); tag_list_iter++) {

+    HTMLScanner::StringRange attribute_value;

+    if (tag_list_iter->GetTagAttribute(L"http-equiv", &attribute_value) &&

+        attribute_value.Equals(L"X-UA-Compatible")) {

+      EXPECT_TRUE(tag_list_iter->GetTagAttribute(L"content", &attribute_value));

+      EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));

+      valid_tag_count++;

+    }

+  }

+  EXPECT_EQ(3, valid_tag_count);

+}

+

+TEST_F(HtmlUtilUnittest, ShortDegenerateTest1) {

+  std::wstring test_data(

+      L"<foo><META http-equiv=X-UA-Compatible content='chrome=1'");

+

+  HTMLScanner scanner(test_data.c_str());

+

+  // Scan for meta tags in the document. We expect not to pick up the one

+  // that is there since it is not properly closed.

+  HTMLScanner::StringRangeList tag_list;

+  scanner.GetTagsByName(L"meta", &tag_list, L"body");

+  EXPECT_TRUE(tag_list.empty());

+}

+

+TEST_F(HtmlUtilUnittest, ShortDegenerateTest2) {

+  std::wstring test_data(

+    L"<foo <META http-equiv=X-UA-Compatible content='chrome=1'/>");

+

+  HTMLScanner scanner(test_data.c_str());

+

+  // Scan for meta tags in the document. We expect not to pick up the one

+  // that appears to be there since it is inside a non-closed tag.

+  HTMLScanner::StringRangeList tag_list;

+  scanner.GetTagsByName(L"meta", &tag_list, L"body");

+  EXPECT_TRUE(tag_list.empty());

+}

+

+TEST_F(HtmlUtilUnittest, QuoteInsideHTMLCommentTest) {

+  std::wstring test_data(

+    L"<!-- comment' --><META http-equiv=X-UA-Compatible content='chrome=1'/>");

+

+  HTMLScanner scanner(test_data.c_str());

+

+  // Grab the meta tag from the document and ensure that we get exactly one.

+  HTMLScanner::StringRangeList tag_list;

+  scanner.GetTagsByName(L"meta", &tag_list, L"body");

+  ASSERT_EQ(1, tag_list.size());

+

+  // Pull out the http-equiv attribute and check its value:

+  HTMLScanner::StringRange attribute_value;

+  EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value));

+  EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible"));

+

+  // Pull out the content attribute and check its value:

+  EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value));

+  EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));

+}

+

+TEST_F(HtmlUtilUnittest, CloseTagInsideHTMLCommentTest) {

+  std::wstring test_data(

+    L"<!-- comment> <META http-equiv=X-UA-Compatible content='chrome=1'/>-->");

+

+  HTMLScanner scanner(test_data.c_str());

+

+  // Grab the meta tag from the document and ensure that we get exactly one.

+  HTMLScanner::StringRangeList tag_list;

+  scanner.GetTagsByName(L"meta", &tag_list, L"body");

+  ASSERT_TRUE(tag_list.empty());

+}

diff --git a/chrome_frame/test/http_server.cc b/chrome_frame/test/http_server.cc
new file mode 100644
index 0000000..f2cc333
--- /dev/null
+++ b/chrome_frame/test/http_server.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2006-2009 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.
+#include "chrome_frame/test/http_server.h"
+
+const wchar_t kDocRoot[] = L"chrome_frame\\test\\data";
+
+void ChromeFrameHTTPServer::SetUp() {
+  std::wstring document_root(kDocRoot);
+  server_ = HTTPTestServer::CreateServer(document_root, NULL, 30, 1000);
+  ASSERT_TRUE(server_ != NULL);
+
+  // copy CFInstance.js into the test directory
+  FilePath cf_source_path;
+  PathService::Get(base::DIR_SOURCE_ROOT, &cf_source_path);
+  cf_source_path = cf_source_path.Append(FILE_PATH_LITERAL("chrome_frame"));
+
+  file_util::CopyFile(cf_source_path.Append(FILE_PATH_LITERAL("CFInstance.js")),
+      cf_source_path.Append(
+          FILE_PATH_LITERAL("test")).Append(
+              FILE_PATH_LITERAL("data")).Append(
+                  FILE_PATH_LITERAL("CFInstance.js")));  // NOLINT
+}
+
+void ChromeFrameHTTPServer::TearDown() {
+  if (server_) {
+    server_ = NULL;
+  }
+
+  // clobber CFInstance.js
+  FilePath cfi_path;
+  PathService::Get(base::DIR_SOURCE_ROOT, &cfi_path);
+  cfi_path = cfi_path
+      .Append(FILE_PATH_LITERAL("chrome_frame"))
+      .Append(FILE_PATH_LITERAL("test"))
+      .Append(FILE_PATH_LITERAL("data"))
+      .Append(FILE_PATH_LITERAL("CFInstance.js"));
+
+  file_util::Delete(cfi_path, false);
+}
+
+bool ChromeFrameHTTPServer::WaitToFinish(int milliseconds) {
+  if (!server_)
+    return true;
+
+  return server_->WaitToFinish(milliseconds);
+}
+
+GURL ChromeFrameHTTPServer::Resolve(const wchar_t* relative_url) {
+  return server_->TestServerPageW(relative_url);
+}
+
+std::wstring ChromeFrameHTTPServer::GetDataDir() {
+  return server_->GetDataDirectory().ToWStringHack();
+}
+
diff --git a/chrome_frame/test/http_server.h b/chrome_frame/test/http_server.h
new file mode 100644
index 0000000..acac5b5
--- /dev/null
+++ b/chrome_frame/test/http_server.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2006-2009 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.
+#ifndef CHROME_FRAME_TEST_HTTP_SERVER_H_
+#define CHROME_FRAME_TEST_HTTP_SERVER_H_
+
+#include <windows.h>
+#include <string>
+
+#include "googleurl/src/gurl.h"
+#include "base/ref_counted.h"
+#include "net/url_request/url_request_unittest.h"
+
+// chrome frame specilization of http server from net.
+class ChromeFrameHTTPServer {
+ public:
+  void SetUp();
+  void TearDown();
+  bool WaitToFinish(int milliseconds);
+  GURL Resolve(const wchar_t* relative_url);
+  std::wstring GetDataDir();
+
+  HTTPTestServer* server() {
+    return server_;
+  }
+
+ protected:
+  scoped_refptr<HTTPTestServer> server_;
+};
+
+#endif  // CHROME_FRAME_TEST_HTTP_SERVER_H_
+
diff --git a/chrome_frame/test/icu_stubs_unittests.cc b/chrome_frame/test/icu_stubs_unittests.cc
new file mode 100644
index 0000000..4da4a4095
--- /dev/null
+++ b/chrome_frame/test/icu_stubs_unittests.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2006-2009 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.
+
+#include "chrome_frame/test/chrome_frame_unittests.h"
+
+// Need to include these first since they're included
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "googleurl/src/url_canon.h"
+
+// Include the implementation of our stubs into a special namespace so that
+// we can separate them from Chrome's implementation.
+namespace icu_stubs {
+  // This struct is only to avoid build problems for the two googleurl stubs
+  // that currently are noops.
+  struct CanonOutputW { };
+
+  #include "chrome_frame/icu_stubs.cc"
+}  // namespace icu_stubs
+
+// anonymous namespace for test data.
+namespace {
+
+  // Test strings borrowed from base/string_util_unittest.cc
+  static const wchar_t* const kConvertRoundtripCases[] = {
+    L"",
+    L"Google Vid¯ôfY »"
+    L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb",
+    //  " ±³ºÌü¹¿Â ÃÄÌÂ"
+    L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
+    L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2",
+    // ">8A: AB@0=8F =0 @CAA:><"
+    L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442"
+    L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430"
+    L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c",
+    // "È´ÌÁD¾¤Â"
+    L"\xc804\xccb4\xc11c\xbe44\xc2a4",
+
+    // Test characters that take more than 16 bits. This will depend on whether
+    // wchar_t is 16 or 32 bits.
+    #if defined(WCHAR_T_IS_UTF16)
+    L"\xd800\xdf00",
+    // ?????  (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E)
+    L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44",
+    #elif defined(WCHAR_T_IS_UTF32)
+    L"\x10300",
+    // ?????  (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E)
+    L"\x11d40\x11d41\x11d42\x11d43\x11d44",
+    #endif
+  };
+
+}  // namespace
+
+TEST(IcuStubsTests, UTF8AndWideStubTest) {
+  // Test code borrowed from ConvertUTF8AndWide in base/string_util_unittest.cc.
+
+  // The difference is that we want to make sure that our stubs work the same
+  // way as chrome's implementation of WideToUTF8 and UTF8ToWide.
+  for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) {
+    std::ostringstream utf8_base, utf8_stub;
+    utf8_base << WideToUTF8(kConvertRoundtripCases[i]);
+    utf8_stub << icu_stubs::WideToUTF8(kConvertRoundtripCases[i]);
+
+    EXPECT_EQ(utf8_base.str(), utf8_stub.str());
+
+    std::wostringstream wide_base, wide_stub;
+    wide_base << UTF8ToWide(utf8_base.str());
+    wide_stub << icu_stubs::UTF8ToWide(utf8_base.str());
+
+    EXPECT_EQ(wide_base.str(), wide_stub.str());
+  }
+}
diff --git a/chrome_frame/test/net/dialog_watchdog.cc b/chrome_frame/test/net/dialog_watchdog.cc
new file mode 100644
index 0000000..27a01a0
--- /dev/null
+++ b/chrome_frame/test/net/dialog_watchdog.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 2009 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.
+
+#include <oleacc.h>
+
+#include "chrome_frame/test/net/dialog_watchdog.h"
+
+#include "base/logging.h"
+#include "base/scoped_comptr_win.h"
+#include "base/string_util.h"
+
+#include "chrome_frame/test/chrome_frame_test_utils.h"
+#include "chrome_frame/function_stub.h"
+
+namespace {
+// Uses the IAccessible interface for the window to set the focus.
+// This can be useful when you don't have control over the thread that
+// owns the window.
+// NOTE: this depends on oleacc.lib which the net tests already depend on
+// but other unit tests don't depend on oleacc so we can't just add the method
+// directly into chrome_frame_test_utils.cc (without adding a
+// #pragma comment(lib, "oleacc.lib")).
+bool SetFocusToAccessibleWindow(HWND hwnd) {
+  bool ret = false;
+  ScopedComPtr<IAccessible> acc;
+  AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_IAccessible,
+      reinterpret_cast<void**>(acc.Receive()));
+  if (acc) {
+    VARIANT self = { VT_I4 };
+    self.lVal = CHILDID_SELF;
+    ret = SUCCEEDED(acc->accSelect(SELFLAG_TAKEFOCUS, self));
+  }
+  return ret;
+}
+
+}  // namespace
+
+SupplyProxyCredentials::SupplyProxyCredentials(const char* username,
+                                               const char* password)
+    : username_(username), password_(password) {
+}
+
+bool SupplyProxyCredentials::OnDialogDetected(HWND hwnd,
+                                              const std::string& caption) {
+  // IE's dialog caption (en-US).
+  if (caption.compare("Windows Security") != 0)
+    return false;
+
+  DialogProps props = {0};
+  ::EnumChildWindows(hwnd, EnumChildren, reinterpret_cast<LPARAM>(&props));
+  DCHECK(::IsWindow(props.username_));
+  DCHECK(::IsWindow(props.password_));
+
+  // We can't use SetWindowText to set the username/password, so simulate
+  // keyboard input instead.
+  chrome_frame_test::ForceSetForegroundWindow(hwnd);
+  CHECK(SetFocusToAccessibleWindow(props.username_));
+  chrome_frame_test::SendString(username_.c_str());
+  Sleep(100);
+
+  chrome_frame_test::SendVirtualKey(VK_TAB);
+  Sleep(100);
+  chrome_frame_test::SendString(password_.c_str());
+
+  Sleep(100);
+  chrome_frame_test::SendVirtualKey(VK_RETURN);
+
+  return true;
+}
+
+// static
+BOOL SupplyProxyCredentials::EnumChildren(HWND hwnd, LPARAM param) {
+  if (!::IsWindowVisible(hwnd))
+    return TRUE;  // Ignore but continue to enumerate.
+
+  DialogProps* props = reinterpret_cast<DialogProps*>(param);
+
+  char class_name[MAX_PATH] = {0};
+  ::GetClassNameA(hwnd, class_name, arraysize(class_name));
+  if (lstrcmpiA(class_name, "Edit") == 0) {
+    if (props->username_ == NULL || props->username_ == hwnd) {
+      props->username_ = hwnd;
+    } else if (props->password_ == NULL) {
+      props->password_ = hwnd;
+    }
+  } else {
+    EnumChildWindows(hwnd, EnumChildren, param);
+  }
+
+  return TRUE;
+}
+
+DialogWatchdog::DialogWatchdog() : hook_(NULL), hook_stub_(NULL) {
+  Initialize();
+}
+
+DialogWatchdog::~DialogWatchdog() {
+  Uninitialize();
+}
+
+bool DialogWatchdog::Initialize() {
+  DCHECK(hook_ == NULL);
+  DCHECK(hook_stub_ == NULL);
+  hook_stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this),
+                                    WinEventHook);
+  hook_ = SetWinEventHook(EVENT_OBJECT_SHOW, EVENT_OBJECT_SHOW, NULL,
+                          reinterpret_cast<WINEVENTPROC>(hook_stub_->code()), 0,
+                          0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
+
+  return hook_ != NULL;
+}
+
+void DialogWatchdog::Uninitialize() {
+  if (hook_) {
+    ::UnhookWinEvent(hook_);
+    hook_ = NULL;
+    FunctionStub::Destroy(hook_stub_);
+    hook_stub_ = NULL;
+  }
+}
+
+// static
+void DialogWatchdog::WinEventHook(DialogWatchdog* me, HWINEVENTHOOK hook,
+                                  DWORD event, HWND hwnd, LONG object_id,
+                                  LONG child_id, DWORD event_thread_id,
+                                  DWORD event_time) {
+  // Check for a dialog class ("#32770") and notify observers if we find one.
+  char class_name[MAX_PATH] = {0};
+  ::GetClassNameA(hwnd, class_name, arraysize(class_name));
+  if (lstrcmpA(class_name, "#32770") == 0) {
+    int len = ::GetWindowTextLength(hwnd);
+    std::string text;
+    ::GetWindowTextA(hwnd, WriteInto(&text, len + 1), len + 1);
+    me->OnDialogFound(hwnd, text);
+  }
+}
+
+void DialogWatchdog::OnDialogFound(HWND hwnd, const std::string& caption) {
+  std::vector<DialogWatchdogObserver*>::iterator it = observers_.begin();
+  while (it != observers_.end()) {
+    if ((*it)->OnDialogDetected(hwnd, caption))
+      break;
+    it++;
+  }
+}
diff --git a/chrome_frame/test/net/dialog_watchdog.h b/chrome_frame/test/net/dialog_watchdog.h
new file mode 100644
index 0000000..dfb8989
--- /dev/null
+++ b/chrome_frame/test/net/dialog_watchdog.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_TEST_NET_DIALOG_WATCHDOG_H_
+#define CHROME_FRAME_TEST_NET_DIALOG_WATCHDOG_H_
+
+#include <windows.h>
+
+#include <string>
+#include <vector>
+
+struct FunctionStub;
+
+class DialogWatchdogObserver {  // NOLINT
+ public:
+  // returns true if this observer handled the dialog.
+  virtual bool OnDialogDetected(HWND hwnd, const std::string& caption) = 0;
+};
+
+class SupplyProxyCredentials : public DialogWatchdogObserver {
+ public:
+  SupplyProxyCredentials(const char* username, const char* password);
+
+ protected:
+  struct DialogProps {
+    HWND username_;
+    HWND password_;
+  };
+
+  virtual bool OnDialogDetected(HWND hwnd, const std::string& caption);
+  static BOOL CALLBACK EnumChildren(HWND hwnd, LPARAM param);
+
+ protected:
+  std::string username_;
+  std::string password_;
+};
+
+class DialogWatchdog {
+ public:
+  DialogWatchdog();
+  ~DialogWatchdog();
+
+  inline void AddObserver(DialogWatchdogObserver* observer) {
+    observers_.push_back(observer);
+  }
+
+  bool Initialize();
+  void Uninitialize();
+
+ protected:
+  static void CALLBACK WinEventHook(DialogWatchdog* me, HWINEVENTHOOK hook,
+      DWORD event, HWND hwnd, LONG object_id, LONG child_id,
+      DWORD event_thread_id, DWORD event_time);
+
+  void OnDialogFound(HWND hwnd, const std::string& caption);
+
+ protected:
+  HWINEVENTHOOK hook_;
+  std::vector<DialogWatchdogObserver*> observers_;
+  FunctionStub* hook_stub_;
+};
+
+#endif  // CHROME_FRAME_TEST_NET_DIALOG_WATCHDOG_H_
diff --git a/chrome_frame/test/net/fake_external_tab.cc b/chrome_frame/test/net/fake_external_tab.cc
new file mode 100644
index 0000000..eebd2d9b
--- /dev/null
+++ b/chrome_frame/test/net/fake_external_tab.cc
@@ -0,0 +1,391 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/test/net/fake_external_tab.h"
+
+#include <exdisp.h>
+
+#include "app/app_paths.h"
+#include "app/resource_bundle.h"
+#include "app/win_util.h"
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/icu_util.h"
+#include "base/path_service.h"
+#include "base/scoped_bstr_win.h"
+#include "base/scoped_comptr_win.h"
+#include "base/scoped_variant_win.h"
+
+#include "chrome/browser/browser_prefs.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/process_singleton.h"
+#include "chrome/browser/profile_manager.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/browser/renderer_host/render_process_host.h"
+
+#include "chrome_frame/utils.h"
+#include "chrome_frame/test/chrome_frame_test_utils.h"
+#include "chrome_frame/test/net/dialog_watchdog.h"
+#include "chrome_frame/test/net/test_automation_resource_message_filter.h"
+
+namespace {
+
+// A special command line switch to allow developers to manually launch the
+// browser and debug CF inside the browser.
+const wchar_t kManualBrowserLaunch[] = L"manual-browser";
+
+// Pops up a message box after the test environment has been set up
+// and before tearing it down.  Useful for when debugging tests and not
+// the test environment that's been set up.
+const wchar_t kPromptAfterSetup[] = L"prompt-after-setup";
+
+const int kTestServerPort = 4666;
+// The test HTML we use to initialize Chrome Frame.
+// Note that there's a little trick in there to avoid an extra URL request
+// that the browser will otherwise make for the site's favicon.
+// If we don't do this the browser will create a new URL request after
+// the CF page has been initialized and that URL request will confuse the
+// global URL instance counter in the unit tests and subsequently trip
+// some DCHECKs.
+const char kChromeFrameHtml[] = "<html><head>"
+    "<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\" />"
+    "<link rel=\"shortcut icon\" href=\"file://c:\\favicon.ico\"/>"
+    "</head><body>Chrome Frame should now be loaded</body></html>";
+
+bool ShouldLaunchBrowser() {
+  return !CommandLine::ForCurrentProcess()->HasSwitch(kManualBrowserLaunch);
+}
+
+bool PromptAfterSetup() {
+  return CommandLine::ForCurrentProcess()->HasSwitch(kPromptAfterSetup);
+}
+
+}  // end namespace
+
+FakeExternalTab::FakeExternalTab() {
+  PathService::Get(chrome::DIR_USER_DATA, &overridden_user_dir_);
+  user_data_dir_ = FilePath::FromWStringHack(GetProfilePath());
+  PathService::Override(chrome::DIR_USER_DATA, user_data_dir_);
+  process_singleton_.reset(new ProcessSingleton(user_data_dir_));
+}
+
+FakeExternalTab::~FakeExternalTab() {
+  if (!overridden_user_dir_.empty()) {
+    PathService::Override(chrome::DIR_USER_DATA, overridden_user_dir_);
+  }
+}
+
+std::wstring FakeExternalTab::GetProfileName() {
+  return L"iexplore";
+}
+
+std::wstring FakeExternalTab::GetProfilePath() {
+  std::wstring path;
+  GetUserProfileBaseDirectory(&path);
+  file_util::AppendToPath(&path, GetProfileName());
+  return path;
+}
+
+void FakeExternalTab::Initialize() {
+  DCHECK(g_browser_process == NULL);
+
+  // The gears plugin causes the PluginRequestInterceptor to kick in and it
+  // will cause problems when it tries to intercept URL requests.
+  PathService::Override(chrome::FILE_GEARS_PLUGIN, FilePath());
+
+  icu_util::Initialize();
+
+  chrome::RegisterPathProvider();
+  app::RegisterPathProvider();
+
+  ResourceBundle::InitSharedInstance(L"en-US");
+  ResourceBundle::GetSharedInstance().LoadThemeResources();
+
+  const CommandLine* cmd = CommandLine::ForCurrentProcess();
+  browser_process_.reset(new BrowserProcessImpl(*cmd));
+  RenderProcessHost::set_run_renderer_in_process(true);
+  // BrowserProcessImpl's constructor should set g_browser_process.
+  DCHECK(g_browser_process);
+
+  Profile* profile = g_browser_process->profile_manager()->
+      GetDefaultProfile(FilePath(user_data()));
+  PrefService* prefs = profile->GetPrefs();
+  PrefService* local_state = browser_process_->local_state();
+  local_state->RegisterStringPref(prefs::kApplicationLocale, L"");
+  local_state->RegisterBooleanPref(prefs::kMetricsReportingEnabled, false);
+
+  browser::RegisterAllPrefs(prefs, local_state);
+
+  // Override some settings to avoid hitting some preferences that have not
+  // been registered.
+  prefs->SetBoolean(prefs::kPasswordManagerEnabled, false);
+  prefs->SetBoolean(prefs::kAlternateErrorPagesEnabled, false);
+  prefs->SetBoolean(prefs::kSafeBrowsingEnabled, false);
+
+  profile->InitExtensions();
+}
+
+void FakeExternalTab::Shutdown() {
+  browser_process_.reset();
+  g_browser_process = NULL;
+  process_singleton_.reset();
+
+  ResourceBundle::CleanupSharedInstance();
+}
+
+CFUrlRequestUnittestRunner::CFUrlRequestUnittestRunner(int argc, char** argv)
+    : NetTestSuite(argc, argv),
+      chrome_frame_html_("/chrome_frame", kChromeFrameHtml) {
+  fake_chrome_.Initialize();
+  pss_subclass_.reset(new ProcessSingletonSubclass(this));
+  EXPECT_TRUE(pss_subclass_->Subclass(fake_chrome_.user_data()));
+  StartChromeFrameInHostBrowser();
+}
+
+CFUrlRequestUnittestRunner::~CFUrlRequestUnittestRunner() {
+  fake_chrome_.Shutdown();
+}
+
+DWORD WINAPI NavigateIE(void* param) {
+  return 0;
+  win_util::ScopedCOMInitializer com;
+  BSTR url = reinterpret_cast<BSTR>(param);
+
+  bool found = false;
+  int retries = 0;
+  const int kMaxRetries = 20;
+  while (!found && retries < kMaxRetries) {
+    ScopedComPtr<IShellWindows> windows;
+    HRESULT hr = ::CoCreateInstance(__uuidof(ShellWindows), NULL, CLSCTX_ALL,
+        IID_IShellWindows, reinterpret_cast<void**>(windows.Receive()));
+    DCHECK(SUCCEEDED(hr)) << "CoCreateInstance";
+
+    if (SUCCEEDED(hr)) {
+      hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
+      long count = 0;  // NOLINT
+      windows->get_Count(&count);
+      VARIANT i = { VT_I4 };
+      for (i.lVal = 0; i.lVal < count; ++i.lVal) {
+        ScopedComPtr<IDispatch> folder;
+        windows->Item(i, folder.Receive());
+        if (folder != NULL) {
+          ScopedComPtr<IWebBrowser2> browser;
+          if (SUCCEEDED(browser.QueryFrom(folder))) {
+            found = true;
+            browser->Stop();
+            Sleep(1000);
+            VARIANT empty = ScopedVariant::kEmptyVariant;
+            hr = browser->Navigate(url, &empty, &empty, &empty, &empty);
+            DCHECK(SUCCEEDED(hr)) << "Failed to navigate";
+            break;
+          }
+        }
+      }
+    }
+    if (!found) {
+      DLOG(INFO) << "Waiting for browser to initialize...";
+      ::Sleep(100);
+      retries++;
+    }
+  }
+
+  DCHECK(retries < kMaxRetries);
+  DCHECK(found);
+
+  ::SysFreeString(url);
+
+  return 0;
+}
+
+void CFUrlRequestUnittestRunner::StartChromeFrameInHostBrowser() {
+  if (!ShouldLaunchBrowser())
+    return;
+
+  win_util::ScopedCOMInitializer com;
+  chrome_frame_test::CloseAllIEWindows();
+
+  test_http_server_.reset(new test_server::SimpleWebServer(kTestServerPort));
+  test_http_server_->AddResponse(&chrome_frame_html_);
+  std::wstring url(StringPrintf(L"http://localhost:%i/chrome_frame",
+                                kTestServerPort).c_str());
+
+  // Launch IE.  This launches IE correctly on Vista too.
+  ScopedHandle ie_process(chrome_frame_test::LaunchIE(url));
+  EXPECT_TRUE(ie_process.IsValid());
+
+  // NOTE: If you're running IE8 and CF is not being loaded, you need to
+  // disable IE8's prebinding until CF properly handles that situation.
+  //
+  // HKCU\Software\Microsoft\Internet Explorer\Main
+  // Value name: EnablePreBinding (REG_DWORD)
+  // Value: 0
+}
+
+void CFUrlRequestUnittestRunner::ShutDownHostBrowser() {
+  if (ShouldLaunchBrowser()) {
+    win_util::ScopedCOMInitializer com;
+    chrome_frame_test::CloseAllIEWindows();
+  }
+}
+
+// Override virtual void Initialize to not call icu initialize
+void CFUrlRequestUnittestRunner::Initialize() {
+  DCHECK(::GetCurrentThreadId() == test_thread_id_);
+
+  // Start by replicating some of the steps that would otherwise be
+  // done by TestSuite::Initialize.  We can't call the base class
+  // directly because it will attempt to initialize some things such as
+  // ICU that have already been initialized for this process.
+  InitializeLogging();
+  base::Time::EnableHiResClockForTests();
+
+#if !defined(PURIFY) && defined(OS_WIN)
+  logging::SetLogAssertHandler(UnitTestAssertHandler);
+#endif  // !defined(PURIFY)
+
+  // Next, do some initialization for NetTestSuite.
+  NetTestSuite::InitializeTestThread();
+}
+
+void CFUrlRequestUnittestRunner::Shutdown() {
+  DCHECK(::GetCurrentThreadId() == test_thread_id_);
+  NetTestSuite::Shutdown();
+}
+
+void CFUrlRequestUnittestRunner::OnConnectAutomationProviderToChannel(
+    const std::string& channel_id) {
+  Profile* profile = g_browser_process->profile_manager()->
+      GetDefaultProfile(fake_chrome_.user_data());
+
+  AutomationProviderList* list =
+      g_browser_process->InitAutomationProviderList();
+  DCHECK(list);
+  list->AddProvider(TestAutomationProvider::NewAutomationProvider(profile,
+      channel_id, this));
+}
+
+void CFUrlRequestUnittestRunner::OnInitialTabLoaded() {
+  test_http_server_.reset();
+  StartTests();
+}
+
+void CFUrlRequestUnittestRunner::RunMainUIThread() {
+  DCHECK(MessageLoop::current());
+  DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI);
+
+  // Register the main thread by instantiating it, but don't call any methods.
+  ChromeThread main_thread;
+  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+  MessageLoop::current()->Run();
+}
+
+void CFUrlRequestUnittestRunner::StartTests() {
+  if (PromptAfterSetup())
+    MessageBoxA(NULL, "click ok to run", "", MB_OK);
+
+  DCHECK_EQ(test_thread_.IsValid(), false);
+  test_thread_.Set(::CreateThread(NULL, 0, RunAllUnittests, this, 0,
+                                  &test_thread_id_));
+  DCHECK(test_thread_.IsValid());
+}
+
+// static
+DWORD CFUrlRequestUnittestRunner::RunAllUnittests(void* param) {
+  PlatformThread::SetName("CFUrlRequestUnittestRunner");
+  CFUrlRequestUnittestRunner* me =
+      reinterpret_cast<CFUrlRequestUnittestRunner*>(param);
+  me->Run();
+  me->fake_chrome_.ui_loop()->PostTask(FROM_HERE,
+      NewRunnableFunction(TakeDownBrowser, me));
+  return 0;
+}
+
+// static
+void CFUrlRequestUnittestRunner::TakeDownBrowser(
+    CFUrlRequestUnittestRunner* me) {
+  if (PromptAfterSetup())
+    MessageBoxA(NULL, "click ok to exit", "", MB_OK);
+
+  me->ShutDownHostBrowser();
+}
+
+void CFUrlRequestUnittestRunner::InitializeLogging() {
+  FilePath exe;
+  PathService::Get(base::FILE_EXE, &exe);
+  FilePath log_filename = exe.ReplaceExtension(FILE_PATH_LITERAL("log"));
+  logging::InitLogging(log_filename.value().c_str(),
+                       logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG,
+                       logging::LOCK_LOG_FILE,
+                       logging::DELETE_OLD_LOG_FILE);
+  // We want process and thread IDs because we may have multiple processes.
+  // Note: temporarily enabled timestamps in an effort to catch bug 6361.
+  logging::SetLogItems(true, true, true, true);
+}
+
+void FilterDisabledTests() {
+  if (::testing::FLAGS_gtest_filter.GetLength() &&
+      ::testing::FLAGS_gtest_filter.Compare("*") != 0) {
+    // Don't override user specified filters.
+    return;
+  }
+
+  const char* disabled_tests[] = {
+    // Tests disabled since they're testing the same functionality used
+    // by the TestAutomationProvider.
+    "URLRequestTest.InterceptNetworkError",

+    "URLRequestTest.InterceptRestartRequired",

+    "URLRequestTest.InterceptRespectsCancelMain",

+    "URLRequestTest.InterceptRespectsCancelRedirect",

+    "URLRequestTest.InterceptRespectsCancelFinal",

+    "URLRequestTest.InterceptRespectsCancelInRestart",
+    "URLRequestTest.InterceptRedirect",
+    "URLRequestTest.InterceptServerError",
+    "URLRequestTestFTP.*",
+
+    // Tests that are currently not working:
+
+    // Temporarily disabled because they needs user input (login dialog).
+    "URLRequestTestHTTP.BasicAuth",
+    "URLRequestTestHTTP.BasicAuthWithCookies",
+
+    // HTTPS tests temporarily disabled due to the certificate error dialog.
+    // TODO(tommi): The tests currently fail though, so need to fix.
+    "HTTPSRequestTest.HTTPSMismatchedTest",
+    "HTTPSRequestTest.HTTPSExpiredTest",
+
+    // Tests chrome's network stack's cache (might not apply to CF).
+    "URLRequestTestHTTP.VaryHeader",
+
+    // I suspect we can only get this one to work (if at all) on IE8 and
+    // later by using the new INTERNET_OPTION_SUPPRESS_BEHAVIOR flags
+    // See http://msdn.microsoft.com/en-us/library/aa385328(VS.85).aspx
+    "URLRequestTest.DoNotSaveCookies",
+  };
+
+  std::string filter("-");  // All following filters will be negative.
+  for (int i = 0; i < arraysize(disabled_tests); ++i) {
+    if (i > 0)
+      filter += ":";
+    filter += disabled_tests[i];
+  }
+
+  ::testing::FLAGS_gtest_filter = filter;
+}
+
+int main(int argc, char** argv) {
+  DialogWatchdog watchdog;
+  // See url_request_unittest.cc for these credentials.
+  SupplyProxyCredentials credentials("user", "secret");
+  watchdog.AddObserver(&credentials);
+  testing::InitGoogleTest(&argc, argv);
+  FilterDisabledTests();
+  CFUrlRequestUnittestRunner test_suite(argc, argv);
+  test_suite.RunMainUIThread();
+  return 0;
+}
diff --git a/chrome_frame/test/net/fake_external_tab.h b/chrome_frame/test/net/fake_external_tab.h
new file mode 100644
index 0000000..6ce4f93a
--- /dev/null
+++ b/chrome_frame/test/net/fake_external_tab.h
@@ -0,0 +1,106 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_TEST_NET_FAKE_EXTERNAL_TAB_H_
+#define CHROME_FRAME_TEST_NET_FAKE_EXTERNAL_TAB_H_
+
+#include <string>
+
+#include "base/file_path.h"
+#include "base/message_loop.h"
+
+#include "chrome/app/scoped_ole_initializer.h"
+#include "chrome/browser/browser_process_impl.h"
+
+#include "chrome_frame/test/test_server.h"
+#include "chrome_frame/test/net/test_automation_provider.h"
+#include "chrome_frame/test/net/process_singleton_subclass.h"
+
+#include "net/base/net_test_suite.h"
+
+class ProcessSingleton;
+
+class FakeExternalTab { 
+ public:
+  FakeExternalTab();
+  ~FakeExternalTab();
+
+  virtual std::wstring GetProfileName();
+
+  virtual std::wstring GetProfilePath();
+  virtual void Initialize();
+  virtual void Shutdown();
+
+  const FilePath& user_data() const {
+    return user_data_dir_;
+  }
+
+  MessageLoopForUI* ui_loop() {
+    return &loop_;
+  }
+
+ protected:
+  MessageLoopForUI loop_;
+  scoped_ptr<BrowserProcess> browser_process_;
+  FilePath overridden_user_dir_;
+  FilePath user_data_dir_;
+  ScopedOleInitializer ole_initializer_;  // For RegisterDropTarget etc to work.
+  scoped_ptr<ProcessSingleton> process_singleton_;
+};
+
+// The "master class" that spins the UI and test threads.
+class CFUrlRequestUnittestRunner
+    : public NetTestSuite,
+      public ProcessSingletonSubclassDelegate,
+      public TestAutomationProviderDelegate {
+ public:
+  CFUrlRequestUnittestRunner(int argc, char** argv);
+  ~CFUrlRequestUnittestRunner();
+
+  virtual void StartChromeFrameInHostBrowser();
+
+  virtual void ShutDownHostBrowser();
+
+  // Overrides to not call icu initialize
+  virtual void Initialize();
+  virtual void Shutdown();
+
+  // ProcessSingletonSubclassDelegate.
+  virtual void OnConnectAutomationProviderToChannel(
+      const std::string& channel_id);
+
+  // TestAutomationProviderDelegate.
+  virtual void OnInitialTabLoaded();
+
+  void RunMainUIThread();
+
+  void StartTests();
+
+ protected:
+  // This is the thread that runs all the UrlRequest tests.
+  // Within its context, the Initialize() and Shutdown() routines above
+  // will be called.
+  static DWORD WINAPI RunAllUnittests(void* param);
+
+  static void TakeDownBrowser(CFUrlRequestUnittestRunner* me);
+
+ protected:
+  // Borrowed from TestSuite::Initialize().
+  void InitializeLogging();
+
+ protected:
+  ScopedHandle test_thread_;
+  DWORD test_thread_id_;
+  scoped_ptr<MessageLoop> test_thread_message_loop_;
+
+  scoped_ptr<test_server::SimpleWebServer> test_http_server_;
+  test_server::SimpleResponse chrome_frame_html_;
+
+  // The fake chrome instance.  This instance owns the UI message loop
+  // on the main thread.
+  FakeExternalTab fake_chrome_;
+  scoped_ptr<ProcessSingletonSubclass> pss_subclass_;
+};
+
+#endif  // CHROME_FRAME_TEST_NET_FAKE_EXTERNAL_TAB_H_
diff --git a/chrome_frame/test/net/process_singleton_subclass.cc b/chrome_frame/test/net/process_singleton_subclass.cc
new file mode 100644
index 0000000..2206a74
--- /dev/null
+++ b/chrome_frame/test/net/process_singleton_subclass.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/test/net/process_singleton_subclass.h"
+
+#include "base/command_line.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "chrome/browser/browser_process_impl.h"
+#include "chrome/browser/profile_manager.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome_frame/test/net/test_automation_provider.h"
+#include "chrome_frame/function_stub.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+ProcessSingletonSubclass::ProcessSingletonSubclass(
+    ProcessSingletonSubclassDelegate* delegate)
+    : stub_(NULL), delegate_(delegate), original_wndproc_(NULL) {
+}
+
+ProcessSingletonSubclass::~ProcessSingletonSubclass() {
+  if (stub_) {
+    stub_->BypassStub(reinterpret_cast<void*>(original_wndproc_));
+  }
+}
+
+bool ProcessSingletonSubclass::Subclass(const FilePath& user_data_dir) {
+  DCHECK(stub_ == NULL);
+  DCHECK(original_wndproc_ == NULL);
+  HWND hwnd = FindWindowEx(HWND_MESSAGE, NULL, chrome::kMessageWindowClass,
+                           user_data_dir.ToWStringHack().c_str());
+  if (!::IsWindow(hwnd))
+    return false;
+
+  // The window must be in this process for us to be able to subclass it.
+  DWORD pid = 0;
+  ::GetWindowThreadProcessId(hwnd, &pid);
+  EXPECT_EQ(pid, ::GetCurrentProcessId());
+
+  original_wndproc_ = reinterpret_cast<WNDPROC>(::GetWindowLongPtr(hwnd,
+      GWLP_WNDPROC));
+  stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this),
+                               &SubclassWndProc);
+  DCHECK(stub_);
+  ::SetWindowLongPtr(hwnd, GWLP_WNDPROC,
+      reinterpret_cast<LONG_PTR>(stub_->code()));
+  return true;
+}
+
+// static
+LRESULT ProcessSingletonSubclass::SubclassWndProc(ProcessSingletonSubclass* me,
+                                                  HWND hwnd, UINT msg,
+                                                  WPARAM wp, LPARAM lp) {
+  switch (msg) {
+    case WM_COPYDATA:
+      return me->OnCopyData(hwnd, reinterpret_cast<HWND>(wp),
+                            reinterpret_cast<COPYDATASTRUCT*>(lp));
+    default:
+      break;
+  }
+
+  return me->original_wndproc_(hwnd, msg, wp, lp);
+}
+
+// static
+LRESULT ProcessSingletonSubclass::OnCopyData(HWND hwnd, HWND from_hwnd,
+                                             const COPYDATASTRUCT* cds) {
+  // We should have enough room for the shortest command (min_message_size)
+  // and also be a multiple of wchar_t bytes. The shortest command
+  // possible is L"START\0\0" (empty current directory and command line).
+  static const int kMinMessageSize = sizeof(L"START\0");
+  EXPECT_TRUE(kMinMessageSize <= cds->cbData);
+
+  if (kMinMessageSize > cds->cbData)
+    return TRUE;
+
+  // We split the string into 4 parts on NULLs.
+  const wchar_t* begin = reinterpret_cast<const wchar_t*>(cds->lpData);
+  const wchar_t* end = begin + (cds->cbData / sizeof(wchar_t));
+  const wchar_t kNull = L'\0';
+  const wchar_t* eos = wmemchr(begin, kNull, end - begin);
+  EXPECT_NE(eos, end);
+  if (lstrcmpW(begin, L"START") == 0) {
+    begin = eos + 1;
+    EXPECT_TRUE(begin <= end);
+    eos = wmemchr(begin, kNull, end - begin);
+    EXPECT_NE(eos, end);
+
+    // Get current directory.
+    const wchar_t* cur_dir = begin;
+    begin = eos + 1;
+    EXPECT_TRUE(begin <= end);
+    eos = wmemchr(begin, kNull, end - begin);
+    // eos might be equal to end at this point.
+
+    // Get command line.
+    std::wstring cmd_line(begin, static_cast<size_t>(end - begin));
+
+    CommandLine parsed_command_line(L"");
+    parsed_command_line.ParseFromString(cmd_line);
+    std::string channel_id(WideToASCII(parsed_command_line.GetSwitchValue(
+        switches::kAutomationClientChannelID)));
+    EXPECT_FALSE(channel_id.empty());
+
+    delegate_->OnConnectAutomationProviderToChannel(channel_id);
+  }
+  return TRUE;
+}
diff --git a/chrome_frame/test/net/process_singleton_subclass.h b/chrome_frame/test/net/process_singleton_subclass.h
new file mode 100644
index 0000000..dd9ce367
--- /dev/null
+++ b/chrome_frame/test/net/process_singleton_subclass.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2009 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.
+#ifndef CHROME_FRAME_TEST_NET_PROCESS_SINGLETON_SUBCLASS_H_
+#define CHROME_FRAME_TEST_NET_PROCESS_SINGLETON_SUBCLASS_H_
+
+#include <windows.h>
+#include "base/file_path.h"
+
+struct FunctionStub;
+
+class ProcessSingletonSubclassDelegate {
+ public:
+  virtual void OnConnectAutomationProviderToChannel(
+      const std::string& channel_id) = 0;
+};
+
+class ProcessSingletonSubclass {
+ public:
+  ProcessSingletonSubclass(ProcessSingletonSubclassDelegate* delegate);
+  ~ProcessSingletonSubclass();
+
+  bool Subclass(const FilePath& user_data_dir);
+
+ protected:
+  static LRESULT CALLBACK SubclassWndProc(ProcessSingletonSubclass* me,
+      HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
+  LRESULT OnCopyData(HWND hwnd, HWND from_hwnd, const COPYDATASTRUCT* cds);
+ protected:
+  FunctionStub* stub_;
+  ProcessSingletonSubclassDelegate* delegate_;
+  WNDPROC original_wndproc_;
+};
+
+#endif  // CHROME_FRAME_TEST_NET_PROCESS_SINGLETON_SUBCLASS_H_
+
diff --git a/chrome_frame/test/net/test_automation_provider.cc b/chrome_frame/test/net/test_automation_provider.cc
new file mode 100644
index 0000000..3a56aa48
--- /dev/null
+++ b/chrome_frame/test/net/test_automation_provider.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/test/net/test_automation_provider.h"
+
+#include "base/command_line.h"
+#include "chrome/test/automation/automation_messages.h"
+
+#include "chrome_frame/test/net/test_automation_resource_message_filter.h"
+
+namespace {
+
+// A special command line switch to just run the unit tests without CF in
+// the picture.  Can be useful when the harness itself needs to be debugged.
+const wchar_t kNoCfTestRun[] = L"no-cf-test-run";
+
+bool CFTestsDisabled() {
+  static bool switch_present = CommandLine::ForCurrentProcess()->
+      HasSwitch(kNoCfTestRun);
+  return switch_present;
+}
+
+}  // end namespace
+
+TestAutomationProvider::TestAutomationProvider(
+    Profile* profile,
+    TestAutomationProviderDelegate* delegate)
+    : AutomationProvider(profile), tab_handle_(-1), delegate_(delegate) {
+  filter_ = new TestAutomationResourceMessageFilter(this);
+  URLRequest::RegisterRequestInterceptor(this);
+}
+
+TestAutomationProvider::~TestAutomationProvider() {
+  URLRequest::UnregisterRequestInterceptor(this);
+}
+
+void TestAutomationProvider::OnMessageReceived(const IPC::Message& msg) {
+  if (filter_->OnMessageReceived(msg))
+    return;  // Message handled by the filter.
+
+  __super::OnMessageReceived(msg);
+}
+
+// IPC override to grab the tab handle.
+bool TestAutomationProvider::Send(IPC::Message* msg) {
+  if (msg->type() == AutomationMsg_TabLoaded::ID) {
+    DCHECK(tab_handle_ == -1) << "Currently only support one tab";
+    void* iter = NULL;
+    CHECK(msg->ReadInt(&iter, &tab_handle_));
+    DLOG(INFO) << "Got tab handle: " << tab_handle_;
+    DCHECK(tab_handle_ != -1 && tab_handle_ != 0);
+    delegate_->OnInitialTabLoaded();
+  }
+
+  return AutomationProvider::Send(msg);
+}
+
+URLRequestJob* TestAutomationProvider::MaybeIntercept(URLRequest* request) {
+  if (CFTestsDisabled())
+    return NULL;
+
+  if (request->url().SchemeIs("http") || request->url().SchemeIs("https")) {
+    // Only look at requests that don't have any user data.
+    // ResourceDispatcherHost uses the user data for requests that it manages.
+    // We don't want to mess with those.
+
+    // We could also check if the current thread is our TestUrlRequest thread
+    // and only intercept requests that belong to that thread.
+    if (request->GetUserData(NULL) == NULL) {
+      DCHECK(tab_handle_ != -1);
+      URLRequestAutomationJob* job = new URLRequestAutomationJob(request,
+          tab_handle_, filter_);
+      return job;
+    }
+  }
+
+  return NULL;
+}
+
+// static
+TestAutomationProvider* TestAutomationProvider::NewAutomationProvider(
+    Profile* p, const std::string& channel,
+    TestAutomationProviderDelegate* delegate) {
+  TestAutomationProvider* automation = new TestAutomationProvider(p, delegate);
+  automation->ConnectToChannel(channel);
+  automation->SetExpectedTabCount(1);
+  return automation;
+}
diff --git a/chrome_frame/test/net/test_automation_provider.h b/chrome_frame/test/net/test_automation_provider.h
new file mode 100644
index 0000000..75830ba2
--- /dev/null
+++ b/chrome_frame/test/net/test_automation_provider.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2009 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.
+#ifndef CHROME_FRAME_TEST_NET_TEST_AUTOMATION_PROVIDER_H_
+#define CHROME_FRAME_TEST_NET_TEST_AUTOMATION_PROVIDER_H_
+
+#include "chrome/browser/automation/automation_provider.h"
+
+class TestAutomationResourceMessageFilter;
+
+// Callback interface for TestAutomationProvider.
+class TestAutomationProviderDelegate {
+ public:
+  virtual void OnInitialTabLoaded() = 0;
+};
+
+// A slightly customized version of AutomationProvider.
+// We override AutomationProvider to be able to filter received messages
+// (see TestAutomationResourceMessageFilter) and know when the initial
+// ExternalTab has been loaded.
+// In order to intercept UrlRequests and make the URLRequestAutomationJob class
+// handle requests from unit tests, we also implement URLRequest::Interceptor.
+class TestAutomationProvider
+    : public AutomationProvider,
+      public URLRequest::Interceptor {
+ public:
+  explicit TestAutomationProvider(Profile* profile,
+      TestAutomationProviderDelegate* delegate);
+
+  virtual ~TestAutomationProvider();
+  
+  // AutomationProvider overrides.
+  virtual void OnMessageReceived(const IPC::Message& msg);
+  virtual bool Send(IPC::Message* msg);
+
+  // URLRequest::Interceptor.
+  virtual URLRequestJob* MaybeIntercept(URLRequest* request);
+
+  // Call to instantiate and initialize a new instance of
+  // TestAutomationProvider.
+  static TestAutomationProvider* NewAutomationProvider(
+      Profile* p,
+      const std::string& channel,
+      TestAutomationProviderDelegate* delegate);
+
+ protected:
+  scoped_refptr<TestAutomationResourceMessageFilter> filter_;
+  int tab_handle_;
+  TestAutomationProviderDelegate* delegate_;
+};
+
+#endif CHROME_FRAME_TEST_NET_TEST_AUTOMATION_PROVIDER_H_
diff --git a/chrome_frame/test/net/test_automation_resource_message_filter.cc b/chrome_frame/test/net/test_automation_resource_message_filter.cc
new file mode 100644
index 0000000..32ef532e1f
--- /dev/null
+++ b/chrome_frame/test/net/test_automation_resource_message_filter.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/test/net/test_automation_resource_message_filter.h"
+
+TestAutomationResourceMessageFilter::TestAutomationResourceMessageFilter(
+    AutomationProvider* automation) : automation_(automation) {
+}
+
+bool TestAutomationResourceMessageFilter::Send(IPC::Message* message) {
+  return automation_->Send(message);
+}
+
+// static
+void TestAutomationResourceMessageFilter::OnRequestMessage(
+    URLRequestAutomationJob* job, IPC::Message* msg) {
+  job->OnMessage(*msg);
+  delete msg;
+}
+
+bool TestAutomationResourceMessageFilter::OnMessageReceived(
+    const IPC::Message& message) {
+  // See if we want to process this message... call the base class
+  // for filter messages, send the message to the correct thread
+  // for URL requests.
+  bool handled = false;
+  int request_id = URLRequestAutomationJob::MayFilterMessage(message);
+  if (request_id) {
+    RequestMap::iterator it = requests_.find(request_id);
+    if (it != requests_.end()) {
+      handled = true;
+      IPC::Message* msg = new IPC::Message(message);
+      RequestJob& job = it->second;
+      job.loop_->PostTask(FROM_HERE, NewRunnableFunction(OnRequestMessage,
+                                                         job.job_, msg));
+    }
+  } else {
+    handled = AutomationResourceMessageFilter::OnMessageReceived(message);
+  }
+  return handled;
+}
+
+// Add request to the list of outstanding requests.
+bool TestAutomationResourceMessageFilter::RegisterRequest(
+    URLRequestAutomationJob* job) {
+  // Store the request in an internal map like the parent class
+  // does, but also store the current loop pointer so we can send
+  // request messages to that loop.
+  DCHECK(requests_.end() == requests_.find(job->id()));
+  RequestJob request_job = { MessageLoop::current(), job };
+  requests_[job->id()] = request_job;
+  return true;
+}
+
+// Remove request from the list of outstanding requests.
+void TestAutomationResourceMessageFilter::UnRegisterRequest(
+    URLRequestAutomationJob* job) {
+  requests_.erase(job->id());
+}
diff --git a/chrome_frame/test/net/test_automation_resource_message_filter.h b/chrome_frame/test/net/test_automation_resource_message_filter.h
new file mode 100644
index 0000000..310307ab
--- /dev/null
+++ b/chrome_frame/test/net/test_automation_resource_message_filter.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2009 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.
+#ifndef CHROME_FRAME_TEST_NET_TEST_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_
+#define CHROME_FRAME_TEST_NET_TEST_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_
+
+#include "chrome/browser/automation/automation_provider.h"
+#include "chrome/browser/automation/url_request_automation_job.h"
+
+// Performs the same duties as AutomationResourceMessageFilter but with one
+// difference.  Instead of being tied to an IPC channel running on Chrome's
+// IO thread, this instance runs on the unit test's IO thread (all URL request
+// tests have their own IO loop) and is tied to an instance of
+// AutomationProvider (TestAutomationProvider to be exact).
+//
+// Messages from the AutomationProvider that are destined to request objects
+// owned by this class are marshaled over to the request's IO thread instead
+// of using the thread the messages are received on.  This way we allow the
+// URL request tests to run sequentially as they were written while still
+// allowing the automation layer to work as it normally does (i.e. process
+// its messages on the receiving thread).
+class TestAutomationResourceMessageFilter
+    : public AutomationResourceMessageFilter {
+ public:
+  TestAutomationResourceMessageFilter(AutomationProvider* automation);
+
+  virtual bool Send(IPC::Message* message);
+
+  static void OnRequestMessage(URLRequestAutomationJob* job,
+                               IPC::Message* msg);
+
+  virtual bool OnMessageReceived(const IPC::Message& message);
+
+  // Add request to the list of outstanding requests.
+  virtual bool RegisterRequest(URLRequestAutomationJob* job);
+  // Remove request from the list of outstanding requests.
+  virtual void UnRegisterRequest(URLRequestAutomationJob* job);
+
+ protected:
+  AutomationProvider* automation_;
+  // declare the request map.
+  struct RequestJob {
+    MessageLoop* loop_;
+    scoped_refptr<URLRequestAutomationJob> job_;
+  };
+  typedef std::map<int, RequestJob> RequestMap;
+  RequestMap requests_;
+};
+
+#endif  // CHROME_FRAME_TEST_NET_TEST_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_
diff --git a/chrome_frame/test/perf/chrome_frame_perftest.cc b/chrome_frame/test/perf/chrome_frame_perftest.cc
new file mode 100644
index 0000000..79bbd7b5
--- /dev/null
+++ b/chrome_frame/test/perf/chrome_frame_perftest.cc
@@ -0,0 +1,1137 @@
+// Copyright (c) 2006-2009 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.
+#include "chrome_frame/test/perf/chrome_frame_perftest.h"
+
+#include <atlwin.h>
+#include <atlhost.h>
+#include <map>
+#include <vector>
+#include <string>
+
+#include "chrome_tab.h"  // Generated from chrome_tab.idl.
+
+#include "base/file_util.h"
+#include "base/registry.h"
+#include "base/scoped_ptr.h"
+#include "base/scoped_bstr_win.h"
+#include "base/scoped_comptr_win.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/chrome_process_util.h"
+#include "chrome/test/perf/mem_usage.h"
+#include "chrome/test/ui/ui_test.h"
+
+#include "chrome_frame/test_utils.h"
+#include "chrome_frame/utils.h"
+
+const wchar_t kSilverlightControlKey[] =
+    L"CLSID\\{DFEAF541-F3E1-4c24-ACAC-99C30715084A}\\InprocServer32";
+
+const wchar_t kFlashControlKey[] =
+    L"CLSID\\{D27CDB6E-AE6D-11cf-96B8-444553540000}\\InprocServer32";
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+// Callback description for onload, onloaderror, onmessage
+static _ATL_FUNC_INFO g_single_param = {CC_STDCALL, VT_EMPTY, 1, {VT_VARIANT}};
+// Simple class that forwards the callbacks.
+template <typename T>
+class DispCallback
+    : public IDispEventSimpleImpl<1, DispCallback<T>, &IID_IDispatch> {
+ public:
+  typedef HRESULT (T::*Method)(VARIANT* param);
+
+  DispCallback(T* owner, Method method) : owner_(owner), method_(method) {
+  }
+
+  BEGIN_SINK_MAP(DispCallback)
+    SINK_ENTRY_INFO(1, IID_IDispatch, DISPID_VALUE, OnCallback, &g_single_param)
+  END_SINK_MAP()
+
+  virtual ULONG STDMETHODCALLTYPE AddRef() {
+    return owner_->AddRef();
+  }
+  virtual ULONG STDMETHODCALLTYPE Release() {
+    return owner_->Release();
+  }
+
+  STDMETHOD(OnCallback)(VARIANT param) {
+    return (owner_->*method_)(&param);
+  }
+
+  IDispatch* ToDispatch() {
+    return reinterpret_cast<IDispatch*>(this);
+  }
+
+  T* owner_;
+  Method method_;
+};
+
+// This class implements an ActiveX container which hosts the ChromeFrame
+// ActiveX control. It provides hooks which can be implemented by derived
+// classes for implementing performance measurement, etc.
+class ChromeFrameActiveXContainer
+    : public CWindowImpl<ChromeFrameActiveXContainer, CWindow, CWinTraits <
+          WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
+          WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> >,
+      public CComObjectRootEx<CComSingleThreadModel>,
+      public IPropertyNotifySink {
+ public:
+  ~ChromeFrameActiveXContainer() {
+    if (m_hWnd)
+      DestroyWindow();
+  }
+
+  DECLARE_WND_CLASS_EX(L"ChromeFrameActiveX_container", 0, 0)
+
+  BEGIN_COM_MAP(ChromeFrameActiveXContainer)
+    COM_INTERFACE_ENTRY(IPropertyNotifySink)
+  END_COM_MAP()
+
+  BEGIN_MSG_MAP(ChromeFrameActiveXContainer)
+    MESSAGE_HANDLER(WM_CREATE, OnCreate)
+    MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
+  END_MSG_MAP()
+
+  HRESULT OnMessageCallback(VARIANT* param) {
+    DLOG(INFO) << __FUNCTION__;
+    OnMessageCallbackImpl(param);
+    return S_OK;
+  }
+
+  HRESULT OnLoadErrorCallback(VARIANT* param) {
+    DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal;
+    OnLoadErrorCallbackImpl(param);
+    return S_OK;
+  }
+
+  HRESULT OnLoadCallback(VARIANT* param) {
+    DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal;
+    OnLoadCallbackImpl(param);
+    return S_OK;
+  }
+
+  ChromeFrameActiveXContainer() :
+      prop_notify_cookie_(0),
+      onmsg_(this, &ChromeFrameActiveXContainer::OnMessageCallback),
+      onloaderror_(this, &ChromeFrameActiveXContainer::OnLoadErrorCallback),
+      onload_(this, &ChromeFrameActiveXContainer::OnLoadCallback) {
+  }
+
+  LRESULT OnCreate(UINT , WPARAM , LPARAM , BOOL& ) {
+    chromeview_.Attach(m_hWnd);
+    return 0;
+  }
+
+  // This will be called twice.
+  // Once from CAxHostWindow::OnDestroy (through DefWindowProc)
+  // and once more from the ATL since CAxHostWindow::OnDestroy claims the
+  // message is not handled.
+  LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& handled) {  // NOLINT
+    if (prop_notify_cookie_) {
+      AtlUnadvise(tab_, IID_IPropertyNotifySink, prop_notify_cookie_);
+      prop_notify_cookie_ = 0;
+    }
+
+    tab_.Release();
+    return 0;
+  }
+
+  virtual void OnFinalMessage(HWND /*hWnd*/) {
+    ::PostQuitMessage(6);
+  }
+
+  static const wchar_t* GetWndCaption() {
+    return L"ChromeFrame Container";
+  }
+
+  // IPropertyNotifySink
+  STDMETHOD(OnRequestEdit)(DISPID disp_id) {
+    OnRequestEditImpl(disp_id);
+    return S_OK;
+  }
+
+  STDMETHOD(OnChanged)(DISPID disp_id) {
+    if (disp_id != DISPID_READYSTATE)
+      return S_OK;
+
+    long ready_state;
+    HRESULT hr = tab_->get_readyState(&ready_state);
+    DCHECK(hr == S_OK);
+
+    OnReadyStateChanged(ready_state);
+
+    if (ready_state == READYSTATE_COMPLETE) {
+      if(!starting_url_.empty()) {
+        Navigate(starting_url_.c_str());
+      } else {
+        PostMessage(WM_CLOSE);
+      }
+    } else if (ready_state == READYSTATE_UNINITIALIZED) {
+      DLOG(ERROR) << __FUNCTION__ << " Chrome launch failed.";
+    }
+
+    return S_OK;
+  }
+
+  void CreateChromeFrameWindow(const std::string& starting_url) {
+    starting_url_ = starting_url;
+    RECT rc = { 0, 0, 800, 600 };
+    Create(NULL, rc);
+    DCHECK(m_hWnd);
+    ShowWindow(SW_SHOWDEFAULT);
+  }
+
+  void CreateControl(bool setup_event_sinks) {
+    HRESULT hr = chromeview_.CreateControl(L"ChromeTab.ChromeFrame");
+    EXPECT_HRESULT_SUCCEEDED(hr);
+    hr = chromeview_.QueryControl(tab_.Receive());
+    EXPECT_HRESULT_SUCCEEDED(hr);
+
+    if (setup_event_sinks)
+      SetupEventSinks();
+  }
+
+  void Navigate(const char* url) {
+    BeforeNavigateImpl(url);
+
+    HRESULT hr = tab_->put_src(ScopedBstr(UTF8ToWide(url).c_str()));
+    DCHECK(hr == S_OK) << "Chrome frame NavigateToURL(" << url
+                       << StringPrintf(L") failed 0x%08X", hr);
+  }
+
+  void SetupEventSinks() {
+    HRESULT hr = AtlAdvise(tab_, this, IID_IPropertyNotifySink,
+                           &prop_notify_cookie_);
+    DCHECK(hr == S_OK) << "AtlAdvice for IPropertyNotifySink failed " << hr;
+
+    CComVariant onmessage(onmsg_.ToDispatch());
+    CComVariant onloaderror(onloaderror_.ToDispatch());
+    CComVariant onload(onload_.ToDispatch());
+    EXPECT_HRESULT_SUCCEEDED(tab_->put_onmessage(onmessage));
+    EXPECT_HRESULT_SUCCEEDED(tab_->put_onloaderror(onloaderror));
+    EXPECT_HRESULT_SUCCEEDED(tab_->put_onload(onload));
+  }
+
+ protected:
+  // These functions are implemented by derived classes for special behavior
+  // like performance measurement, etc.
+  virtual void OnReadyStateChanged(long ready_state) {}
+  virtual void OnRequestEditImpl(DISPID disp_id) {}
+
+  virtual void OnMessageCallbackImpl(VARIANT* param) {}
+
+  virtual void OnLoadCallbackImpl(VARIANT* param) {
+    PostMessage(WM_CLOSE);
+  }
+
+  virtual void OnLoadErrorCallbackImpl(VARIANT* param) {
+    PostMessage(WM_CLOSE);
+  }
+  virtual void BeforeNavigateImpl(const char* url) {}
+
+  CAxWindow chromeview_;
+  ScopedComPtr<IChromeFrame> tab_;
+  DWORD prop_notify_cookie_;
+  DispCallback<ChromeFrameActiveXContainer> onmsg_;
+  DispCallback<ChromeFrameActiveXContainer> onloaderror_;
+  DispCallback<ChromeFrameActiveXContainer> onload_;
+  std::string starting_url_;
+};
+
+// This class overrides the hooks provided by the ChromeFrameActiveXContainer
+// class and measures performance at various stages, like initialzation of
+// the Chrome frame widget, navigation, etc.
+class ChromeFrameActiveXContainerPerf : public ChromeFrameActiveXContainer {
+ public:
+  ChromeFrameActiveXContainerPerf() {}
+
+  void CreateControl(bool setup_event_sinks) {
+    perf_initialize_.reset(new PerfTimeLogger("Fully initialized"));
+    PerfTimeLogger perf_create("Create Control");
+
+    HRESULT hr = chromeview_.CreateControl(L"ChromeTab.ChromeFrame");
+    EXPECT_HRESULT_SUCCEEDED(hr);
+    hr = chromeview_.QueryControl(tab_.Receive());
+    EXPECT_HRESULT_SUCCEEDED(hr);
+
+    perf_create.Done();
+    if (setup_event_sinks)
+      SetupEventSinks();
+  }
+
+ protected:
+  virtual void OnReadyStateChanged(long ready_state) {
+    // READYSTATE_COMPLETE is fired when the automation server is ready.
+    if (ready_state == READYSTATE_COMPLETE) {
+      perf_initialize_->Done();
+    } else if (ready_state == READYSTATE_INTERACTIVE) {
+      // Window ready.  Currently we never receive this notification because it
+      // is fired before we finish setting up our hosting environment.
+      // This is because of how ATL is written.  Moving forward we might
+      // have our own hosting classes and then have more control over when we
+      // set up the prop notify sink.
+    } else {
+      DCHECK(ready_state != READYSTATE_UNINITIALIZED) << "failed to initialize";
+    }
+  }
+
+  virtual void OnLoadCallbackImpl(VARIANT* param) {
+    PostMessage(WM_CLOSE);
+    perf_navigate_->Done();
+  }
+
+  virtual void OnLoadErrorCallbackImpl(VARIANT* param) {
+    PostMessage(WM_CLOSE);
+    perf_navigate_->Done();
+  }
+
+  virtual void BeforeNavigateImpl(const char* url ) {
+    std::string test_name = "Navigate ";
+    test_name += url;
+    perf_navigate_.reset(new PerfTimeLogger(test_name.c_str()));
+  }
+
+  scoped_ptr<PerfTimeLogger> perf_initialize_;
+  scoped_ptr<PerfTimeLogger> perf_navigate_;
+};
+
+// This class provides common functionality which can be used for most of the
+// ChromeFrame/Tab performance tests.
+class ChromeFramePerfTestBase : public UITest {
+ public:
+  ChromeFramePerfTestBase() {}
+ protected:
+  scoped_ptr<ScopedChromeFrameRegistrar> chrome_frame_registrar_;
+};
+
+class ChromeFrameStartupTest : public ChromeFramePerfTestBase {
+ public:
+  ChromeFrameStartupTest() {}
+
+  virtual void SetUp() {
+    ASSERT_TRUE(PathService::Get(chrome::DIR_APP, &dir_app_));
+
+    chrome_dll_ = dir_app_.Append(FILE_PATH_LITERAL("chrome.dll"));
+    chrome_exe_ = dir_app_.Append(
+        FilePath::FromWStringHack(chrome::kBrowserProcessExecutableName));
+    chrome_frame_dll_ = dir_app_.Append(
+        FILE_PATH_LITERAL("servers\\npchrome_tab.dll"));
+  }
+  virtual void TearDown() {}
+
+  // TODO(iyengar)
+  // This function is similar to the RunStartupTest function used in chrome
+  // startup tests. Refactor into a common implementation.
+  void RunStartupTest(const char* graph, const char* trace,
+                      const char* startup_url, bool test_cold,
+                      int total_binaries, const FilePath binaries_to_evict[],
+                      bool important, bool ignore_cache_error) {
+    const int kNumCycles = 20;
+
+    startup_url_ = startup_url;
+
+    TimeDelta timings[kNumCycles];
+
+    for (int i = 0; i < kNumCycles; ++i) {
+      if (test_cold) {
+        for (int binary_index = 0; binary_index < total_binaries;
+             binary_index++) {
+          bool result = EvictFileFromSystemCacheWrapper(
+              binaries_to_evict[binary_index]);
+          if (!ignore_cache_error) {
+            ASSERT_TRUE(result);
+          } else if (!result) {
+            printf("\nFailed to evict file %ls from cache. Not running test\n",
+                   binaries_to_evict[binary_index].value().c_str());
+            return;
+          }
+        }
+      }
+
+      TimeTicks start_time, end_time;
+
+      RunStartupTestImpl(&start_time, &end_time);
+
+      timings[i] = end_time - start_time;
+
+      CoFreeUnusedLibraries();
+      ASSERT_TRUE(GetModuleHandle(L"npchrome_tab.dll") == NULL);
+
+      // TODO(beng): Can't shut down so quickly. Figure out why, and fix. If we
+      // do, we crash.
+      PlatformThread::Sleep(50);
+    }
+
+    std::string times;
+    for (int i = 0; i < kNumCycles; ++i)
+      StringAppendF(&times, "%.2f,", timings[i].InMillisecondsF());
+
+    PrintResultList(graph, "", trace, times, "ms", important);
+  }
+
+  FilePath dir_app_;
+  FilePath chrome_dll_;
+  FilePath chrome_exe_;
+  FilePath chrome_frame_dll_;
+
+ protected:
+  // Individual startup tests should implement this function.
+  virtual void RunStartupTestImpl(TimeTicks* start_time,
+                                  TimeTicks* end_time) {}
+
+  // The host is torn down by this function. It should not be used after
+  // this function returns.
+  static void ReleaseHostComReferences(CAxWindow& host) {
+    CComPtr<IAxWinHostWindow> spWinHost;
+    host.QueryHost(&spWinHost);
+    ASSERT_TRUE(spWinHost != NULL);
+
+    // Hack to get the host to release all interfaces and thus ensure that
+    // the COM server can be unloaded.
+    CAxHostWindow* host_window = static_cast<CAxHostWindow*>(spWinHost.p);
+    host_window->ReleaseAll();
+    host.DestroyWindow();
+  }
+
+  std::string startup_url_;
+};
+
+class ChromeFrameStartupTestActiveX : public ChromeFrameStartupTest {
+ public:
+  virtual void SetUp() {
+    // Register the Chrome Frame DLL in the build directory.
+    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar);
+
+    ChromeFrameStartupTest::SetUp();
+  }
+
+ protected:
+  virtual void RunStartupTestImpl(TimeTicks* start_time,
+                                  TimeTicks* end_time) {
+    *start_time = TimeTicks::Now();
+    SimpleModule module;
+    AtlAxWinInit();
+    CComObjectStackEx<ChromeFrameActiveXContainer> wnd;
+    wnd.CreateChromeFrameWindow(startup_url_);
+    wnd.CreateControl(true);
+    module.RunMessageLoop();
+    *end_time = TimeTicks::Now();
+  }
+};
+
+// This class measures the load time of chrome and chrome frame binaries
+class ChromeFrameBinariesLoadTest : public ChromeFrameStartupTestActiveX {
+ protected:
+  virtual void RunStartupTestImpl(TimeTicks* start_time,
+                                  TimeTicks* end_time) {
+    *start_time = TimeTicks::Now();
+
+    HMODULE chrome_exe = LoadLibrary(chrome_exe_.ToWStringHack().c_str());
+    ASSERT_TRUE(chrome_exe != NULL);
+
+    HMODULE chrome_dll = LoadLibrary(chrome_dll_.ToWStringHack().c_str());
+    ASSERT_TRUE(chrome_dll != NULL);
+
+    HMODULE chrome_tab_dll =
+        LoadLibrary(chrome_frame_dll_.ToWStringHack().c_str());
+    ASSERT_TRUE(chrome_tab_dll != NULL);
+
+    *end_time = TimeTicks::Now();
+
+    FreeLibrary(chrome_exe);
+    FreeLibrary(chrome_dll);
+    FreeLibrary(chrome_tab_dll);
+  }
+};
+
+// This class provides functionality to run the startup performance test for
+// the ChromeFrame ActiveX against a reference build. At this point we only run
+// this test in warm mode.
+class ChromeFrameStartupTestActiveXReference
+    : public ChromeFrameStartupTestActiveX {
+ public:
+  // override the browser directory to use the reference build instead.
+  virtual void SetUp() {
+    // Register the reference build Chrome Frame DLL.
+    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar);
+    chrome_frame_registrar_->RegisterReferenceChromeFrameBuild();
+
+    ChromeFrameStartupTest::SetUp();
+    chrome_frame_dll_ = FilePath::FromWStringHack(
+        chrome_frame_registrar_->GetChromeFrameDllPath());
+  }
+
+  virtual void TearDown() {
+    // Reregister the Chrome Frame DLL in the build directory.
+    chrome_frame_registrar_.reset(NULL);
+  }
+};
+
+// This class provides base functionality to measure ChromeFrame memory
+// usage.
+// TODO(iyengar)
+// Some of the functionality in this class like printing the results, etc
+// is based on the chrome\test\memory_test.cc. We need to factor out
+// the common code.
+class ChromeFrameMemoryTest : public ChromeFramePerfTestBase {
+
+  // Contains information about the memory consumption of a process.
+  class ProcessMemoryInfo {
+   public:
+    // Default constructor
+    // Added to enable us to add ProcessMemoryInfo instances to a map.
+    ProcessMemoryInfo()
+        : process_id_(0),
+          peak_virtual_size_(0),
+          virtual_size_(0),
+          peak_working_set_size_(0),
+          working_set_size_(0),
+          chrome_browser_process_(false),
+          chrome_frame_memory_test_instance_(NULL) {}
+
+    ProcessMemoryInfo(base::ProcessId process_id, bool chrome_browser_process,
+                     ChromeFrameMemoryTest* memory_test_instance)
+        : process_id_(process_id),
+          peak_virtual_size_(0),
+          virtual_size_(0),
+          peak_working_set_size_(0),
+          working_set_size_(0),
+          chrome_browser_process_(chrome_browser_process),
+          chrome_frame_memory_test_instance_(memory_test_instance) {}
+
+    bool GetMemoryConsumptionDetails() {
+      return GetMemoryInfo(process_id_,
+                           &peak_virtual_size_,
+                           &virtual_size_,
+                           &peak_working_set_size_,
+                           &working_set_size_);
+    }
+
+    void Print(const char* test_name) {
+      std::string trace_name(test_name);
+
+      ASSERT_TRUE(chrome_frame_memory_test_instance_ != NULL);
+
+      if (chrome_browser_process_) {
+         chrome_frame_memory_test_instance_->PrintResult(
+             "vm_final_browser", "", trace_name + "_vm_b",
+             virtual_size_ / 1024, "KB", false /* not important */);
+         chrome_frame_memory_test_instance_->PrintResult(
+             "ws_final_browser", "", trace_name + "_ws_b",
+             working_set_size_ / 1024, "KB", false /* not important */);
+      } else if (process_id_ == GetCurrentProcessId()) {
+        chrome_frame_memory_test_instance_->PrintResult(
+            "vm_current_process", "", trace_name + "_vm_c",
+            virtual_size_ / 1024, "KB", false /* not important */);
+        chrome_frame_memory_test_instance_->PrintResult(
+            "ws_current_process", "", trace_name + "_ws_c",
+            working_set_size_ / 1024, "KB", false /* not important */);
+      }
+
+      printf("\n");
+    }
+
+    int process_id_;
+    size_t peak_virtual_size_;
+    size_t virtual_size_;
+    size_t peak_working_set_size_;
+    size_t working_set_size_;
+    // Set to true if this is the chrome browser process.
+    bool chrome_browser_process_;
+
+    // A reference to the ChromeFrameMemoryTest instance. Used to print memory
+    // consumption information.
+    ChromeFrameMemoryTest* chrome_frame_memory_test_instance_;
+  };
+
+  // This map tracks memory usage for a process. It is keyed on the process
+  // id.
+  typedef std::map<DWORD, ProcessMemoryInfo> ProcessMemoryConsumptionMap;
+
+ public:
+  ChromeFrameMemoryTest()
+      : current_url_index_(0),
+        browser_pid_(0) {}
+
+  virtual void SetUp() {
+    // Register the Chrome Frame DLL in the build directory.
+    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar);
+  }
+
+  void RunTest(const char* test_name, char* urls[], int total_urls) {
+    ASSERT_TRUE(urls != NULL);
+    ASSERT_GT(total_urls, 0);
+
+    // Record the initial CommitCharge.  This is a system-wide measurement,
+    // so if other applications are running, they can create variance in this
+    // test.
+    start_commit_charge_ = GetSystemCommitCharge();
+
+    for (int i = 0; i < total_urls; i++)
+      urls_.push_back(urls[i]);
+
+    std::string url;
+    GetNextUrl(&url);
+    ASSERT_TRUE(!url.empty());
+
+    StartTest(url, test_name);
+  }
+
+  void OnNavigationSuccess(VARIANT* param) {
+    ASSERT_TRUE(param != NULL);
+    ASSERT_EQ(VT_BSTR, param->vt);
+
+    DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal;
+    InitiateNextNavigation();
+  }
+
+  void OnNavigationFailure(VARIANT* param) {
+    ASSERT_TRUE(param != NULL);
+    ASSERT_EQ(VT_BSTR, param->vt);
+
+    DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal;
+    InitiateNextNavigation();
+  }
+
+ protected:
+  bool GetNextUrl(std::string* url) {
+    if (current_url_index_ >= urls_.size())
+      return false;
+
+    *url = urls_[current_url_index_++];
+    return true;
+  }
+
+  // Returns the path of the current chrome.exe being used by this test.
+  // This could return the regular chrome path or that of the reference
+  // build.
+  std::wstring GetChromeExePath() {
+    std::wstring chrome_exe_path =
+        chrome_frame_registrar_->GetChromeFrameDllPath();
+    EXPECT_FALSE(chrome_exe_path.empty());
+
+    file_util::UpOneDirectory(&chrome_exe_path);
+
+    std::wstring chrome_exe_test_path = chrome_exe_path;
+    file_util::AppendToPath(&chrome_exe_test_path,
+                            chrome::kBrowserProcessExecutableName);
+
+    if (!file_util::PathExists(chrome_exe_test_path)) {
+      file_util::UpOneDirectory(&chrome_exe_path);
+
+      chrome_exe_test_path = chrome_exe_path;
+      file_util::AppendToPath(&chrome_exe_test_path,
+                              chrome::kBrowserProcessExecutableName);
+    }
+
+    EXPECT_TRUE(file_util::PathExists(chrome_exe_test_path));
+
+    return chrome_exe_path;
+  }
+
+  void InitiateNextNavigation() {
+    if (browser_pid_ == 0) {
+      std::wstring profile_directory;
+      if (GetUserProfileBaseDirectory(&profile_directory)) {
+        file_util::AppendToPath(&profile_directory,
+                                GetHostProcessName(false));
+      }
+
+      user_data_dir_ = FilePath::FromWStringHack(profile_directory);
+      browser_pid_ = ChromeBrowserProcessId(user_data_dir_);
+    }
+
+    EXPECT_TRUE(static_cast<int>(browser_pid_) > 0);
+
+    // Get the memory consumption information for the child processes
+    // of the chrome browser.
+    ChromeProcessList child_processes = GetBrowserChildren();
+    ChromeProcessList::iterator index;
+    for (index = child_processes.begin(); index != child_processes.end();
+         ++index) {
+      AccountProcessMemoryUsage(*index);
+    }
+
+    // TODO(iyengar): Bug 2953
+    // Need to verify if this is still true.
+    // The automation crashes periodically if we cycle too quickly.
+    // To make these tests more reliable, slowing them down a bit.
+    Sleep(200);
+
+    std::string url;
+    bool next_url = GetNextUrl(&url);
+    if (!url.empty()) {
+      NavigateImpl(url);
+    } else {
+      TestCompleted();
+    }
+  }
+
+  void PrintResults(const char* test_name) {
+    PrintMemoryUsageInfo(test_name);
+    memory_consumption_map_.clear();
+
+    // Added to give the OS some time to flush the used pages for the
+    // chrome processes which would have exited by now.
+    Sleep(200);
+
+    size_t end_commit_charge = GetSystemCommitCharge();
+    size_t commit_size = end_commit_charge - start_commit_charge_;
+
+    std::string trace_name(test_name);
+    trace_name.append("_cc");
+
+    PrintResult("commit_charge", "", trace_name,
+                commit_size / 1024, "KB", true /* important */);
+    printf("\n");
+  }
+
+  ChromeProcessList GetBrowserChildren() {
+    ChromeProcessList list = GetRunningChromeProcesses(user_data_dir_);
+    ChromeProcessList::iterator browser =
+        std::find(list.begin(), list.end(), browser_pid_);
+    if (browser != list.end()) {
+      list.erase(browser);
+    }
+    return list;
+  }
+
+  void AccountProcessMemoryUsage(DWORD process_id) {
+    ProcessMemoryInfo process_memory_info(process_id,
+                                          process_id == browser_pid_, this);
+
+    ASSERT_TRUE(process_memory_info.GetMemoryConsumptionDetails());
+
+    memory_consumption_map_[process_id] = process_memory_info;
+  }
+
+  void PrintMemoryUsageInfo(const char* test_name) {
+    printf("\n");
+
+    std::string trace_name(test_name);
+
+    ProcessMemoryConsumptionMap::iterator index;
+    size_t total_virtual_size = 0;
+    size_t total_working_set_size = 0;
+
+    for (index = memory_consumption_map_.begin();
+         index != memory_consumption_map_.end();
+         ++index) {
+      ProcessMemoryInfo& memory_info = (*index).second;
+      memory_info.Print(test_name);
+
+      total_virtual_size += memory_info.virtual_size_;
+      total_working_set_size += memory_info.working_set_size_;
+    }
+
+    printf("\n");
+
+    PrintResult("vm_final_total", "", trace_name + "_vm",
+                total_virtual_size / 1024, "KB",
+                false /* not important */);
+    PrintResult("ws_final_total", "", trace_name + "_ws",
+                total_working_set_size / 1024, "KB",
+                true /* important */);
+  }
+
+  // Should never get called.
+  virtual void StartTest(const std::string& url,
+                         const std::string& test_name) = 0 {
+    ASSERT_FALSE(false);
+  }
+
+  // Should never get called.
+  virtual void NavigateImpl(const std::string& url) = 0 {
+    ASSERT_FALSE(false);
+  }
+
+  virtual void TestCompleted() = 0 {
+    ASSERT_FALSE(false);
+  }
+
+  // Holds the commit charge at the start of the memory test run.
+  size_t start_commit_charge_;
+
+  // The index of the URL being tested.
+  size_t current_url_index_;
+
+  // The chrome browser pid.
+  base::ProcessId browser_pid_;
+
+  // Contains the list of urls against which the tests are run.
+  std::vector<std::string> urls_;
+
+  ProcessMemoryConsumptionMap memory_consumption_map_;
+};
+
+// This class provides functionality to run the memory test against a reference
+// chrome frame build.
+class ChromeFrameMemoryTestReference : public ChromeFrameMemoryTest {
+ public:
+  virtual void SetUp() {
+    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar);
+    chrome_frame_registrar_->RegisterReferenceChromeFrameBuild();
+  }
+
+  virtual void TearDown() {
+    // Reregisters the chrome frame DLL in the build directory.
+    chrome_frame_registrar_.reset(NULL);
+  }
+};
+
+// This class overrides the hooks provided by the ChromeFrameActiveXContainer
+// class and calls back into the ChromeFrameMemoryTest object instance,
+// which measures ChromeFrame memory usage.
+class ChromeFrameActiveXContainerMemory : public ChromeFrameActiveXContainer {
+ public:
+  ChromeFrameActiveXContainerMemory()
+      : delegate_(NULL) {}
+
+  ~ChromeFrameActiveXContainerMemory() {}
+
+  void Initialize(ChromeFrameMemoryTest* delegate) {
+    ASSERT_TRUE(delegate != NULL);
+    delegate_ = delegate;
+  }
+
+ protected:
+  virtual void OnLoadCallbackImpl(VARIANT* param) {
+    delegate_->OnNavigationSuccess(param);
+  }
+
+  virtual void OnLoadErrorCallbackImpl(VARIANT* param) {
+    delegate_->OnNavigationFailure(param);
+  }
+
+  ChromeFrameMemoryTest* delegate_;
+};
+
+// This class runs memory tests against the ChromeFrame ActiveX.
+template<class MemoryTestBase>
+class ChromeFrameActiveXMemoryTest : public MemoryTestBase {
+ public:
+  ChromeFrameActiveXMemoryTest()
+      : chrome_frame_container_(NULL),
+        test_completed_(false) {}
+
+  ~ChromeFrameActiveXMemoryTest() {
+  }
+
+  void StartTest(const std::string& url, const std::string& test_name) {
+    ASSERT_TRUE(chrome_frame_container_ == NULL);
+
+    test_name_ = test_name;
+
+    SimpleModule module;
+    AtlAxWinInit();
+
+    CComObject<ChromeFrameActiveXContainerMemory>::CreateInstance(
+        &chrome_frame_container_);
+    chrome_frame_container_->AddRef();
+
+    chrome_frame_container_->Initialize(this);
+
+    chrome_frame_container_->CreateChromeFrameWindow(url.c_str());
+    chrome_frame_container_->CreateControl(true);
+
+    module.RunMessageLoop();
+
+    chrome_frame_container_->Release();
+
+    PrintResults(test_name_.c_str());
+
+    CoFreeUnusedLibraries();
+    //ASSERT_TRUE(GetModuleHandle(L"npchrome_tab.dll") == NULL);
+  }
+
+  void NavigateImpl(const std::string& url) {
+    ASSERT_TRUE(chrome_frame_container_ != NULL);
+    ASSERT_TRUE(!url.empty());
+    chrome_frame_container_->Navigate(url.c_str());
+  }
+
+  void TestCompleted() {
+    // This can get called multiple times if the last url results in a
+    // redirect.
+    if (!test_completed_) {
+      ASSERT_NE(browser_pid_, 0);
+
+      // Measure memory usage for the browser process.
+      AccountProcessMemoryUsage(browser_pid_);
+      // Measure memory usage for the current process.
+      AccountProcessMemoryUsage(GetCurrentProcessId());
+
+      test_completed_ = true;
+      EXPECT_TRUE(PostMessage(static_cast<HWND>(*chrome_frame_container_),
+                              WM_CLOSE, 0, 0));
+    }
+  }
+
+ protected:
+  CComObject<ChromeFrameActiveXContainerMemory>* chrome_frame_container_;
+  std::string test_name_;
+  bool test_completed_;
+};
+
+// This class runs tests to measure chrome frame creation only. This will help
+// track overall page load performance with chrome frame instances.
+class ChromeFrameCreationTest : public ChromeFrameStartupTest {
+ protected:
+  virtual void RunStartupTestImpl(TimeTicks* start_time,
+                                  TimeTicks* end_time) {
+    SimpleModule module;
+    AtlAxWinInit();
+    CComObjectStackEx<ChromeFrameActiveXContainer> wnd;
+    wnd.CreateChromeFrameWindow(startup_url_);
+    *start_time = TimeTicks::Now();
+    wnd.CreateControl(false);
+    *end_time = TimeTicks::Now();
+  }
+};
+
+// This class provides functionality to run the chrome frame
+// performance test against a reference build.
+class ChromeFrameCreationTestReference : public ChromeFrameCreationTest {
+ public:
+  // override the browser directory to use the reference build instead.
+  virtual void SetUp() {
+    chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar);
+    chrome_frame_registrar_->RegisterReferenceChromeFrameBuild();
+    ChromeFrameStartupTest::SetUp();
+  }
+
+  virtual void TearDown() {
+    chrome_frame_registrar_.reset(NULL);
+  }
+};
+
+// This class measures the creation time for Flash, which would be used
+// as a baseline to measure chrome frame creation performance.
+class FlashCreationTest : public ChromeFrameStartupTest {
+ protected:
+  virtual void RunStartupTestImpl(TimeTicks* start_time,
+                                  TimeTicks* end_time) {
+    SimpleModule module;
+    AtlAxWinInit();
+    CAxWindow host;
+    RECT rc = {0, 0, 800, 600};
+    host.Create(NULL, rc, NULL,
+        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
+        WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
+    EXPECT_TRUE(host.m_hWnd != NULL);
+
+    *start_time = TimeTicks::Now();
+    HRESULT hr = host.CreateControl(L"ShockwaveFlash.ShockwaveFlash");
+    EXPECT_HRESULT_SUCCEEDED(hr);
+    *end_time = TimeTicks::Now();
+
+    ReleaseHostComReferences(host);
+  }
+};
+
+// This class measures the creation time for Silverlight, which would be used
+// as a baseline to measure chrome frame creation performance.
+class SilverlightCreationTest : public ChromeFrameStartupTest {
+ protected:
+  virtual void RunStartupTestImpl(TimeTicks* start_time,
+                                  TimeTicks* end_time) {
+    SimpleModule module;
+    AtlAxWinInit();
+    CAxWindow host;
+    RECT rc = {0, 0, 800, 600};
+    host.Create(NULL, rc, NULL,
+        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
+        WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
+    EXPECT_TRUE(host.m_hWnd != NULL);
+
+    *start_time = TimeTicks::Now();
+    HRESULT hr = host.CreateControl(L"AgControl.AgControl");
+    EXPECT_HRESULT_SUCCEEDED(hr);
+    *end_time = TimeTicks::Now();
+
+    ReleaseHostComReferences(host);
+  }
+};
+
+TEST(ChromeFramePerf, DISABLED_HostActiveX) {
+  // TODO(stoyan): Create a low integrity level thread && perform the test there
+  SimpleModule module;
+  AtlAxWinInit();
+  CComObjectStackEx<ChromeFrameActiveXContainerPerf> wnd;
+  wnd.CreateChromeFrameWindow("http://www.google.com");
+  wnd.CreateControl(true);
+  module.RunMessageLoop();
+}
+
+TEST(ChromeFramePerf, DISABLED_HostActiveXInvalidURL) {
+  // TODO(stoyan): Create a low integrity level thread && perform the test there
+  SimpleModule module;
+  AtlAxWinInit();
+  CComObjectStackEx<ChromeFrameActiveXContainerPerf> wnd;
+  wnd.CreateChromeFrameWindow("http://non-existent-domain.org/");
+  wnd.CreateControl(true);
+  module.RunMessageLoop();
+}
+
+TEST_F(ChromeFrameStartupTestActiveX, PerfWarm) {
+  RunStartupTest("warm", "t", "about:blank", false /* cold */, 0, NULL,
+                 true /* important */, false);
+}
+
+TEST_F(ChromeFrameBinariesLoadTest, PerfWarm) {
+  RunStartupTest("binary_load_warm", "t", "", false /* cold */, 0, NULL,
+                 true /* important */, false);
+}
+
+TEST_F(ChromeFrameStartupTestActiveX, PerfCold) {
+  FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_, chrome_frame_dll_};
+  RunStartupTest("cold", "t", "about:blank", true /* cold */,
+                 arraysize(binaries_to_evict), binaries_to_evict,
+                 false /* not important */, false);
+}
+
+TEST_F(ChromeFrameBinariesLoadTest, PerfCold) {
+  FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_, chrome_frame_dll_};
+  RunStartupTest("binary_load_cold", "t", "", true /* cold */,
+                 arraysize(binaries_to_evict), binaries_to_evict,
+                 false /* not important */, false);
+}
+
+TEST_F(ChromeFrameStartupTestActiveXReference, PerfWarm) {
+  RunStartupTest("warm", "t_ref", "about:blank", false /* cold */, 0, NULL,
+                 true /* important */, false);
+}
+
+TEST_F(ChromeFrameStartupTestActiveX, PerfChromeFrameInitializationWarm) {
+  RunStartupTest("ChromeFrame_init_warm", "t", "", false /* cold */, 0,
+                 NULL, true /* important */, false);
+}
+
+TEST_F(ChromeFrameStartupTestActiveX, PerfChromeFrameInitializationCold) {
+  FilePath binaries_to_evict[] = {chrome_frame_dll_};
+  RunStartupTest("ChromeFrame_init_cold", "t", "", true /* cold */,
+                 arraysize(binaries_to_evict), binaries_to_evict,
+                 false /* not important */, false);
+}
+
+TEST_F(ChromeFrameStartupTestActiveXReference,
+       PerfChromeFrameInitializationWarm) {
+  RunStartupTest("ChromeFrame_init_warm", "t_ref", "", false /* cold */, 0,
+                 NULL, true /* important */, false);
+}
+
+typedef ChromeFrameActiveXMemoryTest<ChromeFrameMemoryTest>
+    RegularChromeFrameActiveXMemoryTest;
+
+TEST_F(RegularChromeFrameActiveXMemoryTest, MemoryTestAboutBlank) {
+  char *urls[] = {"about:blank"};
+  RunTest("memory_about_blank", urls, arraysize(urls));
+}
+
+// TODO(iyengar)
+// Revisit why the chrome frame dll does not unload correctly when this test is
+// run.
+TEST_F(RegularChromeFrameActiveXMemoryTest, DISABLED_MemoryTestUrls) {
+  // TODO(iyengar)
+  // We should use static pages to measure memory usage.
+  char *urls[] = {
+    "http://www.youtube.com/watch?v=PN2HAroA12w",
+    "http://www.youtube.com/watch?v=KmLJDrsaJmk&feature=channel"
+  };
+
+  RunTest("memory", urls, arraysize(urls));
+}
+
+typedef ChromeFrameActiveXMemoryTest<ChromeFrameMemoryTestReference>
+    ReferenceBuildChromeFrameActiveXMemoryTest;
+
+TEST_F(ReferenceBuildChromeFrameActiveXMemoryTest, MemoryTestAboutBlank) {
+  char *urls[] = {"about:blank"};
+  RunTest("memory_about_blank_reference", urls, arraysize(urls));
+}
+
+// TODO(iyengar)
+// Revisit why the chrome frame dll does not unload correctly when this test is
+// run.
+TEST_F(ReferenceBuildChromeFrameActiveXMemoryTest, DISABLED_MemoryTestUrls) {
+  // TODO(iyengar)
+  // We should use static pages to measure memory usage.
+  char *urls[] = {
+    "http://www.youtube.com/watch?v=PN2HAroA12w",
+    "http://www.youtube.com/watch?v=KmLJDrsaJmk&feature=channel"
+  };
+
+  RunTest("memory_reference", urls, arraysize(urls));
+}
+
+TEST_F(ChromeFrameCreationTest, PerfWarm) {
+  RunStartupTest("creation_warm", "t", "", false /* cold */, 0,
+                 NULL, true /* important */, false);
+}
+
+TEST_F(ChromeFrameCreationTestReference, PerfWarm) {
+  RunStartupTest("creation_warm", "t_ref", "about:blank", false /* cold */, 0,
+                 NULL, true /* not important */, false);
+}
+
+TEST_F(FlashCreationTest, PerfWarm) {
+  RunStartupTest("creation_warm", "t_flash", "", false /* cold */, 0, NULL,
+                 true /* not important */, false);
+}
+
+TEST_F(SilverlightCreationTest, DISABLED_PerfWarm) {
+  RunStartupTest("creation_warm", "t_silverlight", "", false /* cold */, 0,
+                 NULL, false /* not important */, false);
+}
+
+TEST_F(ChromeFrameCreationTest, PerfCold) {
+  FilePath binaries_to_evict[] = {chrome_frame_dll_};
+
+  RunStartupTest("creation_cold", "t", "", true /* cold */,
+                 arraysize(binaries_to_evict), binaries_to_evict,
+                 true /* important */, false);
+}
+
+// Attempt to evict the Flash control can fail on the buildbot as the dll
+// is marked read only. The test run is aborted if we fail to evict the file
+// from the cache. This could also fail if the Flash control is in use.
+// On Vista this could fail because of UAC
+TEST_F(FlashCreationTest, PerfCold) {
+  RegKey flash_key(HKEY_CLASSES_ROOT, kFlashControlKey);
+
+  std::wstring plugin_path;
+  ASSERT_TRUE(flash_key.ReadValue(L"", &plugin_path));
+  ASSERT_FALSE(plugin_path.empty());
+
+  FilePath flash_path = FilePath::FromWStringHack(plugin_path);
+  FilePath binaries_to_evict[] = {flash_path};
+
+  RunStartupTest("creation_cold", "t_flash", "", true /* cold */,
+                 arraysize(binaries_to_evict), binaries_to_evict,
+                 false/* important */, true);
+}
+
+// This test would fail on Vista due to UAC or if the Silverlight control is
+// in use. The test run is aborted if we fail to evict the file from the cache.
+// Disabling this test as the Silverlight dll does not seem to get unloaded
+// correctly causing the attempt to evict the dll from the system cache to
+// fail.
+TEST_F(SilverlightCreationTest, DISABLED_PerfCold) {
+  RegKey silverlight_key(HKEY_CLASSES_ROOT, kSilverlightControlKey);
+
+  std::wstring plugin_path;
+  ASSERT_TRUE(silverlight_key.ReadValue(L"", &plugin_path));
+  ASSERT_FALSE(plugin_path.empty());
+
+  FilePath silverlight_path = FilePath::FromWStringHack(plugin_path);
+  FilePath binaries_to_evict[] = {silverlight_path};
+
+  RunStartupTest("creation_cold", "t_silverlight", "", true /* cold */,
+                 arraysize(binaries_to_evict), binaries_to_evict,
+                 false /* important */, true);
+}
diff --git a/chrome_frame/test/perf/chrome_frame_perftest.h b/chrome_frame/test/perf/chrome_frame_perftest.h
new file mode 100644
index 0000000..5b895f3
--- /dev/null
+++ b/chrome_frame/test/perf/chrome_frame_perftest.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2006-2009 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.
+#ifndef CHROME_FRAME_TEST_PERF_CHROME_FRAME_PERFTEST_H_
+#define CHROME_FRAME_TEST_PERF_CHROME_FRAME_PERFTEST_H_
+#include <atlbase.h>
+#include "base/logging.h"
+#include "base/perftimer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class SimpleModule : public CAtlExeModuleT<SimpleModule> {
+ public:
+  // The ATL code does not set _pAtlModule to NULL on destruction, and therefore
+  // creating new module (for another test) will ASSERT in constructor.
+  ~SimpleModule() {
+    Term();
+    _pAtlModule = NULL;
+  }
+};
+#endif  // CHROME_FRAME_TEST_PERF_CHROME_FRAME_PERFTEST_H_
+
diff --git a/chrome_frame/test/perf/chrometab_perftests.vcproj b/chrome_frame/test/perf/chrometab_perftests.vcproj
new file mode 100644
index 0000000..b894e6a
--- /dev/null
+++ b/chrome_frame/test/perf/chrometab_perftests.vcproj
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="Windows-1252"?>

+<VisualStudioProject

+	ProjectType="Visual C++"

+	Version="8.00"

+	Name="chrometab_perftests"

+	ProjectGUID="{760ABE9D-2B3E-48C5-A571-6CD221A04BD6}"

+	RootNamespace="chrometab_perftests"

+	Keyword="Win32Proj"

+	>

+	<Platforms>

+		<Platform

+			Name="Win32"

+		/>

+	</Platforms>

+	<ToolFiles>

+	</ToolFiles>

+	<Configurations>

+		<Configuration

+			Name="Debug|Win32"

+			OutputDirectory="..\..\chrome\$(ConfigurationName)"

+			IntermediateDirectory="$(ConfigurationName)"

+			ConfigurationType="1"

+			InheritedPropertySheets=".\chrometab_perftests.vsprops;$(SolutionDir)..\build\debug.vsprops"

+			>

+			<Tool

+				Name="VCPreBuildEventTool"

+			/>

+			<Tool

+				Name="VCCustomBuildTool"

+			/>

+			<Tool

+				Name="VCXMLDataGeneratorTool"

+			/>

+			<Tool

+				Name="VCWebServiceProxyGeneratorTool"

+			/>

+			<Tool

+				Name="VCMIDLTool"

+			/>

+			<Tool

+				Name="VCCLCompilerTool"

+				Optimization="0"

+				AdditionalIncludeDirectories=".."

+				PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"

+				MinimalRebuild="true"

+				BasicRuntimeChecks="3"

+				RuntimeLibrary="1"

+				UsePrecompiledHeader="0"

+				WarningLevel="3"

+				Detect64BitPortabilityProblems="false"

+				DebugInformationFormat="4"

+			/>

+			<Tool

+				Name="VCManagedResourceCompilerTool"

+			/>

+			<Tool

+				Name="VCResourceCompilerTool"

+			/>

+			<Tool

+				Name="VCPreLinkEventTool"

+			/>

+			<Tool

+				Name="VCLinkerTool"

+				LinkIncremental="2"

+				GenerateDebugInformation="true"

+				SubSystem="1"

+				TargetMachine="1"

+			/>

+			<Tool

+				Name="VCALinkTool"

+			/>

+			<Tool

+				Name="VCManifestTool"

+			/>

+			<Tool

+				Name="VCXDCMakeTool"

+			/>

+			<Tool

+				Name="VCBscMakeTool"

+			/>

+			<Tool

+				Name="VCFxCopTool"

+			/>

+			<Tool

+				Name="VCAppVerifierTool"

+			/>

+			<Tool

+				Name="VCWebDeploymentTool"

+			/>

+			<Tool

+				Name="VCPostBuildEventTool"

+			/>

+		</Configuration>

+		<Configuration

+			Name="Release|Win32"

+			OutputDirectory="..\..\chrome\$(ConfigurationName)"

+			IntermediateDirectory="$(ConfigurationName)"

+			ConfigurationType="1"

+			InheritedPropertySheets=".\chrometab_perftests.vsprops;$(SolutionDir)..\build\release.vsprops"

+			WholeProgramOptimization="1"

+			>

+			<Tool

+				Name="VCPreBuildEventTool"

+			/>

+			<Tool

+				Name="VCCustomBuildTool"

+			/>

+			<Tool

+				Name="VCXMLDataGeneratorTool"

+			/>

+			<Tool

+				Name="VCWebServiceProxyGeneratorTool"

+			/>

+			<Tool

+				Name="VCMIDLTool"

+			/>

+			<Tool

+				Name="VCCLCompilerTool"

+				AdditionalIncludeDirectories=".."

+				PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"

+				RuntimeLibrary="0"

+				UsePrecompiledHeader="0"

+				WarningLevel="3"

+				Detect64BitPortabilityProblems="false"

+				DebugInformationFormat="3"

+			/>

+			<Tool

+				Name="VCManagedResourceCompilerTool"

+			/>

+			<Tool

+				Name="VCResourceCompilerTool"

+			/>

+			<Tool

+				Name="VCPreLinkEventTool"

+			/>

+			<Tool

+				Name="VCLinkerTool"

+				LinkIncremental="1"

+				GenerateDebugInformation="true"

+				SubSystem="1"

+				OptimizeReferences="2"

+				EnableCOMDATFolding="2"

+				TargetMachine="1"

+			/>

+			<Tool

+				Name="VCALinkTool"

+			/>

+			<Tool

+				Name="VCManifestTool"

+			/>

+			<Tool

+				Name="VCXDCMakeTool"

+			/>

+			<Tool

+				Name="VCBscMakeTool"

+			/>

+			<Tool

+				Name="VCFxCopTool"

+			/>

+			<Tool

+				Name="VCAppVerifierTool"

+			/>

+			<Tool

+				Name="VCWebDeploymentTool"

+			/>

+			<Tool

+				Name="VCPostBuildEventTool"

+			/>

+		</Configuration>

+	</Configurations>

+	<References>

+	</References>

+	<Files>

+		<Filter

+			Name="Source Files"

+			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"

+			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"

+			>

+			<File

+				RelativePath=".\chrometab_perftest.cc"

+				>

+			</File>

+			<File

+				RelativePath=".\chrometab_perftest.h"

+				>

+			</File>

+			<File

+				RelativePath=".\silverlight.cc"

+				>

+			</File>

+			<Filter

+				Name="Common"

+				>

+				<File

+					RelativePath="..\..\chrome\test\chrome_process_util.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\..\chrome\test\chrome_process_util.h"

+					>

+				</File>

+				<File

+					RelativePath="..\..\chrome\test\chrome_process_util_win.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\html_utils.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\..\chrome\test\perf\mem_usage.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\..\base\perf_test_suite.h"

+					>

+				</File>

+				<File

+					RelativePath="..\..\base\perftimer.cc"

+					>

+				</File>

+				<File

+					RelativePath="run_all.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\test_utils.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\test_utils.h"

+					>

+				</File>

+				<File

+					RelativePath="..\..\chrome\test\ui\ui_test.cc"

+					>

+				</File>

+				<File

+					RelativePath="..\utils.cc"

+					>

+				</File>

+			</Filter>

+		</Filter>

+	</Files>

+	<Globals>

+	</Globals>

+</VisualStudioProject>

diff --git a/chrome_frame/test/perf/chrometab_perftests.vsprops b/chrome_frame/test/perf/chrometab_perftests.vsprops
new file mode 100644
index 0000000..3891381
--- /dev/null
+++ b/chrome_frame/test/perf/chrometab_perftests.vsprops
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="Windows-1252"?>

+<VisualStudioPropertySheet

+	ProjectType="Visual C++"

+	Version="8.00"

+	Name="chrometeb_perftests"

+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\libxslt\build\using_libxslt.vsprops;..\..\testing\using_gtest.vsprops;$(SolutionDir)..\chrome\third_party\wtl\using_wtl.vsprops;$(SolutionDir)..\tools\grit\build\using_generated_resources.vsprops"

+	>

+  <Tool

+    Name="VCCLCompilerTool"	

+    PreprocessorDefinitions="_ATL_APARTMENT_THREADED;_ATL_CSTRING_EXPLICIT_CONSTRUCTORS"

+    AdditionalIncludeDirectories=""

+  />

+

+</VisualStudioPropertySheet>

diff --git a/chrome_frame/test/perf/run_all.cc b/chrome_frame/test/perf/run_all.cc
new file mode 100644
index 0000000..93a5a67b
--- /dev/null
+++ b/chrome_frame/test/perf/run_all.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2006-2009 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.
+#include "base/platform_thread.h"
+#include "base/perf_test_suite.h"
+#include "base/scoped_ptr.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome_frame/test_utils.h"
+
+int main(int argc, char **argv) {
+  PerfTestSuite perf_suite(argc, argv);
+  chrome::RegisterPathProvider();
+  PlatformThread::SetName("ChromeFrame perf tests");
+  // Use ctor/raii to register the local Chrome Frame dll.
+  scoped_ptr<ScopedChromeFrameRegistrar> registrar(new ScopedChromeFrameRegistrar);
+  return perf_suite.Run();
+}
diff --git a/chrome_frame/test/perf/silverlight.cc b/chrome_frame/test/perf/silverlight.cc
new file mode 100644
index 0000000..3016c2ba
--- /dev/null
+++ b/chrome_frame/test/perf/silverlight.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 2009 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.

+#include <atlbase.h>

+#include <atlwin.h>

+#include <atlhost.h>

+#include "base/scoped_comptr_win.h"

+#include "chrome_frame/test/perf/chrome_frame_perftest.h"

+

+interface IXcpControlDownloadCallback;

+interface __declspec(uuid("1B36028E-B491-4bb2-8584-8A9E0A677D6E"))

+IXcpControlHost : public IUnknown {

+  typedef enum {

+    XcpHostOption_FreezeOnInitialFrame       = 0x001,

+    XcpHostOption_DisableFullScreen          = 0x002,

+    XcpHostOption_DisableManagedExecution    = 0x008,

+    XcpHostOption_EnableCrossDomainDownloads = 0x010,

+    XcpHostOption_UseCustomAppDomain         = 0x020,

+    XcpHostOption_DisableNetworking          = 0x040,

+    XcpHostOption_DisableScriptCallouts      = 0x080,

+    XcpHostOption_EnableHtmlDomAccess        = 0x100,

+    XcpHostOption_EnableScriptableObjectAccess = 0x200,

+  } XcpHostOptions;

+

+  STDMETHOD(GetHostOptions)(DWORD* pdwOptions) PURE;

+  STDMETHOD(NotifyLoaded()) PURE;

+  STDMETHOD(NotifyError)(BSTR bstrError, BSTR bstrSource,

+      long nLine, long nColumn) PURE;

+  STDMETHOD(InvokeHandler)(BSTR bstrName, VARIANT varArg1, VARIANT varArg2,

+      VARIANT* pvarResult) PURE;

+  STDMETHOD(GetBaseUrl)(BSTR* pbstrUrl) PURE;

+  STDMETHOD(GetNamedSource)(BSTR bstrSourceName, BSTR* pbstrSource) PURE;

+  STDMETHOD(DownloadUrl)(BSTR bstrUrl, IXcpControlDownloadCallback* pCallback,

+      IStream** ppStream) PURE;

+};

+

+// Not templatized, to trade execution speed vs typing

+class IXcpControlHostImpl : public IXcpControlHost {

+ public:

+  STDMETHOD(GetHostOptions)(DWORD* pdwOptions) {

+    return E_NOTIMPL;

+  }

+

+  STDMETHOD(NotifyLoaded()) {

+    return E_NOTIMPL;

+  }

+

+  STDMETHOD(NotifyError)(BSTR bstrError, BSTR bstrSource,

+                         long nLine, long nColumn) {

+    return E_NOTIMPL;

+  }

+

+  STDMETHOD(InvokeHandler)(BSTR bstrName, VARIANT varArg1, VARIANT varArg2,

+                           VARIANT* pvarResult) {

+    return E_NOTIMPL;

+  }

+

+  STDMETHOD(GetBaseUrl)(BSTR* pbstrUrl) {

+    return E_NOTIMPL;

+  }

+

+  STDMETHOD(GetNamedSource)(BSTR bstrSourceName, BSTR* pbstrSource) {

+    return E_NOTIMPL;

+  }

+

+  STDMETHOD(DownloadUrl)(BSTR bstrUrl, IXcpControlDownloadCallback* pCallback,

+                         IStream** ppStream) {

+    return E_NOTIMPL;

+  }

+};

+

+// Silverlight container. Supports do-nothing implementation of IXcpControlHost.

+// Should be extended to do some real movie-or-something download.

+class SilverlightContainer :

+    public IServiceProviderImpl<SilverlightContainer>,

+    public IXcpControlHostImpl,

+    public CWindowImpl<SilverlightContainer, CWindow, CWinTraits<

+        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,

+        WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> >,

+    public CComObjectRootEx<CComSingleThreadModel> {

+ public:

+  DECLARE_WND_CLASS_EX(L"Silverlight_container", 0, 0)

+  BEGIN_COM_MAP(SilverlightContainer)

+    COM_INTERFACE_ENTRY(IServiceProvider)

+    COM_INTERFACE_ENTRY(IXcpControlHost)

+  END_COM_MAP()

+

+  BEGIN_SERVICE_MAP(SilverlightContainer)

+    SERVICE_ENTRY(__uuidof(IXcpControlHost))

+  END_SERVICE_MAP()

+

+  BEGIN_MSG_MAP(ChromeFrameActiveXContainer)

+    MESSAGE_HANDLER(WM_DESTROY, OnDestroy)

+  END_MSG_MAP()

+

+  LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& handled) {

+    host_.Release();

+    return 0;

+  }

+

+  virtual void OnFinalMessage(HWND ) {

+  }

+

+  static const wchar_t* GetWndCaption() {

+    return L"Silverlight Container";

+  }

+

+  HRESULT CreateWndAndHost(RECT* r) {

+    Create(NULL, r);

+    ShowWindow(SW_SHOWDEFAULT);

+

+    CComPtr<IUnknown> spUnkContainer;

+    HRESULT hr = CAxHostWindow::_CreatorClass::CreateInstance(NULL,

+        __uuidof(IAxWinHostWindow), reinterpret_cast<void**>(&host_));

+    if (SUCCEEDED(hr)) {

+      CComPtr<IObjectWithSite> p;

+      hr = host_.QueryInterface(&p);

+      if (SUCCEEDED(hr)) {

+        p->SetSite(GetUnknown());

+      }

+    }

+    return hr;

+  }

+

+  HRESULT CreateControl() {

+    HRESULT hr = host_->CreateControl(L"AgControl.AgControl", m_hWnd, NULL);

+    EXPECT_HRESULT_SUCCEEDED(hr);

+    return hr;

+  }

+

+  ScopedComPtr<IAxWinHostWindow> host_;

+};

+

+// Create and in-place Silverlight control. Should be extended to do something

+// more meaningful.

+TEST(ChromeFramePerf, DISABLED_HostSilverlight2) {

+  SimpleModule module;

+  AtlAxWinInit();

+  CComObjectStackEx<SilverlightContainer> wnd;

+  RECT rc = {0, 0, 800, 600};

+  wnd.CreateWndAndHost(&rc);

+  PerfTimeLogger perf_create("Create Silverlight Control2");

+  wnd.CreateControl();

+  perf_create.Done();

+  wnd.DestroyWindow();

+}

+

+// Simplest test - creates in-place Silverlight control.

+TEST(ChromeFramePerf, DISABLED_HostSilverlight) {

+  SimpleModule module;

+  AtlAxWinInit();

+  CAxWindow host;

+  RECT rc = {0, 0, 800, 600};

+  PerfTimeLogger perf_create("Create Silverlight Control");

+  host.Create(NULL, rc, L"AgControl.AgControl",

+      WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,

+      WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);

+  EXPECT_TRUE(host.m_hWnd != NULL);

+  ScopedComPtr<IDispatch> disp;

+  HRESULT hr = host.QueryControl(disp.Receive());

+  EXPECT_HRESULT_SUCCEEDED(hr);

+  disp.Release();

+  perf_create.Done();

+}

+

diff --git a/chrome_frame/test/run_all_unittests.cc b/chrome_frame/test/run_all_unittests.cc
new file mode 100644
index 0000000..787d765
--- /dev/null
+++ b/chrome_frame/test/run_all_unittests.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2006-2008 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.
+
+#include <atlbase.h>
+
+#include "base/at_exit.h"
+#include "base/platform_thread.h"
+#include "base/process_util.h"
+#include "base/test_suite.h"
+#include "base/command_line.h"
+#include "chrome/common/chrome_paths.h"
+
+#include "chrome_frame/test_utils.h"
+
+// To enable ATL-based code to run in this module
+class ChromeFrameUnittestsModule
+    : public CAtlExeModuleT<ChromeFrameUnittestsModule> {
+};
+
+ChromeFrameUnittestsModule _AtlModule;
+
+const wchar_t kNoRegistrationSwitch[] = L"no-registration";
+
+int main(int argc, char **argv) {
+  base::EnableTerminationOnHeapCorruption();
+  PlatformThread::SetName("ChromeFrame tests");
+
+  TestSuite test_suite(argc, argv);
+
+  // If mini_installer is used to register CF, we use the switch
+  // --no-registration to avoid repetitive registration.
+  if (CommandLine::ForCurrentProcess()->HasSwitch(kNoRegistrationSwitch)) {
+    return test_suite.Run();
+  } else {
+    // Register paths needed by the ScopedChromeFrameRegistrar.
+    chrome::RegisterPathProvider();
+
+    // This will register the chrome frame in the build directory. It currently
+    // leaves that chrome frame registered once the tests are done. It must be
+    // constructed AFTER the TestSuite is created since TestSuites create THE
+    // AtExitManager.
+    // TODO(robertshield): Make these tests restore the original registration
+    // once done.
+    ScopedChromeFrameRegistrar registrar;
+    
+    return test_suite.Run();
+  }
+}
diff --git a/chrome_frame/test/test_server.cc b/chrome_frame/test/test_server.cc
new file mode 100644
index 0000000..79ea2cf6
--- /dev/null
+++ b/chrome_frame/test/test_server.cc
@@ -0,0 +1,211 @@
+// Copyright (c) 2006-2008 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.
+
+#include "base/logging.h"
+#include "base/registry.h"
+#include "base/string_util.h"
+
+#include "chrome_frame/test/test_server.h"
+
+#include "net/base/winsock_init.h"
+#include "net/http/http_util.h"
+
+namespace test_server {
+const char kDefaultHeaderTemplate[] =
+    "HTTP/1.1 %hs\r\n"
+    "Connection: close\r\n"
+    "Content-Type: %hs\r\n"
+    "Content-Length: %i\r\n\r\n";
+const char kStatusOk[] = "200 OK";
+const char kStatusNotFound[] = "404 Not Found";
+const char kDefaultContentType[] = "text/html; charset=UTF-8";
+
+void Request::ParseHeaders(const std::string& headers) {
+  size_t pos = headers.find("\r\n");
+  DCHECK(pos != std::string::npos);
+  if (pos != std::string::npos) {
+    headers_ = headers.substr(pos + 2);
+
+    StringTokenizer tokenizer(headers.begin(), headers.begin() + pos, " ");
+    std::string* parse[] = { &method_, &path_, &version_ };
+    int field = 0;
+    while (tokenizer.GetNext() && field < arraysize(parse)) {
+      parse[field++]->assign(tokenizer.token_begin(),
+                             tokenizer.token_end());
+    }
+  }
+
+  // Check for content-length in case we're being sent some data.
+  net::HttpUtil::HeadersIterator it(headers_.begin(), headers_.end(),
+                                    "\r\n");
+  while (it.GetNext()) {
+    if (LowerCaseEqualsASCII(it.name(), "content-length")) {
+       content_length_ = StringToInt(it.values().c_str());
+       break;
+    }
+  }
+}
+
+bool Connection::CheckRequestReceived() {
+  bool ready = false;
+  if (request_.method().length()) {
+    // Headers have already been parsed.  Just check content length.
+    ready = (data_.size() >= request_.content_length());
+  } else {
+    size_t index = data_.find("\r\n\r\n");
+    if (index != std::string::npos) {
+      // Parse the headers before returning and chop them of the
+      // data buffer we've already received.
+      std::string headers(data_.substr(0, index + 2));
+      request_.ParseHeaders(headers);
+      data_.erase(0, index + 4);
+      ready = (data_.size() >= request_.content_length());
+    }
+  }
+
+  return ready;
+}
+
+bool FileResponse::GetContentType(std::string* content_type) const {
+  size_t length = ContentLength();
+  char buffer[4096];
+  void* data = NULL;
+
+  if (length) {
+    // Create a copy of the first few bytes of the file.
+    // If we try and use the mapped file directly, FindMimeFromData will crash
+    // 'cause it cheats and temporarily tries to write to the buffer!
+    length = std::min(arraysize(buffer), length);
+    memcpy(buffer, file_->data(), length);
+    data = buffer;
+  }
+
+  LPOLESTR mime_type = NULL;
+  FindMimeFromData(NULL, file_path_.value().c_str(), data, length, NULL,
+                   FMFD_DEFAULT, &mime_type, 0);
+  if (mime_type) {
+    *content_type = WideToASCII(mime_type);
+    ::CoTaskMemFree(mime_type);
+  }
+
+  return content_type->length() > 0;
+}
+
+void FileResponse::WriteContents(ListenSocket* socket) const {
+  DCHECK(file_.get());
+  if (file_.get()) {
+    socket->Send(reinterpret_cast<const char*>(file_->data()),
+                 file_->length(), false);
+  }
+}
+
+size_t FileResponse::ContentLength() const {
+  if (file_.get() == NULL) {
+    file_.reset(new file_util::MemoryMappedFile());
+    if (!file_->Initialize(file_path_)) {
+      NOTREACHED();
+      file_.reset();
+    }
+  }
+  return file_.get() ? file_->length() : 0;
+}
+
+bool RedirectResponse::GetCustomHeaders(std::string* headers) const {
+  *headers = StringPrintf("HTTP/1.1 302 Found\r\n"
+                          "Connection: close\r\n"
+                          "Content-Length: 0\r\n"
+                          "Content-Type: text/html\r\n"
+                          "Location: %hs\r\n\r\n", redirect_url_.c_str());
+  return true;
+}
+
+SimpleWebServer::SimpleWebServer(int port) {
+  CHECK(MessageLoop::current()) << "SimpleWebServer requires a message loop";
+  net::EnsureWinsockInit();
+  AddResponse(&quit_);
+  server_ = ListenSocket::Listen("127.0.0.1", port, this);
+  DCHECK(server_.get() != NULL);
+}
+
+SimpleWebServer::~SimpleWebServer() {
+  ConnectionList::const_iterator it;
+  for (it = connections_.begin(); it != connections_.end(); it++)
+    delete (*it);
+  connections_.clear();
+}
+
+void SimpleWebServer::AddResponse(Response* response) {
+  responses_.push_back(response);
+}
+
+Response* SimpleWebServer::FindResponse(const Request& request) const {
+  std::list<Response*>::const_iterator it;
+  for (it = responses_.begin(); it != responses_.end(); it++) {
+    Response* response = (*it);
+    if (response->Matches(request)) {
+      return response;
+    }
+  }
+  return NULL;
+}
+
+Connection* SimpleWebServer::FindConnection(const ListenSocket* socket) const {
+  ConnectionList::const_iterator it;
+  for (it = connections_.begin(); it != connections_.end(); it++) {
+    if ((*it)->IsSame(socket)) {
+      return (*it);
+    }
+  }
+  return NULL;
+}
+
+void SimpleWebServer::DidAccept(ListenSocket* server,
+                                ListenSocket* connection) {
+  connections_.push_back(new Connection(connection));
+}
+
+void SimpleWebServer::DidRead(ListenSocket* connection,
+                              const std::string& data) {
+  Connection* c = FindConnection(connection);
+  DCHECK(c);
+  c->AddData(data);
+  if (c->CheckRequestReceived()) {
+    const Request& request = c->request();
+    Response* response = FindResponse(request);
+    if (response) {
+      std::string headers;
+      if (!response->GetCustomHeaders(&headers)) {
+        std::string content_type;
+        if (!response->GetContentType(&content_type))
+          content_type = kDefaultContentType;
+        headers = StringPrintf(kDefaultHeaderTemplate, kStatusOk,
+                               content_type.c_str(), response->ContentLength());
+      }
+
+      connection->Send(headers, false);
+      response->WriteContents(connection);
+      response->IncrementAccessCounter();
+    } else {
+      std::string payload = "sorry, I can't find " + request.path();
+      std::string headers(StringPrintf(kDefaultHeaderTemplate, kStatusNotFound,
+                                       kDefaultContentType, payload.length()));
+      connection->Send(headers, false);
+      connection->Send(payload, false);
+    }
+  }
+}
+
+void SimpleWebServer::DidClose(ListenSocket* sock) {
+  // To keep the historical list of connections reasonably tidy, we delete
+  // 404's when the connection ends.
+  Connection* c = FindConnection(sock);
+  DCHECK(c);
+  if (!FindResponse(c->request())) {
+    // extremely inefficient, but in one line and not that common... :)
+    connections_.erase(std::find(connections_.begin(), connections_.end(), c));
+    delete c;
+  }
+}
+
+}  // namespace test_server
diff --git a/chrome_frame/test/test_server.h b/chrome_frame/test/test_server.h
new file mode 100644
index 0000000..896b2b3
--- /dev/null
+++ b/chrome_frame/test/test_server.h
@@ -0,0 +1,296 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_FRAME_TEST_TEST_SERVER_H_
+#define CHROME_FRAME_TEST_TEST_SERVER_H_
+
+// Implementation of an HTTP server for tests.
+// To instantiate the server, make sure you have a message loop on the
+// current thread and then create an instance of the SimpleWebServer class.
+// The server uses two basic concepts, a request and a response.
+// The Response interface represents an item (e.g. a document) available from
+// the server.  A Request object represents a request from a client (e.g. a
+// browser).  There are several basic Response classes implemented in this file,
+// all derived from the Response interface.
+//
+// Here's a simple example that starts a web server that can serve up
+// a single document (http://localhost:1337/foo).
+// All other requests will get a 404.
+//
+//  MessageLoopForUI loop;
+//  test_server::SimpleWebServer server(1337);
+//  test_server::SimpleResponse document("/foo", "Hello World!");
+//  test_server.AddResponse(&document);
+//  loop.MessageLoop::Run();
+//
+// To close the web server, just go to http://localhost:1337/quit.
+//
+// All Response classes count how many times they have been accessed.  Just
+// call Response::accessed().
+//
+// To implement a custom response object (e.g. to match against a request
+// based on some data, serve up dynamic content or take some action on the
+// server), just inherit from  one of the response classes or directly from the
+// Response interface and add your response object to the server's list of
+// response objects.
+
+#include <list>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "net/base/listen_socket.h"
+
+namespace test_server {
+
+class Request {
+ public:
+  Request() : content_length_(0) {
+  }
+
+  void ParseHeaders(const std::string& headers);
+
+  const std::string& method() const {
+    return method_;
+  }
+
+  const std::string& path() const {
+    return path_;
+  }
+
+  const std::string& headers() const {
+    return headers_;
+  }
+
+  size_t content_length() const {
+    return content_length_;
+  }
+
+ protected:
+  std::string method_;
+  std::string path_;
+  std::string version_;
+  std::string headers_;
+  size_t content_length_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Request);
+};
+
+// Manages request headers for a single request.
+// For each successful request that's made, the server will keep an instance
+// of this class so that they can be checked even after the server has been
+// shut down.
+class Connection {
+ public:
+  explicit Connection(ListenSocket* sock) : socket_(sock) {
+  }
+
+  ~Connection() {
+  }
+
+  bool IsSame(const ListenSocket* socket) const {
+    return socket_ == socket;
+  }
+
+  void AddData(const std::string& data) {
+    data_ += data;
+  }
+
+  bool CheckRequestReceived();
+
+  const Request& request() const {
+    return request_;
+  }
+
+ protected:
+  scoped_refptr<ListenSocket> socket_;
+  std::string data_;
+  Request request_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Connection);
+};
+
+// Abstract interface with default implementations for some of the methods and
+// a counter for how many times the response object has served requests.
+class Response {
+ public:
+  Response() : accessed_(0) {
+  }
+
+  virtual ~Response() {
+  }
+
+  // Returns true if this response object should be used for a given request.
+  virtual bool Matches(const Request& r) const = 0;
+
+  // Response objects can optionally supply their own HTTP headers, completely
+  // bypassing the default ones.
+  virtual bool GetCustomHeaders(std::string* headers) const {
+    return false;
+  }
+
+  // Optionally provide a content type.  Return false if you don't specify
+  // a content type.
+  virtual bool GetContentType(std::string* content_type) const {
+    return false;
+  }
+
+  virtual size_t ContentLength() const {
+    return 0;
+  }
+
+  virtual void WriteContents(ListenSocket* socket) const {
+  }
+
+  void IncrementAccessCounter() {
+    accessed_++;
+  }
+
+  size_t accessed() const {
+    return accessed_;
+  }
+
+ protected:
+  size_t accessed_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Response);
+};
+
+// Partial implementation of Response that matches a request's path.
+// This is just a convenience implementation for the boilerplate implementation
+// of Matches().  Don't instantiate directly.
+class ResponseForPath : public Response {
+ public:
+  explicit ResponseForPath(const char* request_path)
+      : request_path_(request_path) {
+  }
+
+  virtual bool Matches(const Request& r) const {
+    return r.path().compare(request_path_) == 0;
+  }
+
+ protected:
+   std::string request_path_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ResponseForPath);
+};
+
+// A very basic implementation of a response.
+// A simple response matches a single document path on the server
+// (e.g. "/foo") and returns a document in the form of a string.
+class SimpleResponse : public ResponseForPath {
+ public:
+  SimpleResponse(const char* request_path, const std::string& contents)
+      : ResponseForPath(request_path), contents_(contents) {
+  }
+
+  virtual void WriteContents(ListenSocket* socket) const {
+    socket->Send(contents_.c_str(), contents_.length(), false);
+  }
+
+  virtual size_t ContentLength() const {
+    return contents_.length();
+  }
+
+ protected:
+  std::string contents_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SimpleResponse);
+};
+
+// To serve up files from the web server, create an instance of FileResponse
+// and add it to the server's list of responses.  The content type of the
+// file will be determined by calling FindMimeFromData which examines the
+// contents of the file and performs registry lookups.
+class FileResponse : public ResponseForPath {
+ public:
+  FileResponse(const char* request_path, const FilePath& file_path)
+      : ResponseForPath(request_path), file_path_(file_path) {
+  }
+
+  virtual bool GetContentType(std::string* content_type) const;
+  virtual void WriteContents(ListenSocket* socket) const;
+  virtual size_t ContentLength() const;
+
+ protected:
+  FilePath file_path_;
+  mutable scoped_ptr<file_util::MemoryMappedFile> file_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileResponse);
+};
+
+// Returns a 302 (temporary redirect) to redirect the client from a path
+// on the test server to a different URL.
+class RedirectResponse : public ResponseForPath {
+ public:
+  RedirectResponse(const char* request_path, const std::string& redirect_url)
+      : ResponseForPath(request_path), redirect_url_(redirect_url) {
+  }
+
+  virtual bool GetCustomHeaders(std::string* headers) const;
+
+ protected:
+  std::string redirect_url_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(RedirectResponse);
+};
+
+// typedef for a list of connections.  Used by SimpleWebServer.
+typedef std::list<Connection*> ConnectionList;
+
+// Implementation of a simple http server.
+// Before creating an instance of the server, make sure the current thread
+// has a message loop.
+class SimpleWebServer : public ListenSocket::ListenSocketDelegate {
+ public:
+  explicit SimpleWebServer(int port);
+  virtual ~SimpleWebServer();
+
+  void AddResponse(Response* response);
+
+  // ListenSocketDelegate overrides.
+  virtual void DidAccept(ListenSocket* server, ListenSocket* connection);
+  virtual void DidRead(ListenSocket* connection, const std::string& data);
+  virtual void DidClose(ListenSocket* sock);
+
+  const ConnectionList& connections() {
+    return connections_;
+  }
+
+ protected:
+  class QuitResponse : public SimpleResponse {
+   public:
+    QuitResponse()
+        : SimpleResponse("/quit", "So long and thanks for all the fish.") {
+    }
+
+    virtual void QuitResponse::WriteContents(ListenSocket* socket) const {
+      SimpleResponse::WriteContents(socket);
+      MessageLoop::current()->Quit();
+    }
+  };
+
+  Response* FindResponse(const Request& request) const;
+  Connection* FindConnection(const ListenSocket* socket) const;
+
+ protected:
+  scoped_refptr<ListenSocket> server_;
+  ConnectionList connections_;
+  std::list<Response*> responses_;
+  QuitResponse quit_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SimpleWebServer);
+};
+
+}  // namespace test_server
+
+#endif  // CHROME_FRAME_TEST_TEST_SERVER_H_
diff --git a/chrome_frame/test/test_server_test.cc b/chrome_frame/test/test_server_test.cc
new file mode 100644
index 0000000..4bd139e
--- /dev/null
+++ b/chrome_frame/test/test_server_test.cc
@@ -0,0 +1,194 @@
+// Copyright (c) 2006-2008 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.
+
+#include <windows.h>
+#include <wininet.h>
+
+#include "base/basictypes.h"
+#include "base/path_service.h"
+#include "base/scoped_handle_win.h"
+#include "chrome_frame/test/test_server.h"
+#include "net/base/cookie_monster.h"
+#include "net/base/host_resolver_proc.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache.h"
+#include "net/proxy/proxy_service.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class TestServerTest: public testing::Test {
+ protected:
+  virtual void SetUp() {
+    PathService::Get(base::DIR_SOURCE_ROOT, &source_path_);
+    source_path_ = source_path_.Append(FILE_PATH_LITERAL("chrome_frame"));
+  }
+  virtual void TearDown() {
+  }
+
+ public:
+  const FilePath& source_path() const {
+    return source_path_;
+  }
+
+ protected:
+  FilePath source_path_;
+};
+
+namespace {
+
+class ScopedInternet {
+ public:
+  explicit ScopedInternet(HINTERNET handle)
+      : h_(handle) {
+  }
+  ~ScopedInternet() {
+    if (h_) {
+      InternetCloseHandle(h_);
+    }
+  }
+
+  operator HINTERNET() {
+    return h_;
+  }
+
+ protected:
+  HINTERNET h_;
+};
+
+class URLRequestTestContext : public URLRequestContext {
+ public:
+  URLRequestTestContext() {
+    host_resolver_ = net::CreateSystemHostResolver();
+    proxy_service_ = net::ProxyService::CreateNull();
+    ssl_config_service_ = new net::SSLConfigServiceDefaults;
+    http_transaction_factory_ =
+        new net::HttpCache(
+          net::HttpNetworkLayer::CreateFactory(host_resolver_, proxy_service_,
+                                               ssl_config_service_),
+          disk_cache::CreateInMemoryCacheBackend(0));
+    // In-memory cookie store.
+    cookie_store_ = new net::CookieMonster();
+  }
+
+  virtual ~URLRequestTestContext() {
+    delete http_transaction_factory_;
+  }
+};
+
+class TestURLRequest : public URLRequest {
+ public:
+  TestURLRequest(const GURL& url, Delegate* delegate)
+      : URLRequest(url, delegate) {
+    set_context(new URLRequestTestContext());
+  }
+};
+
+class UrlTaskChain {
+ public:
+  UrlTaskChain(const char* url, UrlTaskChain* next)
+      : url_(url), next_(next) {
+  }
+
+  void Run() {
+    EXPECT_EQ(0, delegate_.response_started_count());
+
+    MessageLoopForIO loop;
+
+    TestURLRequest r(GURL(url_), &delegate_);
+    r.Start();
+    EXPECT_TRUE(r.is_pending());
+
+    MessageLoop::current()->Run();
+
+    EXPECT_EQ(1, delegate_.response_started_count());
+    EXPECT_FALSE(delegate_.received_data_before_response());
+    EXPECT_NE(0, delegate_.bytes_received());
+  }
+
+  UrlTaskChain* next() const {
+    return next_;
+  }
+
+  const std::string& response() const {
+    return delegate_.data_received();
+  }
+
+ protected:
+  std::string url_;
+  TestDelegate delegate_;
+  UrlTaskChain* next_;
+};
+
+DWORD WINAPI FetchUrl(void* param) {
+  UrlTaskChain* task = reinterpret_cast<UrlTaskChain*>(param);
+  while (task != NULL) {
+    task->Run();
+    task = task->next();
+  }
+
+  return 0;
+}
+
+struct QuitMessageHit {
+  explicit QuitMessageHit(MessageLoopForUI* loop) : loop_(loop), hit_(false) {
+  }
+
+  MessageLoopForUI* loop_;
+  bool hit_;
+};
+
+void QuitMessageLoop(QuitMessageHit* msg) {
+  msg->hit_ = true;
+  msg->loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask);
+}
+
+}  // end namespace
+
+TEST_F(TestServerTest, DISABLED_TestServer) {
+  // The web server needs a loop to exist on this thread during construction
+  // the loop must be created before we construct the server.
+  MessageLoopForUI loop;
+
+  test_server::SimpleWebServer server(1337);
+  test_server::SimpleResponse person("/person", "Guthrie Govan!");
+  server.AddResponse(&person);
+  test_server::FileResponse file("/file", source_path().Append(
+      FILE_PATH_LITERAL("CFInstance.js")));
+  server.AddResponse(&file);
+  test_server::RedirectResponse redir("/goog", "http://www.google.com/");
+  server.AddResponse(&redir);
+
+  // We should never hit this, but it's our way to break out of the test if
+  // things start hanging.
+  QuitMessageHit quit_msg(&loop);
+  loop.PostDelayedTask(FROM_HERE,
+                       NewRunnableFunction(QuitMessageLoop, &quit_msg),
+                       10 * 1000);
+
+  UrlTaskChain quit_task("http://localhost:1337/quit", NULL);
+  UrlTaskChain fnf_task("http://localhost:1337/404", &quit_task);
+  UrlTaskChain person_task("http://localhost:1337/person", &fnf_task);
+  UrlTaskChain file_task("http://localhost:1337/file", &person_task);
+  UrlTaskChain goog_task("http://localhost:1337/goog", &file_task);
+
+  DWORD tid = 0;
+  ScopedHandle worker(::CreateThread(NULL, 0, FetchUrl, &goog_task, 0, &tid));
+  loop.MessageLoop::Run();
+
+  EXPECT_FALSE(quit_msg.hit_);
+  if (!quit_msg.hit_) {
+    EXPECT_EQ(::WaitForSingleObject(worker, 10 * 1000), WAIT_OBJECT_0);
+
+    EXPECT_EQ(person.accessed(), 1);
+    EXPECT_EQ(file.accessed(), 1);
+    EXPECT_EQ(redir.accessed(), 1);
+
+    EXPECT_TRUE(person_task.response().find("Guthrie") != std::string::npos);
+    EXPECT_TRUE(file_task.response().find("function") != std::string::npos);
+    EXPECT_TRUE(goog_task.response().find("<title>") != std::string::npos);
+  } else {
+    ::TerminateThread(worker, ~0);
+  }
+}
diff --git a/chrome_frame/test/util_unittests.cc b/chrome_frame/test/util_unittests.cc
new file mode 100644
index 0000000..dc3d72d
--- /dev/null
+++ b/chrome_frame/test/util_unittests.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2006-2009 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.

+

+#include "base/file_version_info.h"

+#include "chrome_frame/test/chrome_frame_unittests.h"

+#include "chrome_frame/utils.h"

+

+const wchar_t kChannelName[] = L"-dev";

+const wchar_t kSuffix[] = L"-fix";

+

+TEST(UtilTests, AppendSuffixToChannelNameTest) {

+  std::wstring str_base;

+  std::wstring channel_name(kChannelName);

+  std::wstring suffix(kSuffix);

+

+  str_base = L"2.0-dev-bar";

+  EXPECT_TRUE(AppendSuffixToChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0-dev-fix-bar", str_base.c_str());

+

+  str_base = L"2.0-dev-fix-bar";

+  EXPECT_FALSE(AppendSuffixToChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0-dev-fix-bar", str_base.c_str());

+

+  str_base = L"2.0-dev-bar-dev-bar";

+  EXPECT_TRUE(AppendSuffixToChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0-dev-fix-bar-dev-bar", str_base.c_str());

+

+  str_base = L"2.0";

+  EXPECT_FALSE(AppendSuffixToChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0", str_base.c_str());

+

+  str_base = L"2.0-devvvv";

+  EXPECT_TRUE(AppendSuffixToChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0-dev-fixvvv", str_base.c_str());

+}

+

+TEST(UtilTests, RemoveSuffixFromStringTest) {

+  std::wstring str_base;

+  std::wstring channel_name(kChannelName);

+  std::wstring suffix(kSuffix);

+

+  str_base = L"2.0-dev-fix";

+  EXPECT_TRUE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0-dev", str_base.c_str());

+

+  str_base = L"2.0-dev-fix-full";

+  EXPECT_TRUE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0-dev-full", str_base.c_str());

+

+  str_base = L"2.0";

+  EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0", str_base.c_str());

+

+  str_base = L"2.0-dev";

+  EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0-dev", str_base.c_str());

+

+  str_base = L"2.0-fix";

+  EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0-fix", str_base.c_str());

+

+  str_base = L"2.0-full-fix";

+  EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0-full-fix", str_base.c_str());

+

+  str_base = L"2.0-dev-dev-fix";

+  EXPECT_TRUE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));

+  EXPECT_STREQ(L"2.0-dev-dev", str_base.c_str());

+}

+

+TEST(UtilTests, GetModuleVersionTest) {

+  HMODULE mod = GetModuleHandle(L"kernel32.dll");

+  EXPECT_NE(mod, static_cast<HMODULE>(NULL));

+  wchar_t path[MAX_PATH] = {0};

+  GetModuleFileName(mod, path, arraysize(path));

+
+  // Use the method that goes to disk
+  scoped_ptr<FileVersionInfo> base_info(
+      FileVersionInfo::CreateFileVersionInfo(path));
+  EXPECT_TRUE(base_info.get() != NULL);
+
+  // Use the method that doesn't go to disk
+  uint32 low = 0, high = 0;
+  EXPECT_TRUE(GetModuleVersion(mod, &high, &low));
+  EXPECT_NE(high, 0);
+  EXPECT_NE(low, 0);
+
+  // Make sure they give the same results.
+  VS_FIXEDFILEINFO* fixed_info = base_info->fixed_file_info();
+  EXPECT_TRUE(fixed_info != NULL);
+

+  EXPECT_EQ(fixed_info->dwFileVersionMS, static_cast<DWORD>(high));

+  EXPECT_EQ(fixed_info->dwFileVersionLS, static_cast<DWORD>(low));

+}

+

+TEST(UtilTests, HaveSameOrigin) {

+  struct OriginCompare {

+    const char* a;

+    const char* b;

+    bool same_origin;

+  } test_cases[] = {

+    { "", "", true },

+    { "*", "*", true },

+    { "*", "+", false },

+    { "http://www.google.com/", "http://www.google.com/", true },

+    { "http://www.google.com", "http://www.google.com/", true },

+    { "http://www.google.com:80/", "http://www.google.com/", true },

+    { "http://www.google.com:8080/", "http://www.google.com/", false },

+    { "https://www.google.com/", "http://www.google.com/", false },

+    { "http://docs.google.com/", "http://www.google.com/", false },

+    { "https://www.google.com/", "https://www.google.com:443/", true },

+    { "https://www.google.com/", "https://www.google.com:443", true },

+  };

+

+  for (int i = 0; i < arraysize(test_cases); ++i) {

+    const OriginCompare& test = test_cases[i];

+    EXPECT_EQ(test.same_origin, HaveSameOrigin(test.a, test.b));

+  }

+}

diff --git a/chrome_frame/test_utils.cc b/chrome_frame/test_utils.cc
new file mode 100644
index 0000000..2d47c71
--- /dev/null
+++ b/chrome_frame/test_utils.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/test_utils.h"
+
+#include <atlbase.h>
+#include <atlwin.h>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Statics
+
+std::wstring ScopedChromeFrameRegistrar::GetChromeFrameBuildPath() {
+  std::wstring build_path;
+  PathService::Get(chrome::DIR_APP, &build_path);
+  file_util::AppendToPath(&build_path, L"servers\\npchrome_tab.dll");
+  file_util::PathExists(build_path);
+  return build_path;
+}
+
+void ScopedChromeFrameRegistrar::RegisterDefaults() {
+  std::wstring dll_path_ = GetChromeFrameBuildPath();
+  RegisterAtPath(dll_path_);
+}
+
+void ScopedChromeFrameRegistrar::RegisterAtPath(
+    const std::wstring& path) {
+
+  ASSERT_FALSE(path.empty());
+  HMODULE chrome_frame_dll_handle = LoadLibrary(path.c_str());
+  ASSERT_TRUE(chrome_frame_dll_handle != NULL);
+
+  typedef HRESULT (STDAPICALLTYPE* DllRegisterServerFn)();
+  DllRegisterServerFn register_server =
+      reinterpret_cast<DllRegisterServerFn>(GetProcAddress(
+          chrome_frame_dll_handle, "DllRegisterServer"));
+
+  ASSERT_TRUE(register_server != NULL);
+  EXPECT_HRESULT_SUCCEEDED((*register_server)());
+
+  DllRegisterServerFn register_npapi_server =
+      reinterpret_cast<DllRegisterServerFn>(GetProcAddress(
+          chrome_frame_dll_handle, "RegisterNPAPIPlugin"));
+
+  if (register_npapi_server != NULL)
+    EXPECT_HRESULT_SUCCEEDED((*register_npapi_server)());
+
+  ASSERT_TRUE(FreeLibrary(chrome_frame_dll_handle));
+}
+
+// Non-statics
+
+ScopedChromeFrameRegistrar::ScopedChromeFrameRegistrar() {
+  original_dll_path_ = GetChromeFrameBuildPath();
+  RegisterChromeFrameAtPath(original_dll_path_);
+}
+
+ScopedChromeFrameRegistrar::~ScopedChromeFrameRegistrar() {
+  if (FilePath(original_dll_path_) != FilePath(new_chrome_frame_dll_path_)) {
+    RegisterChromeFrameAtPath(original_dll_path_);
+  }
+}
+
+void ScopedChromeFrameRegistrar::RegisterChromeFrameAtPath(
+    const std::wstring& path) {
+  RegisterAtPath(path);
+  new_chrome_frame_dll_path_ = path;
+}
+
+void ScopedChromeFrameRegistrar::RegisterReferenceChromeFrameBuild() {
+  std::wstring reference_build_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_APP, &reference_build_dir));
+
+  file_util::UpOneDirectory(&reference_build_dir);
+  file_util::UpOneDirectory(&reference_build_dir);
+
+  file_util::AppendToPath(&reference_build_dir, L"chrome_frame");
+  file_util::AppendToPath(&reference_build_dir, L"tools");
+  file_util::AppendToPath(&reference_build_dir, L"test");
+  file_util::AppendToPath(&reference_build_dir, L"reference_build");
+  file_util::AppendToPath(&reference_build_dir, L"chrome");
+  file_util::AppendToPath(&reference_build_dir, L"servers");
+  file_util::AppendToPath(&reference_build_dir, L"npchrome_tab.dll");
+
+  RegisterChromeFrameAtPath(reference_build_dir);
+}
+
+std::wstring ScopedChromeFrameRegistrar::GetChromeFrameDllPath() const {
+  return new_chrome_frame_dll_path_;
+}
diff --git a/chrome_frame/test_utils.h b/chrome_frame/test_utils.h
new file mode 100644
index 0000000..fb470a0
--- /dev/null
+++ b/chrome_frame/test_utils.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_TEST_UTILS_H_
+#define CHROME_FRAME_TEST_UTILS_H_
+
+#include <string>
+
+// Helper class used to register different chrome frame DLLs while running
+// tests. At construction, this registers the DLL found in the build path.
+// At destruction, again registers the DLL found in the build path if another
+// DLL has since been registered. Triggers GTEST asserts on failure.
+//
+// TODO(robertshield): Ideally, make this class restore the originally
+// registered chrome frame DLL (e.g. by looking in HKCR) on destruction.
+class ScopedChromeFrameRegistrar {
+ public:
+  ScopedChromeFrameRegistrar();
+  virtual ~ScopedChromeFrameRegistrar();
+
+  void RegisterChromeFrameAtPath(const std::wstring& path);
+  void RegisterReferenceChromeFrameBuild();
+
+  std::wstring GetChromeFrameDllPath() const;
+
+  static std::wstring GetChromeFrameBuildPath();
+  static void RegisterAtPath(const std::wstring& path);
+  static void RegisterDefaults();
+
+ private:
+  // Contains the path of the most recently registered npchrome_tab.dll.
+  std::wstring new_chrome_frame_dll_path_;
+
+  // Contains the path of the npchrome_tab.dll to be registered at destruction.
+  std::wstring original_dll_path_;
+};
+
+#endif  // CHROME_FRAME_TEST_UTILS_H_
diff --git a/chrome_frame/tools/test/page_cycler/cf_cycler.py b/chrome_frame/tools/test/page_cycler/cf_cycler.py
new file mode 100644
index 0000000..fd49ae8
--- /dev/null
+++ b/chrome_frame/tools/test/page_cycler/cf_cycler.py
@@ -0,0 +1,99 @@
+# Copyright (c) 2009 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.

+

+"""Automates IE to visit a list of web sites while running CF in full tab mode.

+

+The page cycler automates IE and navigates it to a series of URLs. It is

+designed to be run with Chrome Frame configured to load every URL inside

+CF full tab mode.

+

+TODO(robertshield): Make use of the python unittest module as per 

+review comments.

+"""

+

+import optparse

+import sys

+import time

+import win32com.client

+import win32gui

+

+def LoadSiteList(path):

+  """Loads a list of URLs from |path|.

+

+  Expects the URLs to be separated by newlines, with no leading or trailing

+  whitespace. 

+

+  Args:

+    path: The path to a file containing a list of new-line separated URLs.

+

+  Returns:

+    A list of strings, each one a URL.

+  """

+  f = open(path)

+  urls = f.readlines()

+  f.close()

+  return urls

+

+def LaunchIE():

+  """Starts up IE, makes it visible and returns the automation object. 

+

+  Returns: 

+    The IE automation object.

+  """

+  ie = win32com.client.Dispatch("InternetExplorer.Application")

+  ie.visible = 1

+  win32gui.SetForegroundWindow(ie.HWND)

+  return ie

+

+def RunTest(url, ie):

+  """Loads |url| into the InternetExplorer.Application instance in |ie|.

+

+   Waits for the Document object to be created and then waits for

+   the document ready state to reach READYSTATE_COMPLETE.

+  Args:

+    url: A string containing the url to navigate to.

+    ie: The IE automation object to navigate.

+  """

+

+  print "Navigating to " + url

+  ie.Navigate(url)

+  timer = 0

+

+  READYSTATE_COMPLETE = 4

+

+  last_ready_state = -1

+  for retry in xrange(60):

+    try:

+      # TODO(robertshield): Become an event sink instead of polling for

+      # changes to the ready state.

+      last_ready_state = ie.Document.ReadyState

+      if last_ready_state == READYSTATE_COMPLETE:

+        break

+    except:

+      # TODO(robertshield): Find the precise exception related to ie.Document

+      # being not accessible and handle it here.

+      print "Unexpected error:", sys.exc_info()[0]

+      raise

+    time.sleep(1)

+

+  if last_ready_state != READYSTATE_COMPLETE:

+    print "Timeout waiting for " + url

+

+def main():

+  parser = optparse.OptionParser()

+  parser.add_option('-u', '--url_list', default='urllist',

+                    help='The path to the list of URLs')

+  (opts, args) = parser.parse_args()  

+

+  urls = LoadSiteList(opts.url_list)

+  ie = LaunchIE()  

+  for url in urls:

+    RunTest(url, ie)

+    time.sleep(1)

+  ie.visible = 0

+  ie.Quit()

+

+

+if __name__ == '__main__':

+  main()

diff --git a/chrome_frame/tools/test/page_cycler/urllist b/chrome_frame/tools/test/page_cycler/urllist
new file mode 100644
index 0000000..3242774
--- /dev/null
+++ b/chrome_frame/tools/test/page_cycler/urllist
@@ -0,0 +1,500 @@
+http://www.yahoo.com
+http://www.msn.com
+http://www.google.com
+http://www.youtube.com
+http://www.live.com
+http://www.myspace.com
+http://www.baidu.com
+http://www.orkut.com
+http://www.wikipedia.org
+http://www.qq.com
+http://www.yahoo.co.jp
+http://www.microsoft.com
+http://www.megaupload.com
+http://www.sina.com.cn
+http://www.hi5.com
+http://www.blogger.com
+http://www.facebook.com
+http://www.rapidshare.com
+http://www.ebay.com
+http://www.fotolog.net
+http://www.friendster.com
+http://www.sohu.com
+http://www.163.com
+http://www.google.co.uk
+http://www.mail.ru
+http://www.google.fr
+http://www.google.com.br
+http://www.google.de
+http://www.passport.net
+http://www.taobao.com
+http://www.amazon.com
+http://www.skyblog.com
+http://www.yahoo.com.cn
+http://www.google.cl
+http://www.bbc.co.uk
+http://www.google.es
+http://www.google.cn
+http://www.google.pl
+http://www.imdb.com
+http://www.tom.com
+http://www.yandex.ru
+http://www.wretch.cc
+http://www.google.co.jp
+http://www.uol.com.br
+http://www.flickr.com
+http://www.photobucket.com
+http://www.craigslist.org
+http://www.go.com
+http://www.onet.pl
+http://www.imageshack.us
+http://www.google.com.sa
+http://www.google.com.mx
+http://www.aol.com
+http://www.google.com.tr
+http://www.dailymotion.com
+http://www.allegro.pl
+http://www.rambler.ru
+http://www.fc2.com
+http://www.xunlei.com
+http://www.ebay.co.uk
+http://www.google.com.ar
+http://www.google.ca
+http://www.seznam.cz
+http://www.mixi.jp
+http://www.free.fr
+http://www.imagevenue.com
+http://www.google.com.pe
+http://www.google.it
+http://www.google.co.in
+http://www.terra.com.br
+http://www.cnn.com
+http://www.livejournal.com
+http://www.skyrock.com
+http://www.ebay.de
+http://www.geocities.com
+http://www.adultfriendfinder.com
+http://www.wp.pl
+http://www.googlesyndication.com
+http://www.adobe.com
+http://www.google.co.ve
+http://www.bebo.com
+http://www.xanga.com
+http://www.globo.com
+http://www.google.com.co
+http://www.google.com.eg
+http://www.one.lt
+http://www.wordpress.com
+http://www.rakuten.co.jp
+http://www.uwants.com
+http://www.sendspace.com
+http://www.apple.com
+http://www.google.com.vn
+http://www.discuss.com.hk
+http://www.deviantart.com
+http://www.digg.com
+http://www.tagged.com
+http://www.livedoor.com
+http://www.vnexpress.net
+http://www.eastmoney.com
+http://www.soso.com
+http://www.starware.com
+http://www.badongo.com
+http://www.google.co.th
+http://www.naver.com
+http://www.rediff.com
+http://www.iwiw.hu
+http://www.icq.com
+http://www.vnet.cn
+http://www.ig.com.br
+http://www.badoo.com
+http://www.alibaba.com
+http://www.google.com.au
+http://www.maktoob.com
+http://www.newsgroup.la
+http://www.about.com
+http://www.download.com
+http://www.kooora.com
+http://www.google.co.il
+http://www.comcast.net
+http://www.google.nl
+http://www.google.be
+http://www.megarotic.com
+http://www.sexyono.com
+http://www.sourceforge.net
+http://www.walla.co.il
+http://www.fotka.pl
+http://www.pchome.com.tw
+http://www.theplanet.com
+http://www.google.ae
+http://www.orange.fr
+http://www.interia.pl
+http://www.invisionfree.com
+http://www.rapidshare.de
+http://www.tudou.com
+http://www.statcounter.com
+http://www.56.com
+http://www.netlog.com
+http://www.4shared.com
+http://www.mywebsearch.com
+http://www.digitalpoint.com
+http://www.mininova.org
+http://www.dantri.com.vn
+http://www.126.com
+http://www.goo.ne.jp
+http://www.dell.com
+http://www.google.co.hu
+http://www.google.pt
+http://www.information.com
+http://www.youporn.com
+http://www.metacafe.com
+http://www.google.lt
+http://www.google.ro
+http://www.nba.com
+http://www.metroflog.com
+http://www.miniclip.com
+http://www.cnet.com
+http://www.sogou.com
+http://www.ebay.fr
+http://www.weather.com
+http://www.depositfiles.com
+http://www.hp.com
+http://www.google.ru
+http://www.51.com
+http://www.infoseek.co.jp
+http://www.linkedin.com
+http://www.gmx.net
+http://www.xinhuanet.com
+http://www.narod.ru
+http://www.multiply.com
+http://www.pomoho.com
+http://www.nytimes.com
+http://www.yourfilehost.com
+http://www.gamespot.com
+http://www.typepad.com
+http://www.ask.com
+http://www.89.com
+http://www.geocities.jp
+http://www.yam.com
+http://www.neopets.com
+http://www.mercadolivre.com.br
+http://www.centrum.cz
+http://www.mapquest.com
+http://www.univision.com
+http://www.zol.com.cn
+http://www.veoh.com
+http://www.amazon.co.jp
+http://www.6park.com
+http://www.24h.com.vn
+http://www.china.com
+http://www.minijuegos.com
+http://www.sh3bwah.com
+http://www.mop.com
+http://www.miarroba.com
+http://www.tianya.cn
+http://www.ign.com
+http://www.technorati.com
+http://www.yesky.com
+http://www.pornotube.com
+http://www.google.hr
+http://www.cmfu.com
+http://www.aebn.net
+http://www.mynet.com
+http://www.nifty.com
+http://www.livejasmin.com
+http://www.wwe.com
+http://www.daum.net
+http://www.nastydollars.com
+http://www.filefront.com
+http://www.bangbros1.com
+http://www.doubleclick.com
+http://www.torrentspy.com
+http://www.zaycev.net
+http://www.chinaren.com
+http://www.youku.com
+http://www.altavista.com
+http://www.spiegel.de
+http://www.icio.us
+http://www.hinet.net
+http://www.anonym.to
+http://www.usercash.com
+http://www.gamer.com.tw
+http://www.sapo.pt
+http://www.2ch.net
+http://www.tripod.com
+http://www.reference.com
+http://www.blogchina.com
+http://www.piczo.com
+http://www.startimes2.com
+http://www.zshare.net
+http://www.uusee.com
+http://www.dada.net
+http://www.amazon.co.uk
+http://www.panet.co.il
+http://www.6rb.com
+http://www.smileycentral.com
+http://www.juggcrew.com
+http://www.liveinternet.ru
+http://www.google.com.my
+http://www.mercadolibre.com.ar
+http://www.sakura.ne.jp
+http://www.gamefaqs.com
+http://www.jrj.com.cn
+http://www.fanfiction.net
+http://www.libero.it
+http://www.google.se
+http://www.milliyet.com.tr
+http://www.imeem.com
+http://www.archive.org
+http://www.mediafire.com
+http://www.google.com.sg
+http://www.blogfa.com
+http://www.filefactory.com
+http://www.softonic.com
+http://www.homeway.com.cn
+http://www.google.com.tw
+http://www.pconline.com.cn
+http://www.blog.cz
+http://www.symantec.com
+http://www.biglobe.ne.jp
+http://www.mlb.com
+http://www.webshots.com
+http://www.hatena.ne.jp
+http://www.phoenixtv.com
+http://www.google.co.cr
+http://www.nate.com
+http://www.google.gr
+http://www.mobile.de
+http://www.pornaccess.com
+http://www.ynet.co.il
+http://www.amazon.de
+http://www.imagefap.com
+http://www.whenu.com
+http://www.vietnamnet.vn
+http://www.ebay.it
+http://www.hurriyet.com.tr
+http://www.freewebs.com
+http://www.tuoitre.com.vn
+http://www.php.net
+http://www.broadcaster.com
+http://www.google.at
+http://www.xnxx.com
+http://www.studiverzeichnis.com
+http://www.elmundo.es
+http://www.it168.com
+http://www.lide.cz
+http://www.wikimedia.org
+http://www.wefong.com
+http://www.mercadolibre.com.mx
+http://www.thepiratebay.org
+http://www.istockphoto.com
+http://www.sanook.com
+http://www.petardas.com
+http://www.answers.com
+http://www.no-ip.com
+http://www.match.com
+http://www.ebay.com.au
+http://www.payserve.com
+http://www.soufun.com
+http://www.flurl.com
+http://www.alice.it
+http://www.globalsearch.cz
+http://www.888.com
+http://www.isohunt.com
+http://www.poco.cn
+http://www.ameblo.jp
+http://www.heise.de
+http://www.6rbtop.com
+http://www.google.co.id
+http://www.godaddy.com
+http://www.google.fi
+http://www.bala.com.cn
+http://www.break.com
+http://www.stumbleupon.com
+http://www.feedburner.com
+http://www.ngoisao.net
+http://www.atnext.com
+http://www.screensavers.com
+http://www.17173.com
+http://www.domaintools.com
+http://www.nih.gov
+http://www.novinky.cz
+http://www.freeones.com
+http://www.last.fm
+http://www.skype.com
+http://www.google.bg
+http://www.21cn.com
+http://www.o2.pl
+http://www.musica.com
+http://www.pplive.com
+http://www.hkjc.com
+http://www.grono.net
+http://www.bramjnet.com
+http://www.adbrite.com
+http://www.divx.com
+http://www.pcpop.com
+http://www.verycd.com
+http://www.cnfol.com
+http://www.google.ch
+http://www.myway.com
+http://www.wangyou.com
+http://www.gazeta.pl
+http://www.milta1980.co.uk
+http://www.prizee.com
+http://www.livescore.com
+http://www.facebox.com
+http://www.overture.com
+http://www.google.com.ua
+http://www.cartoonnetwork.com
+http://www.nicovideo.jp
+http://www.ku6.com
+http://www.it.com.cn
+http://www.one.lv
+http://www.t-online.de
+http://www.kooxoo.com
+http://www.super.cz
+http://www.google.co.ma
+http://www.xuite.net
+http://www.qihoo.com
+http://www.freelotto.com
+http://www.sedoparking.com
+http://www.monografias.com
+http://www.dmoz.org
+http://www.foxsports.com
+http://www.mocxi.com
+http://www.google.sk
+http://www.as7apcool.com
+http://www.igw.net.sa
+http://www.slide.com
+http://www.zhanzuo.com
+http://www.idnes.cz
+http://www.mobile9.com
+http://www.hawaaworld.com
+http://www.aljazeera.net
+http://www.nana.co.il
+http://www.seesaa.net
+http://www.51job.com
+http://www.abv.bg
+http://www.paipai.com
+http://www.real.com
+http://www.cctv.com
+http://www.tv.com
+http://www.terra.cl
+http://www.google.com.ph
+http://www.wannawatch.com
+http://www.thottbot.com
+http://www.4399.com
+http://www.5show.com
+http://www.vkontakte.ru
+http://www.cricinfo.com
+http://www.hyves.nl
+http://www.google.ie
+http://www.cnnic.cn
+http://www.tinypic.com
+http://www.forumer.com
+http://www.demonoid.com
+http://www.ouou.com
+http://www.mozilla.com
+http://www.naukri.com
+http://www.volam.com.vn
+http://www.torrentz.com
+http://www.google.co.za
+http://www.google.jo
+http://www.dvd4arab.com
+http://www.msn.ca
+http://www.clarin.com
+http://www.rincondelvago.com
+http://www.dmm.co.jp
+http://www.1ting.com
+http://www.everythinggirl.com
+http://www.freemail.hu
+http://www.xtube.com
+http://www.4399.net
+http://www.ocn.ne.jp
+http://www.aweber.com
+http://www.monster.com
+http://www.chinaz.com
+http://www.careerbuilder.com
+http://www.megaclick.com
+http://www.jeeran.com
+http://www.w3.org
+http://www.uume.com
+http://www.sitepoint.com
+http://www.people.com.cn
+http://www.google.com.uy
+http://www.mybloglog.com
+http://www.9you.com
+http://www.ebay.com.cn
+http://www.zedo.com
+http://www.juegosjuegos.com
+http://www.sweetim.com
+http://www.ikea.com
+http://www.walmart.com
+http://www.earthlink.net
+http://www.turboupload.com
+http://www.crunchyroll.com
+http://www.google.com.bh
+http://www.flogao.com.br
+http://www.usps.com
+http://www.6arab.com
+http://www.target.com
+http://www.wordreference.com
+http://www.perfspot.com
+http://www.gigasize.com
+http://www.mercadolibre.com.ve
+http://www.stockstar.com
+http://www.3721.com
+http://www.upspiral.com
+http://www.clubbox.co.kr
+http://www.trademe.co.nz
+http://www.nokia.com
+http://www.met-art.com
+http://www.ppstream.com
+http://www.ibm.com
+http://www.qianlong.com
+http://www.softpedia.com
+http://www.adultadworld.com
+http://www.webmasterworld.com
+http://www.ifolder.ru
+http://www.mofile.com
+http://www.delfi.lt
+http://www.sina.com.hk
+http://www.google.com.bo
+http://www.excite.co.jp
+http://www.myfreepaysite.com
+http://www.ev1servers.net
+http://www.dyndns.org
+http://www.wordpress.org
+http://www.drivecleaner.com
+http://www.google.co.nz
+http://www.people.com
+http://www.expedia.com
+http://www.sonyericsson.com
+http://www.msn.com.cn
+http://www.hubotv.com
+http://www.bangbros.com
+http://www.mediaplex.com
+http://www.google.com.qa
+http://www.videosz.com
+http://www.leo.org
+http://www.atlas.cz
+http://www.kinghost.com
+http://www.aljayyash.net
+http://www.irc-galleria.net
+http://www.xiaonei.com
+http://www.nnm.ru
+http://www.tv-links.co.uk
+http://www.indiatimes.com
+http://www.icbc.com.cn
+http://www.draugiem.lv
+http://www.6rooms.com
+http://www.hc360.com
+http://www.focus.cn
+http://www.atdmt.com
+http://www.chilewarez.org
+http://www.tripadvisor.com
+http://www.mcanime.net
+http://www.esnips.com
+http://www.chinamobile.com
+http://www.ebay.ca
+http://www.im.tv
diff --git a/chrome_frame/tools/test/reference_build/chrome/First Run b/chrome_frame/tools/test/reference_build/chrome/First Run
new file mode 100644
index 0000000..852ad16
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/First Run
@@ -0,0 +1 @@
+krome 

diff --git a/chrome_frame/tools/test/reference_build/chrome/avcodec-52.dll b/chrome_frame/tools/test/reference_build/chrome/avcodec-52.dll
new file mode 100644
index 0000000..9b55506
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/avcodec-52.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/avformat-52.dll b/chrome_frame/tools/test/reference_build/chrome/avformat-52.dll
new file mode 100644
index 0000000..33d3dcfd
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/avformat-52.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/avutil-50.dll b/chrome_frame/tools/test/reference_build/chrome/avutil-50.dll
new file mode 100644
index 0000000..63f4d2d
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/avutil-50.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/chrome.dll b/chrome_frame/tools/test/reference_build/chrome/chrome.dll
new file mode 100644
index 0000000..bc17fe4
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/chrome.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/chrome.exe b/chrome_frame/tools/test/reference_build/chrome/chrome.exe
new file mode 100644
index 0000000..040fbaa
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/chrome.exe
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/chrome_dll.pdb b/chrome_frame/tools/test/reference_build/chrome/chrome_dll.pdb
new file mode 100644
index 0000000..203f1576
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/chrome_dll.pdb
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/chrome_exe.pdb b/chrome_frame/tools/test/reference_build/chrome/chrome_exe.pdb
new file mode 100644
index 0000000..5d79f8b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/chrome_exe.pdb
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/crash_service.exe b/chrome_frame/tools/test/reference_build/chrome/crash_service.exe
new file mode 100644
index 0000000..1eac8eed
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/crash_service.exe
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/gears.dll b/chrome_frame/tools/test/reference_build/chrome/gears.dll
new file mode 100644
index 0000000..db6d8641
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/gears.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/icudt42.dll b/chrome_frame/tools/test/reference_build/chrome/icudt42.dll
new file mode 100644
index 0000000..40a49c19
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/icudt42.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ar.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ar.dll
new file mode 100644
index 0000000..d925ac35
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/ar.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/bg.dll b/chrome_frame/tools/test/reference_build/chrome/locales/bg.dll
new file mode 100644
index 0000000..90d4933
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/bg.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/bn.dll b/chrome_frame/tools/test/reference_build/chrome/locales/bn.dll
new file mode 100644
index 0000000..3364aaa
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/bn.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ca.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ca.dll
new file mode 100644
index 0000000..9d8a07ae
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/ca.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/cs.dll b/chrome_frame/tools/test/reference_build/chrome/locales/cs.dll
new file mode 100644
index 0000000..c4b5d1c
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/cs.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/da.dll b/chrome_frame/tools/test/reference_build/chrome/locales/da.dll
new file mode 100644
index 0000000..239cf008
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/da.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/de.dll b/chrome_frame/tools/test/reference_build/chrome/locales/de.dll
new file mode 100644
index 0000000..44da5d3
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/de.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/el.dll b/chrome_frame/tools/test/reference_build/chrome/locales/el.dll
new file mode 100644
index 0000000..4afbade
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/el.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/en-GB.dll b/chrome_frame/tools/test/reference_build/chrome/locales/en-GB.dll
new file mode 100644
index 0000000..a3cc174
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/en-GB.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/en-US.dll b/chrome_frame/tools/test/reference_build/chrome/locales/en-US.dll
new file mode 100644
index 0000000..6b4db3c2
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/en-US.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/es-419.dll b/chrome_frame/tools/test/reference_build/chrome/locales/es-419.dll
new file mode 100644
index 0000000..58ee8bc0
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/es-419.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/es.dll b/chrome_frame/tools/test/reference_build/chrome/locales/es.dll
new file mode 100644
index 0000000..96168ad
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/es.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/et.dll b/chrome_frame/tools/test/reference_build/chrome/locales/et.dll
new file mode 100644
index 0000000..237750df
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/et.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/fi.dll b/chrome_frame/tools/test/reference_build/chrome/locales/fi.dll
new file mode 100644
index 0000000..579a2a22
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/fi.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/fil.dll b/chrome_frame/tools/test/reference_build/chrome/locales/fil.dll
new file mode 100644
index 0000000..12cc22e
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/fil.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/fr.dll b/chrome_frame/tools/test/reference_build/chrome/locales/fr.dll
new file mode 100644
index 0000000..87003ac
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/fr.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/gu.dll b/chrome_frame/tools/test/reference_build/chrome/locales/gu.dll
new file mode 100644
index 0000000..0fdc291
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/gu.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/he.dll b/chrome_frame/tools/test/reference_build/chrome/locales/he.dll
new file mode 100644
index 0000000..332fa93
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/he.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/hi.dll b/chrome_frame/tools/test/reference_build/chrome/locales/hi.dll
new file mode 100644
index 0000000..1eee966
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/hi.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/hr.dll b/chrome_frame/tools/test/reference_build/chrome/locales/hr.dll
new file mode 100644
index 0000000..debe9d0
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/hr.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/hu.dll b/chrome_frame/tools/test/reference_build/chrome/locales/hu.dll
new file mode 100644
index 0000000..8f6a8c2
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/hu.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/id.dll b/chrome_frame/tools/test/reference_build/chrome/locales/id.dll
new file mode 100644
index 0000000..3c78bcc
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/id.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/it.dll b/chrome_frame/tools/test/reference_build/chrome/locales/it.dll
new file mode 100644
index 0000000..25c39f75
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/it.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ja.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ja.dll
new file mode 100644
index 0000000..1378e477
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/ja.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/kn.dll b/chrome_frame/tools/test/reference_build/chrome/locales/kn.dll
new file mode 100644
index 0000000..6154988
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/kn.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ko.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ko.dll
new file mode 100644
index 0000000..bbe62eda
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/ko.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/lt.dll b/chrome_frame/tools/test/reference_build/chrome/locales/lt.dll
new file mode 100644
index 0000000..fd9069e
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/lt.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/lv.dll b/chrome_frame/tools/test/reference_build/chrome/locales/lv.dll
new file mode 100644
index 0000000..a402aec
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/lv.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ml.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ml.dll
new file mode 100644
index 0000000..6902ead5
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/ml.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/mr.dll b/chrome_frame/tools/test/reference_build/chrome/locales/mr.dll
new file mode 100644
index 0000000..d63aafe
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/mr.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/nb.dll b/chrome_frame/tools/test/reference_build/chrome/locales/nb.dll
new file mode 100644
index 0000000..7043aa6
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/nb.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/nl.dll b/chrome_frame/tools/test/reference_build/chrome/locales/nl.dll
new file mode 100644
index 0000000..52fbf1d1
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/nl.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/or.dll b/chrome_frame/tools/test/reference_build/chrome/locales/or.dll
new file mode 100644
index 0000000..34428ad
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/or.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/pl.dll b/chrome_frame/tools/test/reference_build/chrome/locales/pl.dll
new file mode 100644
index 0000000..2836731
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/pl.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/pt-BR.dll b/chrome_frame/tools/test/reference_build/chrome/locales/pt-BR.dll
new file mode 100644
index 0000000..5aa52e8
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/pt-BR.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/pt-PT.dll b/chrome_frame/tools/test/reference_build/chrome/locales/pt-PT.dll
new file mode 100644
index 0000000..cc523c9
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/pt-PT.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ro.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ro.dll
new file mode 100644
index 0000000..0ee4d70c8
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/ro.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ru.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ru.dll
new file mode 100644
index 0000000..3fbfcfe
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/ru.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/sk.dll b/chrome_frame/tools/test/reference_build/chrome/locales/sk.dll
new file mode 100644
index 0000000..da498062
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/sk.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/sl.dll b/chrome_frame/tools/test/reference_build/chrome/locales/sl.dll
new file mode 100644
index 0000000..186d2f0a
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/sl.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/sr.dll b/chrome_frame/tools/test/reference_build/chrome/locales/sr.dll
new file mode 100644
index 0000000..9278069
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/sr.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/sv.dll b/chrome_frame/tools/test/reference_build/chrome/locales/sv.dll
new file mode 100644
index 0000000..29ff6bf1
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/sv.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ta.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ta.dll
new file mode 100644
index 0000000..711739a
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/ta.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/te.dll b/chrome_frame/tools/test/reference_build/chrome/locales/te.dll
new file mode 100644
index 0000000..5468bede
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/te.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/th.dll b/chrome_frame/tools/test/reference_build/chrome/locales/th.dll
new file mode 100644
index 0000000..bb41ca2e
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/th.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/tr.dll b/chrome_frame/tools/test/reference_build/chrome/locales/tr.dll
new file mode 100644
index 0000000..5a9333c
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/tr.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/uk.dll b/chrome_frame/tools/test/reference_build/chrome/locales/uk.dll
new file mode 100644
index 0000000..830904a
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/uk.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/vi.dll b/chrome_frame/tools/test/reference_build/chrome/locales/vi.dll
new file mode 100644
index 0000000..3c3c425ad
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/vi.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/zh-CN.dll b/chrome_frame/tools/test/reference_build/chrome/locales/zh-CN.dll
new file mode 100644
index 0000000..0a09475d
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/zh-CN.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/zh-TW.dll b/chrome_frame/tools/test/reference_build/chrome/locales/zh-TW.dll
new file mode 100644
index 0000000..237c46c
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/locales/zh-TW.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/mini_installer.pdb b/chrome_frame/tools/test/reference_build/chrome/mini_installer.pdb
new file mode 100644
index 0000000..111f61da
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/mini_installer.pdb
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BottomUpProfileDataGridTree.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BottomUpProfileDataGridTree.js
new file mode 100644
index 0000000..89b4ddc
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BottomUpProfileDataGridTree.js
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2009 280 North Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+// Bottom Up Profiling shows the entire callstack backwards:
+// The root node is a representation of each individual function called, and each child of that node represents
+// a reverse-callstack showing how many of those calls came from it. So, unlike top-down, the statistics in
+// each child still represent the root node. We have to be particularly careful of recursion with this mode
+// because a root node can represent itself AND an ancestor.
+
+WebInspector.BottomUpProfileDataGridTree = function(/*ProfileView*/ aProfileView, /*ProfileNode*/ aProfileNode)
+{
+    WebInspector.ProfileDataGridTree.call(this, aProfileView, aProfileNode);
+
+    // Iterate each node in pre-order.
+    var profileNodeUIDs = 0;
+    var profileNodeGroups = [[], [aProfileNode]];
+    var visitedProfileNodesForCallUID = {};
+
+    this._remainingNodeInfos = [];
+
+    for (var profileNodeGroupIndex = 0; profileNodeGroupIndex < profileNodeGroups.length; ++profileNodeGroupIndex) {
+        var parentProfileNodes = profileNodeGroups[profileNodeGroupIndex];
+        var profileNodes = profileNodeGroups[++profileNodeGroupIndex];
+        var count = profileNodes.length;
+
+        for (var index = 0; index < count; ++index) {
+            var profileNode = profileNodes[index];
+
+            if (!profileNode.UID)
+                profileNode.UID = ++profileNodeUIDs;
+
+            if (profileNode.head && profileNode !== profileNode.head) {
+                // The total time of this ancestor is accounted for if we're in any form of recursive cycle.
+                var visitedNodes = visitedProfileNodesForCallUID[profileNode.callUID];
+                var totalTimeAccountedFor = false;
+
+                if (!visitedNodes) {
+                    visitedNodes = {}
+                    visitedProfileNodesForCallUID[profileNode.callUID] = visitedNodes;
+                } else {
+                    // The total time for this node has already been accounted for iff one of it's parents has already been visited.
+                    // We can do this check in this style because we are traversing the tree in pre-order.
+                    var parentCount = parentProfileNodes.length;
+                    for (var parentIndex = 0; parentIndex < parentCount; ++parentIndex) {
+                        if (visitedNodes[parentProfileNodes[parentIndex].UID]) {
+                            totalTimeAccountedFor = true;
+                            break;
+                        }
+                    }
+                }
+
+                visitedNodes[profileNode.UID] = true;
+
+                this._remainingNodeInfos.push({ ancestor:profileNode, focusNode:profileNode, totalTimeAccountedFor:totalTimeAccountedFor });
+            }
+
+            var children = profileNode.children;
+            if (children.length) {
+                profileNodeGroups.push(parentProfileNodes.concat([profileNode]))
+                profileNodeGroups.push(children);
+            }
+        }
+    }
+
+    // Populate the top level nodes.
+    WebInspector.BottomUpProfileDataGridNode.prototype._populate.call(this);
+
+    return this;
+}
+
+WebInspector.BottomUpProfileDataGridTree.prototype = {
+    // When focusing, we keep the entire callstack up to this ancestor.
+    focus: function(/*ProfileDataGridNode*/ profileDataGridNode)
+    {
+        if (!profileDataGridNode)
+            return;
+
+        this._save();
+
+        var currentNode = profileDataGridNode;
+        var focusNode = profileDataGridNode;
+
+        while (currentNode.parent && (currentNode instanceof WebInspector.ProfileDataGridNode)) {
+            currentNode._takePropertiesFromProfileDataGridNode(profileDataGridNode);
+
+            focusNode = currentNode;
+            currentNode = currentNode.parent;
+
+            if (currentNode instanceof WebInspector.ProfileDataGridNode)
+                currentNode._keepOnlyChild(focusNode);
+        }
+
+        this.children = [focusNode];
+        this.totalTime = profileDataGridNode.totalTime;
+    },
+
+    exclude: function(/*ProfileDataGridNode*/ profileDataGridNode)
+    {
+        if (!profileDataGridNode)
+            return;
+
+        this._save();
+
+        var excludedCallUID = profileDataGridNode.callUID;
+        var excludedTopLevelChild = this.childrenByCallUID[excludedCallUID];
+
+        // If we have a top level node that is excluded, get rid of it completely (not keeping children),
+        // since bottom up data relies entirely on the root node.
+        if (excludedTopLevelChild)
+            this.children.remove(excludedTopLevelChild);
+
+        var children = this.children;
+        var count = children.length;
+
+        for (var index = 0; index < count; ++index)
+            children[index]._exclude(excludedCallUID);
+
+        if (this.lastComparator)
+            this.sort(this.lastComparator, true);
+    }
+}
+
+WebInspector.BottomUpProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype;
+
+WebInspector.BottomUpProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*BottomUpProfileDataGridTree*/ owningTree)
+{
+    // In bottom up mode, our parents are our children since we display an inverted tree.
+    // However, we don't want to show the very top parent since it is redundant.
+    var hasChildren = !!(profileNode.parent && profileNode.parent.parent);
+
+    WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, hasChildren);
+
+    this._remainingNodeInfos = [];
+}
+
+WebInspector.BottomUpProfileDataGridNode.prototype = {
+    _takePropertiesFromProfileDataGridNode: function(/*ProfileDataGridNode*/ profileDataGridNode)
+    {
+        this._save();
+
+        this.selfTime = profileDataGridNode.selfTime;
+        this.totalTime = profileDataGridNode.totalTime;
+        this.numberOfCalls = profileDataGridNode.numberOfCalls;
+    },
+
+    // When focusing, we keep just the members of the callstack.
+    _keepOnlyChild: function(/*ProfileDataGridNode*/ child)
+    {
+        this._save();
+
+        this.removeChildren();
+        this.appendChild(child);
+    },
+
+    _exclude: function(aCallUID)
+    {
+        if (this._remainingNodeInfos)
+            this._populate();
+
+        this._save();
+
+        var children = this.children;
+        var index = this.children.length;
+
+        while (index--)
+            children[index]._exclude(aCallUID);
+
+        var child = this.childrenByCallUID[aCallUID];
+
+        if (child)
+            this._merge(child, true);
+    },
+
+    _merge: function(/*ProfileDataGridNode*/ child, /*Boolean*/ shouldAbsorb)
+    {
+        this.selfTime -= child.selfTime;
+
+        WebInspector.ProfileDataGridNode.prototype._merge.call(this, child, shouldAbsorb);
+    },
+
+    _populate: function(event)
+    {
+        var remainingNodeInfos = this._remainingNodeInfos;
+        var count = remainingNodeInfos.length;
+
+        for (var index = 0; index < count; ++index) {
+            var nodeInfo = remainingNodeInfos[index];
+            var ancestor = nodeInfo.ancestor;
+            var focusNode = nodeInfo.focusNode;
+            var child = this.findChild(ancestor);
+
+            // If we already have this child, then merge the data together.
+            if (child) {
+                var totalTimeAccountedFor = nodeInfo.totalTimeAccountedFor;
+
+                child.selfTime += focusNode.selfTime;
+                child.numberOfCalls += focusNode.numberOfCalls;
+
+                if (!totalTimeAccountedFor)
+                    child.totalTime += focusNode.totalTime;
+            } else {
+                // If not, add it as a true ancestor.
+                // In heavy mode, we take our visual identity from ancestor node...
+                var child = new WebInspector.BottomUpProfileDataGridNode(this.profileView, ancestor, this.tree);
+
+                if (ancestor !== focusNode) {
+                    // but the actual statistics from the "root" node (bottom of the callstack).
+                    child.selfTime = focusNode.selfTime;
+                    child.totalTime = focusNode.totalTime;
+                    child.numberOfCalls = focusNode.numberOfCalls;
+                }
+
+                this.appendChild(child);
+            }
+
+            var parent = ancestor.parent;
+            if (parent && parent.parent) {
+                nodeInfo.ancestor = parent;
+                child._remainingNodeInfos.push(nodeInfo);
+            }
+        }
+
+        delete this._remainingNodeInfos;
+
+        if (this.removeEventListener)
+            this.removeEventListener("populate", this._populate, this);
+    }
+}
+
+WebInspector.BottomUpProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Breakpoint.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Breakpoint.js
new file mode 100644
index 0000000..292975a
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Breakpoint.js
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.Breakpoint = function(url, line, sourceID, condition)
+{
+    this.url = url;
+    this.line = line;
+    this.sourceID = sourceID;
+    this._enabled = true;
+    this._sourceText = "";
+    this._condition = condition || "";
+}
+
+WebInspector.Breakpoint.prototype = {
+    get enabled()
+    {
+        return this._enabled;
+    },
+
+    set enabled(x)
+    {
+        if (this._enabled === x)
+            return;
+
+        this._enabled = x;
+
+        if (this._enabled)
+            this.dispatchEventToListeners("enabled");
+        else
+            this.dispatchEventToListeners("disabled");
+    },
+
+    get sourceText()
+    {
+        return this._sourceText;
+    },
+
+    set sourceText(text)
+    {
+        this._sourceText = text;
+        this.dispatchEventToListeners("text-changed");
+    },
+
+    get label()
+    {
+        var displayName = (this.url ? WebInspector.displayNameForURL(this.url) : WebInspector.UIString("(program)"));
+        return displayName + ":" + this.line;
+    },
+
+    get id()
+    {
+        return this.sourceID + ":" + this.line;
+    },
+
+    get condition()
+    {
+        return this._condition;
+    },
+
+    set condition(c)
+    {
+        c = c || "";
+        if (this._condition === c)
+            return;
+
+        this._condition = c;
+        this.dispatchEventToListeners("condition-changed");
+
+        if (this.enabled)
+            InspectorController.updateBreakpoint(this.sourceID, this.line, c);
+    }
+}
+
+WebInspector.Breakpoint.prototype.__proto__ = WebInspector.Object.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BreakpointsSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BreakpointsSidebarPane.js
new file mode 100644
index 0000000..e6edece
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BreakpointsSidebarPane.js
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.BreakpointsSidebarPane = function()
+{
+    WebInspector.SidebarPane.call(this, WebInspector.UIString("Breakpoints"));
+
+    this.breakpoints = {};
+
+    this.listElement = document.createElement("ol");
+    this.listElement.className = "breakpoint-list";
+
+    this.emptyElement = document.createElement("div");
+    this.emptyElement.className = "info";
+    this.emptyElement.textContent = WebInspector.UIString("No Breakpoints");
+
+    this.bodyElement.appendChild(this.emptyElement);
+}
+
+WebInspector.BreakpointsSidebarPane.prototype = {
+    addBreakpoint: function(breakpoint)
+    {
+        if (this.breakpoints[breakpoint.id])
+            return;
+
+        this.breakpoints[breakpoint.id] = breakpoint;
+
+        breakpoint.addEventListener("enabled", this._breakpointEnableChanged, this);
+        breakpoint.addEventListener("disabled", this._breakpointEnableChanged, this);
+        breakpoint.addEventListener("text-changed", this._breakpointTextChanged, this);
+
+        this._appendBreakpointElement(breakpoint);
+
+        if (this.emptyElement.parentElement) {
+            this.bodyElement.removeChild(this.emptyElement);
+            this.bodyElement.appendChild(this.listElement);
+        }
+
+        if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID)
+            return;
+
+        if (breakpoint.enabled)
+            InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition);
+    },
+
+    _appendBreakpointElement: function(breakpoint)
+    {
+        function checkboxClicked()
+        {
+            breakpoint.enabled = !breakpoint.enabled;
+        }
+
+        function labelClicked()
+        {
+            var script = WebInspector.panels.scripts.scriptOrResourceForID(breakpoint.sourceID);
+            if (script)
+                WebInspector.panels.scripts.showScript(script, breakpoint.line);
+        }
+
+        var breakpointElement = document.createElement("li");
+        breakpoint._breakpointListElement = breakpointElement;
+        breakpointElement._breakpointObject = breakpoint;
+
+        var checkboxElement = document.createElement("input");
+        checkboxElement.className = "checkbox-elem";
+        checkboxElement.type = "checkbox";
+        checkboxElement.checked = breakpoint.enabled;
+        checkboxElement.addEventListener("click", checkboxClicked, false);
+        breakpointElement.appendChild(checkboxElement);
+
+        var labelElement = document.createElement("a");
+        labelElement.textContent = breakpoint.label;
+        labelElement.addEventListener("click", labelClicked, false);
+        breakpointElement.appendChild(labelElement);
+
+        var sourceTextElement = document.createElement("div");
+        sourceTextElement.textContent = breakpoint.sourceText;
+        sourceTextElement.className = "source-text";
+        breakpointElement.appendChild(sourceTextElement);
+
+        var currentElement = this.listElement.firstChild;
+        while (currentElement) {
+            var currentBreak = currentElement._breakpointObject;
+            if (currentBreak.url > breakpoint.url) {
+                this.listElement.insertBefore(breakpointElement, currentElement);
+                return;
+            } else if (currentBreak.url == breakpoint.url && currentBreak.line > breakpoint.line) {
+                this.listElement.insertBefore(breakpointElement, currentElement);
+                return;
+            }
+            currentElement = currentElement.nextSibling;
+        }
+        this.listElement.appendChild(breakpointElement);
+    },
+
+    removeBreakpoint: function(breakpoint)
+    {
+        if (!this.breakpoints[breakpoint.id])
+            return;
+        delete this.breakpoints[breakpoint.id];
+
+        breakpoint.removeEventListener("enabled", null, this);
+        breakpoint.removeEventListener("disabled", null, this);
+        breakpoint.removeEventListener("text-changed", null, this);
+
+        var element = breakpoint._breakpointListElement;
+        element.parentElement.removeChild(element);
+
+        if (!this.listElement.firstChild) {
+            this.bodyElement.removeChild(this.listElement);
+            this.bodyElement.appendChild(this.emptyElement);
+        }
+
+        if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID)
+            return;
+
+        InspectorController.removeBreakpoint(breakpoint.sourceID, breakpoint.line);
+    },
+
+    _breakpointEnableChanged: function(event)
+    {
+        var breakpoint = event.target;
+
+        var checkbox = breakpoint._breakpointListElement.firstChild;
+        checkbox.checked = breakpoint.enabled;
+
+        if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID)
+            return;
+
+        if (breakpoint.enabled)
+            InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition);
+        else
+            InspectorController.removeBreakpoint(breakpoint.sourceID, breakpoint.line);
+    },
+
+    _breakpointTextChanged: function(event)
+    {
+        var breakpoint = event.target;
+
+        var sourceTextElement = breakpoint._breakpointListElement.firstChild.nextSibling.nextSibling;
+        sourceTextElement.textContent = breakpoint.sourceText;
+    }
+}
+
+WebInspector.BreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CallStackSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CallStackSidebarPane.js
new file mode 100644
index 0000000..2fe4315c
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CallStackSidebarPane.js
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.CallStackSidebarPane = function()
+{
+    WebInspector.SidebarPane.call(this, WebInspector.UIString("Call Stack"));
+    
+    this._shortcuts = {};
+
+    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Period,
+                                                         WebInspector.KeyboardShortcut.Modifiers.Ctrl);
+    this._shortcuts[shortcut] = this._selectNextCallFrameOnStack.bind(this);
+
+    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Comma,
+                                                         WebInspector.KeyboardShortcut.Modifiers.Ctrl);
+    this._shortcuts[shortcut] = this._selectPreviousCallFrameOnStack.bind(this);
+}
+
+WebInspector.CallStackSidebarPane.prototype = {
+    update: function(callFrames, sourceIDMap)
+    {
+        this.bodyElement.removeChildren();
+
+        this.placards = [];
+        delete this._selectedCallFrame;
+
+        if (!callFrames) {
+            var infoElement = document.createElement("div");
+            infoElement.className = "info";
+            infoElement.textContent = WebInspector.UIString("Not Paused");
+            this.bodyElement.appendChild(infoElement);
+            return;
+        }
+
+        var title;
+        var subtitle;
+        var scriptOrResource;
+
+        for (var i = 0; i < callFrames.length; ++i) {
+            var callFrame = callFrames[i];
+            switch (callFrame.type) {
+            case "function":
+                title = callFrame.functionName || WebInspector.UIString("(anonymous function)");
+                break;
+            case "program":
+                title = WebInspector.UIString("(program)");
+                break;
+            }
+
+            scriptOrResource = sourceIDMap[callFrame.sourceID];
+            subtitle = WebInspector.displayNameForURL(scriptOrResource.sourceURL || scriptOrResource.url);
+
+            if (callFrame.line > 0) {
+                if (subtitle)
+                    subtitle += ":" + callFrame.line;
+                else
+                    subtitle = WebInspector.UIString("line %d", callFrame.line);
+            }
+
+            var placard = new WebInspector.Placard(title, subtitle);
+            placard.callFrame = callFrame;
+
+            placard.element.addEventListener("click", this._placardSelected.bind(this), false);
+
+            this.placards.push(placard);
+            this.bodyElement.appendChild(placard.element);
+        }
+    },
+
+    get selectedCallFrame()
+    {
+        return this._selectedCallFrame;
+    },
+
+    set selectedCallFrame(x)
+    {
+        if (this._selectedCallFrame === x)
+            return;
+
+        this._selectedCallFrame = x;
+
+        for (var i = 0; i < this.placards.length; ++i) {
+            var placard = this.placards[i];
+            placard.selected = (placard.callFrame === this._selectedCallFrame);
+        }
+
+        this.dispatchEventToListeners("call frame selected");
+    },
+
+    handleKeyEvent: function(event)
+    {
+        var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
+        var handler = this._shortcuts[shortcut];
+        if (handler) {
+            handler(event);
+            event.preventDefault();
+            event.handled = true;
+        }
+    },
+
+    _selectNextCallFrameOnStack: function()
+    {
+        var index = this._selectedCallFrameIndex();
+        if (index == -1)
+            return;
+        this._selectedPlacardByIndex(index + 1);
+    },
+
+    _selectPreviousCallFrameOnStack: function()
+    {
+        var index = this._selectedCallFrameIndex();
+        if (index == -1)
+            return;
+        this._selectedPlacardByIndex(index - 1);
+    },
+
+    _selectedPlacardByIndex: function(index)
+    {
+        if (index < 0 || index >= this.placards.length)
+            return;
+        var placard = this.placards[index];
+        this.selectedCallFrame = placard.callFrame
+    },
+
+    _selectedCallFrameIndex: function()
+    {
+        if (!this._selectedCallFrame)
+            return -1;
+        for (var i = 0; i < this.placards.length; ++i) {
+            var placard = this.placards[i];
+            if (placard.callFrame === this._selectedCallFrame)
+                return i;
+        }
+        return -1;
+    },
+
+    _placardSelected: function(event)
+    {
+        var placardElement = event.target.enclosingNodeOrSelfWithClass("placard");
+        this.selectedCallFrame = placardElement.placard.callFrame;
+    }
+}
+
+WebInspector.CallStackSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Callback.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Callback.js
new file mode 100644
index 0000000..8ae7f95
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Callback.js
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2009 Google Inc. 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.
+ */
+
+WebInspector.Callback = function()
+{
+    this._lastCallbackId = 1;
+    this._callbacks = {};
+}
+
+WebInspector.Callback.prototype = {
+    wrap: function(callback)
+    {
+        var callbackId = this._lastCallbackId++;
+        this._callbacks[callbackId] = callback || function() {};
+        return callbackId;
+    },
+
+    processCallback: function(callbackId, opt_vararg)
+    {
+        var args = Array.prototype.slice.call(arguments, 1);
+        var callback = this._callbacks[callbackId];
+        callback.apply(null, args);
+        delete this._callbacks[callbackId];
+    }
+}
+
+WebInspector.Callback._INSTANCE = new WebInspector.Callback();
+WebInspector.Callback.wrap = WebInspector.Callback._INSTANCE.wrap.bind(WebInspector.Callback._INSTANCE);
+WebInspector.Callback.processCallback = WebInspector.Callback._INSTANCE.processCallback.bind(WebInspector.Callback._INSTANCE);
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ChangesView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ChangesView.js
new file mode 100644
index 0000000..802fdba
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ChangesView.js
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.ChangesView = function(drawer)
+{
+    WebInspector.View.call(this);
+    this.element.innerHTML = "<div style=\"bottom:25%;color:rgb(192,192,192);font-size:12px;height:65px;left:0px;margin:auto;position:absolute;right:0px;text-align:center;top:0px;\"><h1>Not Implemented Yet</h1></div>";
+
+    this.drawer = drawer;
+
+    this.clearButton = document.createElement("button");
+    this.clearButton.id = "clear-changes-status-bar-item";
+    this.clearButton.title = WebInspector.UIString("Clear changes log.");
+    this.clearButton.className = "status-bar-item";
+    this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false);
+
+    this.toggleChangesButton = document.getElementById("changes-status-bar-item");
+    this.toggleChangesButton.title = WebInspector.UIString("Show changes view.");
+    this.toggleChangesButton.addEventListener("click", this._toggleChangesButtonClicked.bind(this), false);
+    var anchoredStatusBar = document.getElementById("anchored-status-bar-items");
+    anchoredStatusBar.appendChild(this.toggleChangesButton);
+}
+
+WebInspector.ChangesView.prototype = {
+    _clearButtonClicked: function()
+    {
+        // Not Implemented Yet
+    },
+
+    _toggleChangesButtonClicked: function()
+    {
+        this.drawer.visibleView = this;
+    },
+
+    attach: function(mainElement, statusBarElement)
+    {
+        mainElement.appendChild(this.element);
+        statusBarElement.appendChild(this.clearButton);
+    },
+
+    show: function()
+    {
+        this.toggleChangesButton.addStyleClass("toggled-on");
+        this.toggleChangesButton.title = WebInspector.UIString("Hide changes view.");
+    },
+
+    hide: function()
+    {
+        this.toggleChangesButton.removeStyleClass("toggled-on");
+        this.toggleChangesButton.title = WebInspector.UIString("Show changes view.");
+    }
+}
+
+WebInspector.ChangesView.prototype.__proto__ = WebInspector.View.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Color.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Color.js
new file mode 100644
index 0000000..9d9cd76
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Color.js
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 2009 Apple Inc.  All rights reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.Color = function(str)
+{
+    this.value = str;
+    this._parse();
+}
+
+WebInspector.Color.prototype = {
+    get shorthex()
+    {
+        if ("_short" in this)
+            return this._short;
+
+        if (!this.simple)
+            return null;
+
+        var hex = this.hex;
+        if (hex.charAt(0) === hex.charAt(1) && hex.charAt(2) === hex.charAt(3) && hex.charAt(4) === hex.charAt(5))
+            this._short = hex.charAt(0) + hex.charAt(2) + hex.charAt(4);
+        else
+            this._short = hex;
+
+        return this._short;
+    },
+
+    get hex()
+    {
+        if (!this.simple)
+            return null;
+
+        return this._hex;
+    },
+
+    set hex(x)
+    {
+        this._hex = x;
+    },
+
+    get rgb()
+    {
+        if ("_rgb" in this)
+            return this._rgb;
+
+        if (this.simple)
+            this._rgb = this._hexToRGB(this.hex);
+        else {
+            var rgba = this.rgba;
+            this._rgb = [rgba[0], rgba[1], rgba[2]];
+        }
+
+        return this._rgb;
+    },
+
+    set rgb(x)
+    {
+        this._rgb = x;
+    },
+
+    get hsl()
+    {
+        if ("_hsl" in this)
+            return this._hsl;
+
+        this._hsl = this._rgbToHSL(this.rgb);
+        return this._hsl;
+    },
+
+    set hsl(x)
+    {
+        this._hsl = x;
+    },
+
+    get nickname()
+    {
+        if (typeof this._nickname !== "undefined") // would be set on parse if there was a nickname
+            return this._nickname;
+        else
+            return null;
+    },
+
+    set nickname(x)
+    {
+        this._nickname = x;
+    },
+
+    get rgba()
+    {
+        return this._rgba;
+    },
+
+    set rgba(x)
+    {
+        this._rgba = x;
+    },
+
+    get hsla()
+    {
+        return this._hsla;
+    },
+
+    set hsla(x)
+    {
+        this._hsla = x;
+    },
+
+    hasShortHex: function()
+    {
+        var shorthex = this.shorthex;
+        return (shorthex && shorthex.length === 3);
+    },
+
+    toString: function(format)
+    {
+        if (!format)
+            format = this.format;
+
+        switch (format) {
+            case "rgb":
+                return "rgb(" + this.rgb.join(", ") + ")";
+            case "rgba":
+                return "rgba(" + this.rgba.join(", ") + ")";
+            case "hsl":
+                var hsl = this.hsl;
+                return "hsl(" + hsl[0] + ", " + hsl[1] + "%, " + hsl[2] + "%)";
+            case "hsla":
+                var hsla = this.hsla;
+                return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + hsla[3] + ")";
+            case "hex":
+                return "#" + this.hex;
+            case "shorthex":
+                return "#" + this.shorthex;
+            case "nickname":
+                return this.nickname;
+        }
+
+        throw "invalid color format";
+    },
+
+    _rgbToHex: function(rgb)
+    {
+        var r = parseInt(rgb[0]).toString(16);
+        var g = parseInt(rgb[1]).toString(16);
+        var b = parseInt(rgb[2]).toString(16);
+        if (r.length === 1)
+            r = "0" + r;
+        if (g.length === 1)
+            g = "0" + g;
+        if (b.length === 1)
+            b = "0" + b;
+
+        return (r + g + b).toUpperCase();
+    },
+
+    _hexToRGB: function(hex)
+    {
+        var r = parseInt(hex.substring(0,2), 16);
+        var g = parseInt(hex.substring(2,4), 16);
+        var b = parseInt(hex.substring(4,6), 16);
+
+        return [r, g, b];
+    },
+
+    _rgbToHSL: function(rgb)
+    {
+        var r = parseInt(rgb[0]) / 255;
+        var g = parseInt(rgb[1]) / 255;
+        var b = parseInt(rgb[2]) / 255;
+        var max = Math.max(r, g, b);
+        var min = Math.min(r, g, b);
+        var diff = max - min;
+        var add = max + min;
+
+        if (min === max)
+            var h = 0;
+        else if (r === max)
+            var h = ((60 * (g - b) / diff) + 360) % 360;
+        else if (g === max)
+            var h = (60 * (b - r) / diff) + 120;
+        else
+            var h = (60 * (r - g) / diff) + 240;
+
+        var l = 0.5 * add;
+
+        if (l === 0)
+            var s = 0;
+        else if (l === 1)
+            var s = 1;
+        else if (l <= 0.5)
+            var s = diff / add;
+        else
+            var s = diff / (2 - add);
+
+        h = Math.round(h);
+        s = Math.round(s*100);
+        l = Math.round(l*100);
+
+        return [h, s, l];
+    },
+
+    _hslToRGB: function(hsl)
+    {
+        var h = parseFloat(hsl[0]) / 360;
+        var s = parseFloat(hsl[1]) / 100;
+        var l = parseFloat(hsl[2]) / 100;
+
+        if (l <= 0.5)
+            var q = l * (1 + s);
+        else
+            var q = l + s - (l * s);
+
+        var p = 2 * l - q;
+
+        var tr = h + (1 / 3);
+        var tg = h;
+        var tb = h - (1 / 3);
+
+        var r = Math.round(hueToRGB(p, q, tr) * 255);
+        var g = Math.round(hueToRGB(p, q, tg) * 255);
+        var b = Math.round(hueToRGB(p, q, tb) * 255);
+        return [r, g, b];
+
+        function hueToRGB(p, q, h) {
+            if (h < 0)
+                h += 1;
+            else if (h > 1)
+                h -= 1;
+
+            if ((h * 6) < 1)
+                return p + (q - p) * h * 6;
+            else if ((h * 2) < 1)
+                return q;
+            else if ((h * 3) < 2)
+                return p + (q - p) * ((2 / 3) - h) * 6;
+            else
+                return p;
+        }
+    },
+
+    _rgbaToHSLA: function(rgba)
+    {
+        var alpha = rgba[3];
+        var hsl = this._rgbToHSL(rgba)
+        hsl.push(alpha);
+        return hsl;
+    },
+
+    _hslaToRGBA: function(hsla)
+    {
+        var alpha = hsla[3];
+        var rgb = this._hslToRGB(hsla);
+        rgb.push(alpha);
+        return rgb;
+    },
+
+    _parse: function()
+    {
+        // Special Values - Advanced but Must Be Parsed First - transparent
+        var value = this.value.toLowerCase().replace(/%|\s+/g, "");
+        if (value in WebInspector.Color.AdvancedNickNames) {
+            this.format = "nickname";
+            var set = WebInspector.Color.AdvancedNickNames[value];
+            this.simple = false;
+            this.rgba = set[0];
+            this.hsla = set[1];
+            this.nickname = set[2];
+            this.alpha = set[0][3];
+            return;
+        }
+
+        // Simple - #hex, rgb(), nickname, hsl()
+        var simple = /^(?:#([0-9a-f]{3,6})|rgb\(([^)]+)\)|(\w+)|hsl\(([^)]+)\))$/i;
+        var match = this.value.match(simple);
+        if (match) {
+            this.simple = true;
+
+            if (match[1]) { // hex
+                var hex = match[1].toUpperCase();
+                if (hex.length === 3) {
+                    this.format = "shorthex";
+                    this.hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
+                } else {
+                    this.format = "hex";
+                    this.hex = hex;
+                }
+            } else if (match[2]) { // rgb
+                this.format = "rgb";
+                var rgb = match[2].split(/\s*,\s*/);
+                this.rgb = rgb;
+                this.hex = this._rgbToHex(rgb);
+            } else if (match[3]) { // nickname
+                var nickname = match[3].toLowerCase();
+                if (nickname in WebInspector.Color.Nicknames) {
+                    this.format = "nickname";
+                    this.hex = WebInspector.Color.Nicknames[nickname];
+                } else // unknown name
+                    throw "unknown color name";
+            } else if (match[4]) { // hsl
+                this.format = "hsl";
+                var hsl = match[4].replace(/%g/, "").split(/\s*,\s*/);
+                this.hsl = hsl;
+                this.rgb = this._hslToRGB(hsl);
+                this.hex = this._rgbToHex(this.rgb);
+            }
+
+            // Fill in the values if this is a known hex color
+            var hex = this.hex;
+            if (hex && hex in WebInspector.Color.HexTable) {
+                var set = WebInspector.Color.HexTable[hex];
+                this.rgb = set[0];
+                this.hsl = set[1];
+                this.nickname = set[2];
+            }
+
+            return;
+        }
+
+        // Advanced - rgba(), hsla()
+        var advanced = /^(?:rgba\(([^)]+)\)|hsla\(([^)]+)\))$/;
+        match = this.value.match(advanced);
+        if (match) {
+            this.simple = false;
+            if (match[1]) { // rgba
+                this.format = "rgba";
+                this.rgba = match[1].split(/\s*,\s*/);
+                this.hsla = this._rgbaToHSLA(this.rgba);
+                this.alpha = this.rgba[3];
+            } else if (match[2]) { // hsla
+                this.format = "hsla";
+                this.hsla = match[2].replace(/%/g, "").split(/\s*,\s*/);
+                this.rgba = this._hslaToRGBA(this.hsla);
+                this.alpha = this.hsla[3];
+            }
+
+            return;
+        }
+
+        // Could not parse as a valid color
+        throw "could not parse color";
+    }
+}
+
+// Simple Values: [rgb, hsl, nickname]
+WebInspector.Color.HexTable = {
+    "000000": [[0, 0, 0], [0, 0, 0], "black"],
+    "000080": [[0, 0, 128], [240, 100, 25], "navy"],
+    "00008B": [[0, 0, 139], [240, 100, 27], "darkBlue"],
+    "0000CD": [[0, 0, 205], [240, 100, 40], "mediumBlue"],
+    "0000FF": [[0, 0, 255], [240, 100, 50], "blue"],
+    "006400": [[0, 100, 0], [120, 100, 20], "darkGreen"],
+    "008000": [[0, 128, 0], [120, 100, 25], "green"],
+    "008080": [[0, 128, 128], [180, 100, 25], "teal"],
+    "008B8B": [[0, 139, 139], [180, 100, 27], "darkCyan"],
+    "00BFFF": [[0, 191, 255], [195, 100, 50], "deepSkyBlue"],
+    "00CED1": [[0, 206, 209], [181, 100, 41], "darkTurquoise"],
+    "00FA9A": [[0, 250, 154], [157, 100, 49], "mediumSpringGreen"],
+    "00FF00": [[0, 255, 0], [120, 100, 50], "lime"],
+    "00FF7F": [[0, 255, 127], [150, 100, 50], "springGreen"],
+    "00FFFF": [[0, 255, 255], [180, 100, 50], "cyan"],
+    "191970": [[25, 25, 112], [240, 64, 27], "midnightBlue"],
+    "1E90FF": [[30, 144, 255], [210, 100, 56], "dodgerBlue"],
+    "20B2AA": [[32, 178, 170], [177, 70, 41], "lightSeaGreen"],
+    "228B22": [[34, 139, 34], [120, 61, 34], "forestGreen"],
+    "2E8B57": [[46, 139, 87], [146, 50, 36], "seaGreen"],
+    "2F4F4F": [[47, 79, 79], [180, 25, 25], "darkSlateGray"],
+    "32CD32": [[50, 205, 50], [120, 61, 50], "limeGreen"],
+    "3CB371": [[60, 179, 113], [147, 50, 47], "mediumSeaGreen"],
+    "40E0D0": [[64, 224, 208], [174, 72, 56], "turquoise"],
+    "4169E1": [[65, 105, 225], [225, 73, 57], "royalBlue"],
+    "4682B4": [[70, 130, 180], [207, 44, 49], "steelBlue"],
+    "483D8B": [[72, 61, 139], [248, 39, 39], "darkSlateBlue"],
+    "48D1CC": [[72, 209, 204], [178, 60, 55], "mediumTurquoise"],
+    "4B0082": [[75, 0, 130], [275, 100, 25], "indigo"],
+    "556B2F": [[85, 107, 47], [82, 39, 30], "darkOliveGreen"],
+    "5F9EA0": [[95, 158, 160], [182, 25, 50], "cadetBlue"],
+    "6495ED": [[100, 149, 237], [219, 79, 66], "cornflowerBlue"],
+    "66CDAA": [[102, 205, 170], [160, 51, 60], "mediumAquaMarine"],
+    "696969": [[105, 105, 105], [0, 0, 41], "dimGray"],
+    "6A5ACD": [[106, 90, 205], [248, 53, 58], "slateBlue"],
+    "6B8E23": [[107, 142, 35], [80, 60, 35], "oliveDrab"],
+    "708090": [[112, 128, 144], [210, 13, 50], "slateGray"],
+    "778899": [[119, 136, 153], [210, 14, 53], "lightSlateGray"],
+    "7B68EE": [[123, 104, 238], [249, 80, 67], "mediumSlateBlue"],
+    "7CFC00": [[124, 252, 0], [90, 100, 49], "lawnGreen"],
+    "7FFF00": [[127, 255, 0], [90, 100, 50], "chartreuse"],
+    "7FFFD4": [[127, 255, 212], [160, 100, 75], "aquamarine"],
+    "800000": [[128, 0, 0], [0, 100, 25], "maroon"],
+    "800080": [[128, 0, 128], [300, 100, 25], "purple"],
+    "808000": [[128, 128, 0], [60, 100, 25], "olive"],
+    "808080": [[128, 128, 128], [0, 0, 50], "gray"],
+    "87CEEB": [[135, 206, 235], [197, 71, 73], "skyBlue"],
+    "87CEFA": [[135, 206, 250], [203, 92, 75], "lightSkyBlue"],
+    "8A2BE2": [[138, 43, 226], [271, 76, 53], "blueViolet"],
+    "8B0000": [[139, 0, 0], [0, 100, 27], "darkRed"],
+    "8B008B": [[139, 0, 139], [300, 100, 27], "darkMagenta"],
+    "8B4513": [[139, 69, 19], [25, 76, 31], "saddleBrown"],
+    "8FBC8F": [[143, 188, 143], [120, 25, 65], "darkSeaGreen"],
+    "90EE90": [[144, 238, 144], [120, 73, 75], "lightGreen"],
+    "9370D8": [[147, 112, 219], [260, 60, 65], "mediumPurple"],
+    "9400D3": [[148, 0, 211], [282, 100, 41], "darkViolet"],
+    "98FB98": [[152, 251, 152], [120, 93, 79], "paleGreen"],
+    "9932CC": [[153, 50, 204], [280, 61, 50], "darkOrchid"],
+    "9ACD32": [[154, 205, 50], [80, 61, 50], "yellowGreen"],
+    "A0522D": [[160, 82, 45], [19, 56, 40], "sienna"],
+    "A52A2A": [[165, 42, 42], [0, 59, 41], "brown"],
+    "A9A9A9": [[169, 169, 169], [0, 0, 66], "darkGray"],
+    "ADD8E6": [[173, 216, 230], [195, 53, 79], "lightBlue"],
+    "ADFF2F": [[173, 255, 47], [84, 100, 59], "greenYellow"],
+    "AFEEEE": [[175, 238, 238], [180, 65, 81], "paleTurquoise"],
+    "B0C4DE": [[176, 196, 222], [214, 41, 78], "lightSteelBlue"],
+    "B0E0E6": [[176, 224, 230], [187, 52, 80], "powderBlue"],
+    "B22222": [[178, 34, 34], [0, 68, 42], "fireBrick"],
+    "B8860B": [[184, 134, 11], [43, 89, 38], "darkGoldenRod"],
+    "BA55D3": [[186, 85, 211], [288, 59, 58], "mediumOrchid"],
+    "BC8F8F": [[188, 143, 143], [0, 25, 65], "rosyBrown"],
+    "BDB76B": [[189, 183, 107], [56, 38, 58], "darkKhaki"],
+    "C0C0C0": [[192, 192, 192], [0, 0, 75], "silver"],
+    "C71585": [[199, 21, 133], [322, 81, 43], "mediumVioletRed"],
+    "CD5C5C": [[205, 92, 92], [0, 53, 58], "indianRed"],
+    "CD853F": [[205, 133, 63], [30, 59, 53], "peru"],
+    "D2691E": [[210, 105, 30], [25, 75, 47], "chocolate"],
+    "D2B48C": [[210, 180, 140], [34, 44, 69], "tan"],
+    "D3D3D3": [[211, 211, 211], [0, 0, 83], "lightGrey"],
+    "D87093": [[219, 112, 147], [340, 60, 65], "paleVioletRed"],
+    "D8BFD8": [[216, 191, 216], [300, 24, 80], "thistle"],
+    "DA70D6": [[218, 112, 214], [302, 59, 65], "orchid"],
+    "DAA520": [[218, 165, 32], [43, 74, 49], "goldenRod"],
+    "DC143C": [[237, 164, 61], [35, 83, 58], "crimson"],
+    "DCDCDC": [[220, 220, 220], [0, 0, 86], "gainsboro"],
+    "DDA0DD": [[221, 160, 221], [300, 47, 75], "plum"],
+    "DEB887": [[222, 184, 135], [34, 57, 70], "burlyWood"],
+    "E0FFFF": [[224, 255, 255], [180, 100, 94], "lightCyan"],
+    "E6E6FA": [[230, 230, 250], [240, 67, 94], "lavender"],
+    "E9967A": [[233, 150, 122], [15, 72, 70], "darkSalmon"],
+    "EE82EE": [[238, 130, 238], [300, 76, 72], "violet"],
+    "EEE8AA": [[238, 232, 170], [55, 67, 80], "paleGoldenRod"],
+    "F08080": [[240, 128, 128], [0, 79, 72], "lightCoral"],
+    "F0E68C": [[240, 230, 140], [54, 77, 75], "khaki"],
+    "F0F8FF": [[240, 248, 255], [208, 100, 97], "aliceBlue"],
+    "F0FFF0": [[240, 255, 240], [120, 100, 97], "honeyDew"],
+    "F0FFFF": [[240, 255, 255], [180, 100, 97], "azure"],
+    "F4A460": [[244, 164, 96], [28, 87, 67], "sandyBrown"],
+    "F5DEB3": [[245, 222, 179], [39, 77, 83], "wheat"],
+    "F5F5DC": [[245, 245, 220], [60, 56, 91], "beige"],
+    "F5F5F5": [[245, 245, 245], [0, 0, 96], "whiteSmoke"],
+    "F5FFFA": [[245, 255, 250], [150, 100, 98], "mintCream"],
+    "F8F8FF": [[248, 248, 255], [240, 100, 99], "ghostWhite"],
+    "FA8072": [[250, 128, 114], [6, 93, 71], "salmon"],
+    "FAEBD7": [[250, 235, 215], [34, 78, 91], "antiqueWhite"],
+    "FAF0E6": [[250, 240, 230], [30, 67, 94], "linen"],
+    "FAFAD2": [[250, 250, 210], [60, 80, 90], "lightGoldenRodYellow"],
+    "FDF5E6": [[253, 245, 230], [39, 85, 95], "oldLace"],
+    "FF0000": [[255, 0, 0], [0, 100, 50], "red"],
+    "FF00FF": [[255, 0, 255], [300, 100, 50], "magenta"],
+    "FF1493": [[255, 20, 147], [328, 100, 54], "deepPink"],
+    "FF4500": [[255, 69, 0], [16, 100, 50], "orangeRed"],
+    "FF6347": [[255, 99, 71], [9, 100, 64], "tomato"],
+    "FF69B4": [[255, 105, 180], [330, 100, 71], "hotPink"],
+    "FF7F50": [[255, 127, 80], [16, 100, 66], "coral"],
+    "FF8C00": [[255, 140, 0], [33, 100, 50], "darkOrange"],
+    "FFA07A": [[255, 160, 122], [17, 100, 74], "lightSalmon"],
+    "FFA500": [[255, 165, 0], [39, 100, 50], "orange"],
+    "FFB6C1": [[255, 182, 193], [351, 100, 86], "lightPink"],
+    "FFC0CB": [[255, 192, 203], [350, 100, 88], "pink"],
+    "FFD700": [[255, 215, 0], [51, 100, 50], "gold"],
+    "FFDAB9": [[255, 218, 185], [28, 100, 86], "peachPuff"],
+    "FFDEAD": [[255, 222, 173], [36, 100, 84], "navajoWhite"],
+    "FFE4B5": [[255, 228, 181], [38, 100, 85], "moccasin"],
+    "FFE4C4": [[255, 228, 196], [33, 100, 88], "bisque"],
+    "FFE4E1": [[255, 228, 225], [6, 100, 94], "mistyRose"],
+    "FFEBCD": [[255, 235, 205], [36, 100, 90], "blanchedAlmond"],
+    "FFEFD5": [[255, 239, 213], [37, 100, 92], "papayaWhip"],
+    "FFF0F5": [[255, 240, 245], [340, 100, 97], "lavenderBlush"],
+    "FFF5EE": [[255, 245, 238], [25, 100, 97], "seaShell"],
+    "FFF8DC": [[255, 248, 220], [48, 100, 93], "cornsilk"],
+    "FFFACD": [[255, 250, 205], [54, 100, 90], "lemonChiffon"],
+    "FFFAF0": [[255, 250, 240], [40, 100, 97], "floralWhite"],
+    "FFFAFA": [[255, 250, 250], [0, 100, 99], "snow"],
+    "FFFF00": [[255, 255, 0], [60, 100, 50], "yellow"],
+    "FFFFE0": [[255, 255, 224], [60, 100, 94], "lightYellow"],
+    "FFFFF0": [[255, 255, 240], [60, 100, 97], "ivory"],
+    "FFFFFF": [[255, 255, 255], [0, 100, 100], "white"]
+};
+
+// Simple Values
+WebInspector.Color.Nicknames = {
+    "aliceblue": "F0F8FF",
+    "antiquewhite": "FAEBD7",
+    "aqua": "00FFFF",
+    "aquamarine": "7FFFD4",
+    "azure": "F0FFFF",
+    "beige": "F5F5DC",
+    "bisque": "FFE4C4",
+    "black": "000000",
+    "blanchedalmond": "FFEBCD",
+    "blue": "0000FF",
+    "blueviolet": "8A2BE2",
+    "brown": "A52A2A",
+    "burlywood": "DEB887",
+    "cadetblue": "5F9EA0",
+    "chartreuse": "7FFF00",
+    "chocolate": "D2691E",
+    "coral": "FF7F50",
+    "cornflowerblue": "6495ED",
+    "cornsilk": "FFF8DC",
+    "crimson": "DC143C",
+    "cyan": "00FFFF",
+    "darkblue": "00008B",
+    "darkcyan": "008B8B",
+    "darkgoldenrod": "B8860B",
+    "darkgray": "A9A9A9",
+    "darkgreen": "006400",
+    "darkkhaki": "BDB76B",
+    "darkmagenta": "8B008B",
+    "darkolivegreen": "556B2F",
+    "darkorange": "FF8C00",
+    "darkorchid": "9932CC",
+    "darkred": "8B0000",
+    "darksalmon": "E9967A",
+    "darkseagreen": "8FBC8F",
+    "darkslateblue": "483D8B",
+    "darkslategray": "2F4F4F",
+    "darkturquoise": "00CED1",
+    "darkviolet": "9400D3",
+    "deeppink": "FF1493",
+    "deepskyblue": "00BFFF",
+    "dimgray": "696969",
+    "dodgerblue": "1E90FF",
+    "firebrick": "B22222",
+    "floralwhite": "FFFAF0",
+    "forestgreen": "228B22",
+    "fuchsia": "FF00FF",
+    "gainsboro": "DCDCDC",
+    "ghostwhite": "F8F8FF",
+    "gold": "FFD700",
+    "goldenrod": "DAA520",
+    "gray": "808080",
+    "green": "008000",
+    "greenyellow": "ADFF2F",
+    "honeydew": "F0FFF0",
+    "hotpink": "FF69B4",
+    "indianred": "CD5C5C",
+    "indigo": "4B0082",
+    "ivory": "FFFFF0",
+    "khaki": "F0E68C",
+    "lavender": "E6E6FA",
+    "lavenderblush": "FFF0F5",
+    "lawngreen": "7CFC00",
+    "lemonchiffon": "FFFACD",
+    "lightblue": "ADD8E6",
+    "lightcoral": "F08080",
+    "lightcyan": "E0FFFF",
+    "lightgoldenrodyellow": "FAFAD2",
+    "lightgreen": "90EE90",
+    "lightgrey": "D3D3D3",
+    "lightpink": "FFB6C1",
+    "lightsalmon": "FFA07A",
+    "lightseagreen": "20B2AA",
+    "lightskyblue": "87CEFA",
+    "lightslategray": "778899",
+    "lightsteelblue": "B0C4DE",
+    "lightyellow": "FFFFE0",
+    "lime": "00FF00",
+    "limegreen": "32CD32",
+    "linen": "FAF0E6",
+    "magenta": "FF00FF",
+    "maroon": "800000",
+    "mediumaquamarine": "66CDAA",
+    "mediumblue": "0000CD",
+    "mediumorchid": "BA55D3",
+    "mediumpurple": "9370D8",
+    "mediumseagreen": "3CB371",
+    "mediumslateblue": "7B68EE",
+    "mediumspringgreen": "00FA9A",
+    "mediumturquoise": "48D1CC",
+    "mediumvioletred": "C71585",
+    "midnightblue": "191970",
+    "mintcream": "F5FFFA",
+    "mistyrose": "FFE4E1",
+    "moccasin": "FFE4B5",
+    "navajowhite": "FFDEAD",
+    "navy": "000080",
+    "oldlace": "FDF5E6",
+    "olive": "808000",
+    "olivedrab": "6B8E23",
+    "orange": "FFA500",
+    "orangered": "FF4500",
+    "orchid": "DA70D6",
+    "palegoldenrod": "EEE8AA",
+    "palegreen": "98FB98",
+    "paleturquoise": "AFEEEE",
+    "palevioletred": "D87093",
+    "papayawhip": "FFEFD5",
+    "peachpuff": "FFDAB9",
+    "peru": "CD853F",
+    "pink": "FFC0CB",
+    "plum": "DDA0DD",
+    "powderblue": "B0E0E6",
+    "purple": "800080",
+    "red": "FF0000",
+    "rosybrown": "BC8F8F",
+    "royalblue": "4169E1",
+    "saddlebrown": "8B4513",
+    "salmon": "FA8072",
+    "sandybrown": "F4A460",
+    "seagreen": "2E8B57",
+    "seashell": "FFF5EE",
+    "sienna": "A0522D",
+    "silver": "C0C0C0",
+    "skyblue": "87CEEB",
+    "slateblue": "6A5ACD",
+    "slategray": "708090",
+    "snow": "FFFAFA",
+    "springgreen": "00FF7F",
+    "steelblue": "4682B4",
+    "tan": "D2B48C",
+    "teal": "008080",
+    "thistle": "D8BFD8",
+    "tomato": "FF6347",
+    "turquoise": "40E0D0",
+    "violet": "EE82EE",
+    "wheat": "F5DEB3",
+    "white": "FFFFFF",
+    "whitesmoke": "F5F5F5",
+    "yellow": "FFFF00",
+    "yellowgreen": "9ACD32"
+};
+
+// Advanced Values [rgba, hsla, nickname]
+WebInspector.Color.AdvancedNickNames = {
+    "transparent": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"],
+    "rgba(0,0,0,0)": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"],
+    "hsla(0,0,0,0)": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"],
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ConsoleView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ConsoleView.js
new file mode 100644
index 0000000..fa9a363
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ConsoleView.js
@@ -0,0 +1,970 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.ConsoleView = function(drawer)
+{
+    WebInspector.View.call(this, document.getElementById("console-view"));
+
+    this.messages = [];
+    this.drawer = drawer;
+
+    this.clearButton = document.getElementById("clear-console-status-bar-item");
+    this.clearButton.title = WebInspector.UIString("Clear console log.");
+    this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false);
+
+    this.messagesElement = document.getElementById("console-messages");
+    this.messagesElement.addEventListener("selectstart", this._messagesSelectStart.bind(this), false);
+    this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true);
+
+    this.promptElement = document.getElementById("console-prompt");
+    this.promptElement.handleKeyEvent = this._promptKeyDown.bind(this);
+    this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), " .=:[({;");
+
+    this.topGroup = new WebInspector.ConsoleGroup(null, 0);
+    this.messagesElement.insertBefore(this.topGroup.element, this.promptElement);
+    this.groupLevel = 0;
+    this.currentGroup = this.topGroup;
+
+    this.toggleConsoleButton = document.getElementById("console-status-bar-item");
+    this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
+    this.toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false);
+
+    var anchoredStatusBar = document.getElementById("anchored-status-bar-items");
+    anchoredStatusBar.appendChild(this.toggleConsoleButton);
+    
+    // Will hold the list of filter elements
+    this.filterBarElement = document.getElementById("console-filter");
+    
+    function createFilterElement(category) {
+        var categoryElement = document.createElement("li");
+        categoryElement.category = category;
+     
+        categoryElement.addStyleClass(categoryElement.category);
+            
+        var label = category.toString();
+        categoryElement.appendChild(document.createTextNode(label));
+     
+        categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
+     
+        this.filterBarElement.appendChild(categoryElement);
+        return categoryElement;
+    }
+    
+    this.allElement = createFilterElement.call(this, "All");
+    this.errorElement = createFilterElement.call(this, "Errors");
+    this.warningElement = createFilterElement.call(this, "Warnings");
+    this.logElement = createFilterElement.call(this, "Logs");
+
+    this.filter(this.allElement);
+}
+
+WebInspector.ConsoleView.prototype = {
+    
+    _updateFilter: function(e)
+    {
+        this.filter(e.target);
+    },
+    
+    filter: function(target)
+    {
+        if (target.category == "All") {
+            if (target.hasStyleClass("selected")) {
+                // We can't unselect all, so we break early here
+                return;
+            }
+            
+            this.errorElement.removeStyleClass("selected");
+            this.warningElement.removeStyleClass("selected");
+            this.logElement.removeStyleClass("selected");
+            
+            document.getElementById("console-messages").removeStyleClass("filter-errors");
+            document.getElementById("console-messages").removeStyleClass("filter-warnings");
+            document.getElementById("console-messages").removeStyleClass("filter-logs");
+        } else {
+            // Something other than all is being selected, so we want to unselect all
+            if (this.allElement.hasStyleClass("selected")) {
+                this.allElement.removeStyleClass("selected");
+                document.getElementById("console-messages").removeStyleClass("filter-all");
+            }
+        }
+        
+        if (target.hasStyleClass("selected")) {
+            target.removeStyleClass("selected");
+            var newClass = "filter-" + target.category.toLowerCase();
+            var filterElement = document.getElementById("console-messages");
+            filterElement.removeStyleClass(newClass);
+        } else {
+            target.addStyleClass("selected");
+            var newClass = "filter-" + target.category.toLowerCase();
+            var filterElement = document.getElementById("console-messages");
+            filterElement.addStyleClass(newClass);
+        }
+    },
+    
+    _toggleConsoleButtonClicked: function()
+    {
+        this.drawer.visibleView = this;
+    },
+
+    attach: function(mainElement, statusBarElement)
+    {
+        mainElement.appendChild(this.element);
+        statusBarElement.appendChild(this.clearButton);
+        statusBarElement.appendChild(this.filterBarElement);
+    },
+
+    show: function()
+    {
+        this.toggleConsoleButton.addStyleClass("toggled-on");
+        this.toggleConsoleButton.title = WebInspector.UIString("Hide console.");
+        if (!this.prompt.isCaretInsidePrompt())
+            this.prompt.moveCaretToEndOfPrompt();
+    },
+
+    afterShow: function()
+    {
+        WebInspector.currentFocusElement = this.promptElement;  
+    },
+
+    hide: function()
+    {
+        this.toggleConsoleButton.removeStyleClass("toggled-on");
+        this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
+    },
+
+    addMessage: function(msg)
+    {
+        if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) {
+            msg.totalRepeatCount = msg.repeatCount;
+            msg.repeatDelta = msg.repeatCount;
+
+            var messageRepeated = false;
+
+            if (msg.isEqual && msg.isEqual(this.previousMessage)) {
+                // Because sometimes we get a large number of repeated messages and sometimes
+                // we get them one at a time, we need to know the difference between how many
+                // repeats we used to have and how many we have now.
+                msg.repeatDelta -= this.previousMessage.totalRepeatCount;
+
+                if (!isNaN(this.repeatCountBeforeCommand))
+                    msg.repeatCount -= this.repeatCountBeforeCommand;
+
+                if (!this.commandSincePreviousMessage) {
+                    // Recreate the previous message element to reset the repeat count.
+                    var messagesElement = this.currentGroup.messagesElement;
+                    messagesElement.removeChild(messagesElement.lastChild);
+                    messagesElement.appendChild(msg.toMessageElement());
+
+                    messageRepeated = true;
+                }
+            } else
+                delete this.repeatCountBeforeCommand;
+
+            // Increment the error or warning count
+            switch (msg.level) {
+            case WebInspector.ConsoleMessage.MessageLevel.Warning:
+                WebInspector.warnings += msg.repeatDelta;
+                break;
+            case WebInspector.ConsoleMessage.MessageLevel.Error:
+                WebInspector.errors += msg.repeatDelta;
+                break;
+            }
+
+            // Add message to the resource panel
+            if (msg.url in WebInspector.resourceURLMap) {
+                msg.resource = WebInspector.resourceURLMap[msg.url];
+                if (WebInspector.panels.resources)
+                    WebInspector.panels.resources.addMessageToResource(msg.resource, msg);
+            }
+
+            this.commandSincePreviousMessage = false;
+            this.previousMessage = msg;
+
+            if (messageRepeated)
+                return;
+        } else if (msg instanceof WebInspector.ConsoleCommand) {
+            if (this.previousMessage) {
+                this.commandSincePreviousMessage = true;
+                this.repeatCountBeforeCommand = this.previousMessage.totalRepeatCount;
+            }
+        }
+
+        this.messages.push(msg);
+
+        if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
+            if (this.groupLevel < 1)
+                return;
+
+            this.groupLevel--;
+
+            this.currentGroup = this.currentGroup.parentGroup;
+        } else {
+            if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) {
+                this.groupLevel++;
+
+                var group = new WebInspector.ConsoleGroup(this.currentGroup, this.groupLevel);
+                this.currentGroup.messagesElement.appendChild(group.element);
+                this.currentGroup = group;
+            }
+
+            this.currentGroup.addMessage(msg);
+        }
+
+        this.promptElement.scrollIntoView(false);
+    },
+
+    clearMessages: function(clearInspectorController)
+    {
+        if (clearInspectorController)
+            InspectorController.clearMessages(false);
+        if (WebInspector.panels.resources)
+            WebInspector.panels.resources.clearMessages();
+
+        this.messages = [];
+
+        this.groupLevel = 0;
+        this.currentGroup = this.topGroup;
+        this.topGroup.messagesElement.removeChildren();
+
+        WebInspector.errors = 0;
+        WebInspector.warnings = 0;
+
+        delete this.commandSincePreviousMessage;
+        delete this.repeatCountBeforeCommand;
+        delete this.previousMessage;
+    },
+
+    completions: function(wordRange, bestMatchOnly, completionsReadyCallback)
+    {
+        // Pass less stop characters to rangeOfWord so the range will be a more complete expression.
+        const expressionStopCharacters = " =:{;";
+        var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, expressionStopCharacters, this.promptElement, "backward");
+        var expressionString = expressionRange.toString();
+        var lastIndex = expressionString.length - 1;
+
+        var dotNotation = (expressionString[lastIndex] === ".");
+        var bracketNotation = (expressionString[lastIndex] === "[");
+
+        if (dotNotation || bracketNotation)
+            expressionString = expressionString.substr(0, lastIndex);
+
+        var prefix = wordRange.toString();
+        if (!expressionString && !prefix)
+            return;
+
+        var reportCompletions = this._reportCompletions.bind(this, bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix);
+        // Collect comma separated object properties for the completion.
+
+        if (!expressionString) {
+            if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
+                // Evaluate into properties in scope of the selected call frame.
+                reportCompletions(WebInspector.panels.scripts.variablesInSelectedCallFrame());
+                return;
+            } else {
+                expressionString = "this";
+            }
+        }
+
+        var includeInspectorCommandLineAPI = (!dotNotation && !bracketNotation);
+        InjectedScriptAccess.getCompletions(expressionString, includeInspectorCommandLineAPI, reportCompletions);
+    },
+
+    _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) {
+        if (isException)
+            return;
+
+        if (bracketNotation) {
+            if (prefix.length && prefix[0] === "'")
+                var quoteUsed = "'";
+            else
+                var quoteUsed = "\"";
+        }
+
+        var results = [];
+        var properties = Object.sortedProperties(result);
+
+        for (var i = 0; i < properties.length; ++i) {
+            var property = properties[i];
+
+            if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
+                continue;
+
+            if (bracketNotation) {
+                if (!/^[0-9]+$/.test(property))
+                    property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
+                property += "]";
+            }
+
+            if (property.length < prefix.length)
+                continue;
+            if (property.indexOf(prefix) !== 0)
+                continue;
+
+            results.push(property);
+            if (bestMatchOnly)
+                break;
+        }
+        setTimeout(completionsReadyCallback, 0, results);
+    },
+
+    _clearButtonClicked: function()
+    {
+        this.clearMessages(true);
+    },
+
+    _messagesSelectStart: function(event)
+    {
+        if (this._selectionTimeout)
+            clearTimeout(this._selectionTimeout);
+
+        this.prompt.clearAutoComplete();
+
+        function moveBackIfOutside()
+        {
+            delete this._selectionTimeout;
+            if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
+                this.prompt.moveCaretToEndOfPrompt();
+            this.prompt.autoCompleteSoon();
+        }
+
+        this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
+    },
+
+    _messagesClicked: function(event)
+    {
+        var link = event.target.enclosingNodeOrSelfWithNodeName("a");
+        if (!link || !link.representedNode)
+            return;
+
+        WebInspector.updateFocusedNode(link.representedNode.id);
+        event.stopPropagation();
+        event.preventDefault();
+    },
+
+    _promptKeyDown: function(event)
+    {
+        switch (event.keyIdentifier) {
+            case "Enter":
+                this._enterKeyPressed(event);
+                return;
+        }
+
+        this.prompt.handleKeyEvent(event);
+    },
+
+    evalInInspectedWindow: function(expression, callback)
+    {
+        if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
+            WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, callback);
+            return;
+        }
+        this.doEvalInWindow(expression, callback);
+    },
+
+    doEvalInWindow: function(expression, callback)
+    {
+        if (!expression) {
+            // There is no expression, so the completion should happen against global properties.
+            expression = "this";
+        }
+
+        function evalCallback(result)
+        {
+            callback(result.value, result.isException);
+        };
+        InjectedScriptAccess.evaluate(expression, evalCallback);
+    },
+
+    _enterKeyPressed: function(event)
+    {
+        if (event.altKey)
+            return;
+
+        event.preventDefault();
+        event.stopPropagation();
+
+        this.prompt.clearAutoComplete(true);
+
+        var str = this.prompt.text;
+        if (!str.length)
+            return;
+
+        var commandMessage = new WebInspector.ConsoleCommand(str);
+        this.addMessage(commandMessage);
+
+        var self = this;
+        function printResult(result, exception)
+        {
+            self.prompt.history.push(str);
+            self.prompt.historyOffset = 0;
+            self.prompt.text = "";
+            self.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage));
+        }
+        this.evalInInspectedWindow(str, printResult);
+    },
+
+    _format: function(output, forceObjectFormat)
+    {
+        var isProxy = (output != null && typeof output === "object");
+
+        if (forceObjectFormat)
+            var type = "object";
+        else
+            var type = Object.proxyType(output);
+
+        if (isProxy && type !== "object" && type !== "function" && type !== "array" && type !== "node") {
+            // Unwrap primitive value, skip decoration.
+            output = output.description;
+            type = "undecorated"
+        }
+
+        // We don't perform any special formatting on these types, so we just
+        // pass them through the simple _formatvalue function.
+        var undecoratedTypes = {
+            "undefined": 1,
+            "null": 1,
+            "boolean": 1,
+            "number": 1,
+            "undecorated": 1
+        };
+
+        var formatter;
+        if (forceObjectFormat)
+            formatter = "_formatobject";
+        else if (type in undecoratedTypes)
+            formatter = "_formatvalue";
+        else {
+            formatter = "_format" + type;
+            if (!(formatter in this)) {
+                formatter = "_formatobject";
+                type = "object";
+            }
+        }
+
+        var span = document.createElement("span");
+        span.addStyleClass("console-formatted-" + type);
+        this[formatter](output, span);
+        return span;
+    },
+
+    _formatvalue: function(val, elem)
+    {
+        elem.appendChild(document.createTextNode(val));
+    },
+
+    _formatfunction: function(func, elem)
+    {
+        elem.appendChild(document.createTextNode(func.description));
+    },
+
+    _formatdate: function(date, elem)
+    {
+        elem.appendChild(document.createTextNode(date));
+    },
+
+    _formatstring: function(str, elem)
+    {
+        elem.appendChild(document.createTextNode("\"" + str + "\""));
+    },
+
+    _formatregexp: function(re, elem)
+    {
+        var formatted = String(re.description).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);
+        elem.appendChild(document.createTextNode(formatted));
+    },
+
+    _formatarray: function(arr, elem)
+    {
+        var self = this;
+        function printResult(properties)
+        {
+            if (!properties)
+                return;
+            elem.appendChild(document.createTextNode("["));
+            for (var i = 0; i < properties.length; ++i) {
+                var property = properties[i].value;
+                elem.appendChild(self._format(property));
+                if (i < properties.length - 1)
+                    elem.appendChild(document.createTextNode(", "));
+            }
+            elem.appendChild(document.createTextNode("]"));
+        }
+        InjectedScriptAccess.getProperties(arr, false, printResult);
+    },
+
+    _formatnode: function(object, elem)
+    {
+        function printNode(nodeId)
+        {
+            if (!nodeId)
+                return;
+            var treeOutline = new WebInspector.ElementsTreeOutline();
+            treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId);
+            treeOutline.element.addStyleClass("outline-disclosure");
+            if (!treeOutline.children[0].hasChildren)
+                treeOutline.element.addStyleClass("single-node");
+            elem.appendChild(treeOutline.element);
+        }
+        InjectedScriptAccess.pushNodeToFrontend(object, printNode);
+    },
+
+    _formatobject: function(obj, elem)
+    {
+        elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element);
+    },
+
+    _formaterror: function(obj, elem)
+    {
+        var messageElement = document.createElement("span");
+        messageElement.className = "error-message";
+        messageElement.textContent = obj.name + ": " + obj.message;
+        elem.appendChild(messageElement);
+
+        if (obj.sourceURL) {
+            var urlElement = document.createElement("a");
+            urlElement.className = "webkit-html-resource-link";
+            urlElement.href = obj.sourceURL;
+            urlElement.lineNumber = obj.line;
+            urlElement.preferredPanel = "scripts";
+
+            if (obj.line > 0)
+                urlElement.textContent = WebInspector.displayNameForURL(obj.sourceURL) + ":" + obj.line;
+            else
+                urlElement.textContent = WebInspector.displayNameForURL(obj.sourceURL);
+
+            elem.appendChild(document.createTextNode(" ("));
+            elem.appendChild(urlElement);
+            elem.appendChild(document.createTextNode(")"));
+        }
+    }
+}
+
+WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype;
+
+WebInspector.ConsoleMessage = function(source, type, level, line, url, groupLevel, repeatCount)
+{
+    this.source = source;
+    this.type = type;
+    this.level = level;
+    this.line = line;
+    this.url = url;
+    this.groupLevel = groupLevel;
+    this.repeatCount = repeatCount;
+    if (arguments.length > 7)
+        this.setMessageBody(Array.prototype.slice.call(arguments, 7));
+}
+
+WebInspector.ConsoleMessage.prototype = {
+    setMessageBody: function(args)
+    {
+        switch (this.type) {
+            case WebInspector.ConsoleMessage.MessageType.Trace:
+                var span = document.createElement("span");
+                span.addStyleClass("console-formatted-trace");
+                var stack = Array.prototype.slice.call(args);
+                var funcNames = stack.map(function(f) {
+                    return f || WebInspector.UIString("(anonymous function)");
+                });
+                span.appendChild(document.createTextNode(funcNames.join("\n")));
+                this.formattedMessage = span;
+                break;
+            case WebInspector.ConsoleMessage.MessageType.Object:
+                this.formattedMessage = this._format(["%O", args[0]]);
+                break;
+            default:
+                this.formattedMessage = this._format(args);
+                break;
+        }
+
+        // This is used for inline message bubbles in SourceFrames, or other plain-text representations.
+        this.message = this.formattedMessage.textContent;
+    },
+
+    isErrorOrWarning: function()
+    {
+        return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
+    },
+
+    _format: function(parameters)
+    {
+        var formattedResult = document.createElement("span");
+
+        if (!parameters.length)
+            return formattedResult;
+
+        function formatForConsole(obj)
+        {
+            return WebInspector.console._format(obj);
+        }
+
+        function formatAsObjectForConsole(obj)
+        {
+            return WebInspector.console._format(obj, true);
+        }
+
+        if (typeof parameters[0] === "string") {
+            var formatters = {}
+            for (var i in String.standardFormatters)
+                formatters[i] = String.standardFormatters[i];
+
+            // Firebug uses %o for formatting objects.
+            formatters.o = formatForConsole;
+            // Firebug allows both %i and %d for formatting integers.
+            formatters.i = formatters.d;
+            // Support %O to force object formating, instead of the type-based %o formatting.
+            formatters.O = formatAsObjectForConsole;
+
+            function append(a, b)
+            {
+                if (!(b instanceof Node))
+                    a.appendChild(WebInspector.linkifyStringAsFragment(b.toString()));
+                else
+                    a.appendChild(b);
+                return a;
+            }
+
+            var result = String.format(parameters[0], parameters.slice(1), formatters, formattedResult, append);
+            formattedResult = result.formattedResult;
+            parameters = result.unusedSubstitutions;
+            if (parameters.length)
+                formattedResult.appendChild(document.createTextNode(" "));
+        }
+
+        for (var i = 0; i < parameters.length; ++i) {
+            if (typeof parameters[i] === "string")
+                formattedResult.appendChild(WebInspector.linkifyStringAsFragment(parameters[i]));
+            else
+                formattedResult.appendChild(formatForConsole(parameters[i]));
+
+            if (i < parameters.length - 1)
+                formattedResult.appendChild(document.createTextNode(" "));
+        }
+
+        return formattedResult;
+    },
+
+    toMessageElement: function()
+    {
+        if (this.propertiesSection)
+            return this.propertiesSection.element;
+
+        var element = document.createElement("div");
+        element.message = this;
+        element.className = "console-message";
+
+        switch (this.source) {
+            case WebInspector.ConsoleMessage.MessageSource.HTML:
+                element.addStyleClass("console-html-source");
+                break;
+            case WebInspector.ConsoleMessage.MessageSource.WML:
+                element.addStyleClass("console-wml-source");
+                break;
+            case WebInspector.ConsoleMessage.MessageSource.XML:
+                element.addStyleClass("console-xml-source");
+                break;
+            case WebInspector.ConsoleMessage.MessageSource.JS:
+                element.addStyleClass("console-js-source");
+                break;
+            case WebInspector.ConsoleMessage.MessageSource.CSS:
+                element.addStyleClass("console-css-source");
+                break;
+            case WebInspector.ConsoleMessage.MessageSource.Other:
+                element.addStyleClass("console-other-source");
+                break;
+        }
+
+        switch (this.level) {
+            case WebInspector.ConsoleMessage.MessageLevel.Tip:
+                element.addStyleClass("console-tip-level");
+                break;
+            case WebInspector.ConsoleMessage.MessageLevel.Log:
+                element.addStyleClass("console-log-level");
+                break;
+            case WebInspector.ConsoleMessage.MessageLevel.Debug:
+                element.addStyleClass("console-debug-level");
+                break;
+            case WebInspector.ConsoleMessage.MessageLevel.Warning:
+                element.addStyleClass("console-warning-level");
+                break;
+            case WebInspector.ConsoleMessage.MessageLevel.Error:
+                element.addStyleClass("console-error-level");
+                break;
+        }
+        
+        if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup) {
+            element.addStyleClass("console-group-title");
+        }
+
+        if (this.elementsTreeOutline) {
+            element.addStyleClass("outline-disclosure");
+            element.appendChild(this.elementsTreeOutline.element);
+            return element;
+        }
+
+        if (this.repeatCount > 1) {
+            var messageRepeatCountElement = document.createElement("span");
+            messageRepeatCountElement.className = "bubble";
+            messageRepeatCountElement.textContent = this.repeatCount;
+
+            element.appendChild(messageRepeatCountElement);
+            element.addStyleClass("repeated-message");
+        }
+
+        if (this.url && this.url !== "undefined") {
+            var urlElement = document.createElement("a");
+            urlElement.className = "console-message-url webkit-html-resource-link";
+            urlElement.href = this.url;
+            urlElement.lineNumber = this.line;
+
+            if (this.source === WebInspector.ConsoleMessage.MessageSource.JS)
+                urlElement.preferredPanel = "scripts";
+
+            if (this.line > 0)
+                urlElement.textContent = WebInspector.displayNameForURL(this.url) + ":" + this.line;
+            else
+                urlElement.textContent = WebInspector.displayNameForURL(this.url);
+
+            element.appendChild(urlElement);
+        }
+
+        var messageTextElement = document.createElement("span");
+        messageTextElement.className = "console-message-text";
+        messageTextElement.appendChild(this.formattedMessage);
+        element.appendChild(messageTextElement);
+
+        return element;
+    },
+
+    toString: function()
+    {
+        var sourceString;
+        switch (this.source) {
+            case WebInspector.ConsoleMessage.MessageSource.HTML:
+                sourceString = "HTML";
+                break;
+            case WebInspector.ConsoleMessage.MessageSource.WML:
+                sourceString = "WML";
+                break;
+            case WebInspector.ConsoleMessage.MessageSource.XML:
+                sourceString = "XML";
+                break;
+            case WebInspector.ConsoleMessage.MessageSource.JS:
+                sourceString = "JS";
+                break;
+            case WebInspector.ConsoleMessage.MessageSource.CSS:
+                sourceString = "CSS";
+                break;
+            case WebInspector.ConsoleMessage.MessageSource.Other:
+                sourceString = "Other";
+                break;
+        }
+
+        var typeString;
+        switch (this.type) {
+            case WebInspector.ConsoleMessage.MessageType.Log:
+                typeString = "Log";
+                break;
+            case WebInspector.ConsoleMessage.MessageType.Object:
+                typeString = "Object";
+                break;
+            case WebInspector.ConsoleMessage.MessageType.Trace:
+                typeString = "Trace";
+                break;
+            case WebInspector.ConsoleMessage.MessageType.StartGroup:
+                typeString = "Start Group";
+                break;
+            case WebInspector.ConsoleMessage.MessageType.EndGroup:
+                typeString = "End Group";
+                break;
+        }
+        
+        var levelString;
+        switch (this.level) {
+            case WebInspector.ConsoleMessage.MessageLevel.Tip:
+                levelString = "Tip";
+                break;
+            case WebInspector.ConsoleMessage.MessageLevel.Log:
+                levelString = "Log";
+                break;
+            case WebInspector.ConsoleMessage.MessageLevel.Warning:
+                levelString = "Warning";
+                break;
+            case WebInspector.ConsoleMessage.MessageLevel.Debug:
+                levelString = "Debug";
+                break;
+            case WebInspector.ConsoleMessage.MessageLevel.Error:
+                levelString = "Error";
+                break;
+        }
+
+        return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line;
+    },
+
+    isEqual: function(msg, disreguardGroup)
+    {
+        if (!msg)
+            return false;
+
+        var ret = (this.source == msg.source)
+            && (this.type == msg.type)
+            && (this.level == msg.level)
+            && (this.line == msg.line)
+            && (this.url == msg.url)
+            && (this.message == msg.message);
+
+        return (disreguardGroup ? ret : (ret && (this.groupLevel == msg.groupLevel)));
+    }
+}
+
+// Note: Keep these constants in sync with the ones in Console.h
+WebInspector.ConsoleMessage.MessageSource = {
+    HTML: 0,
+    WML: 1,
+    XML: 2,
+    JS: 3,
+    CSS: 4,
+    Other: 5
+}
+
+WebInspector.ConsoleMessage.MessageType = {
+    Log: 0,
+    Object: 1,
+    Trace: 2,
+    StartGroup: 3,
+    EndGroup: 4
+}
+
+WebInspector.ConsoleMessage.MessageLevel = {
+    Tip: 0,
+    Log: 1,
+    Warning: 2,
+    Error: 3,
+    Debug: 4
+}
+
+WebInspector.ConsoleCommand = function(command)
+{
+    this.command = command;
+}
+
+WebInspector.ConsoleCommand.prototype = {
+    toMessageElement: function()
+    {
+        var element = document.createElement("div");
+        element.command = this;
+        element.className = "console-user-command";
+
+        var commandTextElement = document.createElement("span");
+        commandTextElement.className = "console-message-text";
+        commandTextElement.textContent = this.command;
+        element.appendChild(commandTextElement);
+
+        return element;
+    }
+}
+
+WebInspector.ConsoleTextMessage = function(text, level)
+{
+    level = level || WebInspector.ConsoleMessage.MessageLevel.Log;
+    WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, null, 1, text);
+}
+
+WebInspector.ConsoleTextMessage.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;
+
+WebInspector.ConsoleCommandResult = function(result, exception, originatingCommand)
+{
+    var level = (exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
+    var message = (exception ? String(result) : result);
+    var line = (exception ? result.line : -1);
+    var url = (exception ? result.sourceURL : null);
+
+    WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, line, url, null, 1, message);
+
+    this.originatingCommand = originatingCommand;
+}
+
+WebInspector.ConsoleCommandResult.prototype = {
+    toMessageElement: function()
+    {
+        var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this);
+        element.addStyleClass("console-user-command-result");
+        return element;
+    }
+}
+
+WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;
+
+WebInspector.ConsoleGroup = function(parentGroup, level)
+{
+    this.parentGroup = parentGroup;
+    this.level = level;
+
+    var element = document.createElement("div");
+    element.className = "console-group";
+    element.group = this;
+    this.element = element;
+
+    var messagesElement = document.createElement("div");
+    messagesElement.className = "console-group-messages";
+    element.appendChild(messagesElement);
+    this.messagesElement = messagesElement;
+}
+
+WebInspector.ConsoleGroup.prototype = {
+    addMessage: function(msg)
+    {
+        var element = msg.toMessageElement();
+        
+        if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) {
+            this.messagesElement.parentNode.insertBefore(element, this.messagesElement);
+            element.addEventListener("click", this._titleClicked.bind(this), true);
+        } else
+            this.messagesElement.appendChild(element);
+
+        if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand)
+            element.previousSibling.addStyleClass("console-adjacent-user-command-result");
+    },
+
+    _titleClicked: function(event)
+    {
+        var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title");
+        if (groupTitleElement) {
+            var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group");
+            if (groupElement)
+                if (groupElement.hasStyleClass("collapsed"))
+                    groupElement.removeStyleClass("collapsed");
+                else
+                    groupElement.addStyleClass("collapsed");
+            groupTitleElement.scrollIntoViewIfNeeded(true);
+        }
+
+        event.stopPropagation();
+        event.preventDefault();
+    }
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CookieItemsView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CookieItemsView.js
new file mode 100644
index 0000000..f9604a4
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CookieItemsView.js
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2009 Apple Inc.  All rights reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.CookieItemsView = function()
+{
+    WebInspector.View.call(this);
+
+    this.element.addStyleClass("storage-view");
+    this.element.addStyleClass("table");
+
+    this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item");
+    this.deleteButton.visible = false;
+    this.deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);
+
+    this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
+    this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
+}
+
+WebInspector.CookieItemsView.prototype = {
+    get statusBarItems()
+    {
+        return [this.refreshButton.element, this.deleteButton.element];
+    },
+
+    show: function(parentElement)
+    {
+        WebInspector.View.prototype.show.call(this, parentElement);
+        this.update();
+    },
+
+    hide: function()
+    {
+        WebInspector.View.prototype.hide.call(this);
+        this.deleteButton.visible = false;
+    },
+
+    update: function()
+    {
+        this.element.removeChildren();
+
+        var self = this;
+        function callback(cookies, isAdvanced) {
+            var dataGrid = (isAdvanced ? self.dataGridForCookies(cookies) : self.simpleDataGridForCookies(cookies));
+            if (dataGrid) {
+                self._dataGrid = dataGrid;
+                self.element.appendChild(dataGrid.element);
+                if (isAdvanced)
+                    self.deleteButton.visible = true;
+            } else {
+                var emptyMsgElement = document.createElement("div");
+                emptyMsgElement.className = "storage-table-empty";
+                emptyMsgElement.textContent = WebInspector.UIString("This site has no cookies.");
+                self.element.appendChild(emptyMsgElement);
+                self._dataGrid = null;
+                self.deleteButton.visible = false;
+            }
+        }
+
+        WebInspector.Cookies.getCookiesAsync(callback);
+    },
+
+    dataGridForCookies: function(cookies)
+    {
+        if (!cookies.length)
+            return null;
+
+        for (var i = 0; i < cookies.length; ++i)
+            cookies[i].expires = new Date(cookies[i].expires);
+
+        var columns = { 0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {} };
+        columns[0].title = WebInspector.UIString("Name");
+        columns[0].width = columns[0].title.length;
+        columns[1].title = WebInspector.UIString("Value");
+        columns[1].width = columns[1].title.length;
+        columns[2].title = WebInspector.UIString("Domain");
+        columns[2].width = columns[2].title.length;
+        columns[3].title = WebInspector.UIString("Path");
+        columns[3].width = columns[3].title.length;
+        columns[4].title = WebInspector.UIString("Expires");
+        columns[4].width = columns[4].title.length;
+        columns[5].title = WebInspector.UIString("Size");
+        columns[5].width = columns[5].title.length;
+        columns[5].aligned = "right";
+        columns[6].title = WebInspector.UIString("HTTP");
+        columns[6].width = columns[6].title.length;
+        columns[6].aligned = "centered";
+        columns[7].title = WebInspector.UIString("Secure");
+        columns[7].width = columns[7].title.length;
+        columns[7].aligned = "centered";
+
+        function updateDataAndColumn(index, value) {
+            data[index] = value;
+            if (value.length > columns[index].width)
+                columns[index].width = value.length;
+        }
+
+        var data;
+        var nodes = [];
+        for (var i = 0; i < cookies.length; ++i) {
+            var cookie = cookies[i];
+            data = {};
+
+            updateDataAndColumn(0, cookie.name);
+            updateDataAndColumn(1, cookie.value);
+            updateDataAndColumn(2, cookie.domain);
+            updateDataAndColumn(3, cookie.path);
+            updateDataAndColumn(4, (cookie.session ? WebInspector.UIString("Session") : cookie.expires.toGMTString()));
+            updateDataAndColumn(5, Number.bytesToString(cookie.size, WebInspector.UIString));
+            updateDataAndColumn(6, (cookie.httpOnly ? "\u2713" : "")); // Checkmark
+            updateDataAndColumn(7, (cookie.secure ? "\u2713" : "")); // Checkmark
+
+            var node = new WebInspector.DataGridNode(data, false);
+            node.cookie = cookie;
+            node.selectable = true;
+            nodes.push(node);
+        }
+
+        var totalColumnWidths = 0;
+        for (var columnIdentifier in columns)
+            totalColumnWidths += columns[columnIdentifier].width;
+
+        // Enforce the Value column (the 2nd column) to be a max of 33%
+        // tweaking the raw total width because may massively outshadow the others
+        var valueColumnWidth = columns[1].width;
+        if (valueColumnWidth / totalColumnWidths > 0.33) {
+            totalColumnWidths -= valueColumnWidth;
+            totalColumnWidths *= 1.33;
+            columns[1].width = totalColumnWidths * 0.33;
+        }
+
+        // Calculate the percentage width for the columns.
+        const minimumPrecent = 6;
+        var recoupPercent = 0;
+        for (var columnIdentifier in columns) {
+            var width = columns[columnIdentifier].width;
+            width = Math.round((width / totalColumnWidths) * 100);
+            if (width < minimumPrecent) {
+                recoupPercent += (minimumPrecent - width);
+                width = minimumPrecent;
+            }
+            columns[columnIdentifier].width = width;
+        }
+
+        // Enforce the minimum percentage width. (need to narrow total percentage due to earlier additions)
+        while (recoupPercent > 0) {
+            for (var columnIdentifier in columns) {
+                if (columns[columnIdentifier].width > minimumPrecent) {
+                    --columns[columnIdentifier].width;
+                    --recoupPercent;
+                    if (!recoupPercent)
+                        break;
+                }
+            }
+        }
+
+        for (var columnIdentifier in columns)
+            columns[columnIdentifier].width += "%";
+
+        var dataGrid = new WebInspector.DataGrid(columns);
+        var length = nodes.length;
+        for (var i = 0; i < length; ++i)
+            dataGrid.appendChild(nodes[i]);
+        if (length > 0)
+            nodes[0].selected = true;
+
+        return dataGrid;
+    },
+
+    simpleDataGridForCookies: function(cookies)
+    {
+        if (!cookies.length)
+            return null;
+
+        var columns = {};
+        columns[0] = {};
+        columns[1] = {};
+        columns[0].title = WebInspector.UIString("Name");
+        columns[0].width = columns[0].title.length;
+        columns[1].title = WebInspector.UIString("Value");
+        columns[1].width = columns[1].title.length;
+
+        var nodes = [];
+        for (var i = 0; i < cookies.length; ++i) {
+            var cookie = cookies[i];
+            var data = {};
+
+            var name = cookie.name;
+            data[0] = name;
+            if (name.length > columns[0].width)
+                columns[0].width = name.length;
+
+            var value = cookie.value;
+            data[1] = value;
+            if (value.length > columns[1].width)
+                columns[1].width = value.length;
+
+            var node = new WebInspector.DataGridNode(data, false);
+            node.selectable = true;
+            nodes.push(node);
+        }
+
+        var totalColumnWidths = columns[0].width + columns[1].width;
+        var width = Math.round((columns[0].width * 100) / totalColumnWidths);
+        const minimumPrecent = 20;
+        if (width < minimumPrecent)
+            width = minimumPrecent;
+        if (width > 100 - minimumPrecent)
+            width = 100 - minimumPrecent;
+        columns[0].width = width;
+        columns[1].width = 100 - width;
+        columns[0].width += "%";
+        columns[1].width += "%";
+
+        var dataGrid = new WebInspector.DataGrid(columns);
+        var length = nodes.length;
+        for (var i = 0; i < length; ++i)
+            dataGrid.appendChild(nodes[i]);
+        if (length > 0)
+            nodes[0].selected = true;
+
+        return dataGrid;
+    },
+
+    _deleteButtonClicked: function(event)
+    {
+        if (!this._dataGrid)
+            return;
+
+        var cookie = this._dataGrid.selectedNode.cookie;
+        InspectorController.deleteCookie(cookie.name);
+        this.update();
+    },
+
+    _refreshButtonClicked: function(event)
+    {
+        this.update();
+    }
+}
+
+WebInspector.CookieItemsView.prototype.__proto__ = WebInspector.View.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMAgent.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMAgent.js
new file mode 100644
index 0000000..47c8041
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMAgent.js
@@ -0,0 +1,650 @@
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * 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.
+ */
+
+WebInspector.DOMNode = function(doc, payload) {
+    this.ownerDocument = doc;
+
+    this.id = payload.id;
+    this.nodeType = payload.nodeType;
+    this.nodeName = payload.nodeName;
+    this._nodeValue = payload.nodeValue;
+    this.textContent = this.nodeValue;
+
+    this.attributes = [];
+    this._attributesMap = {};
+    if (payload.attributes)
+        this._setAttributesPayload(payload.attributes);
+
+    this._childNodeCount = payload.childNodeCount;
+    this.children = null;
+
+    this.nextSibling = null;
+    this.prevSibling = null;
+    this.firstChild = null;
+    this.lastChild = null;
+    this.parentNode = null;
+
+    if (payload.children)
+        this._setChildrenPayload(payload.children);
+
+    this._computedStyle = null;
+    this.style = null;
+    this._matchedCSSRules = [];
+
+    if (this.nodeType == Node.ELEMENT_NODE) {
+        if (this.nodeName == "HTML")
+            this.ownerDocument.documentElement = this;
+        if (this.nodeName == "BODY")
+            this.ownerDocument.body = this;
+    }
+}
+
+WebInspector.DOMNode.prototype = {
+    hasAttributes: function()
+    {
+        return this.attributes.length > 0;
+    },
+
+    hasChildNodes: function()  {
+        return this._childNodeCount > 0;
+    },
+
+    get nodeValue() {
+        return this._nodeValue;
+    },
+
+    set nodeValue(value) {
+        if (this.nodeType != Node.TEXT_NODE)
+            return;
+        var self = this;
+        var callback = function()
+        {
+            self._nodeValue = value;
+            self.textContent = value;
+        };
+        this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, callback);
+    },
+
+    getAttribute: function(name)
+    {
+        var attr = this._attributesMap[name];
+        return attr ? attr.value : undefined;
+    },
+
+    setAttribute: function(name, value)
+    {
+        var self = this;
+        var callback = function()
+        {
+            var attr = self._attributesMap[name];
+            if (attr)
+                attr.value = value;
+            else
+                attr = self._addAttribute(name, value);
+        };
+        this.ownerDocument._domAgent.setAttributeAsync(this, name, value, callback);
+    },
+
+    removeAttribute: function(name)
+    {
+        var self = this;
+        var callback = function()
+        {
+            delete self._attributesMap[name];
+            for (var i = 0;  i < self.attributes.length; ++i) {
+                if (self.attributes[i].name == name) {
+                    self.attributes.splice(i, 1);
+                    break;
+                }
+            }
+        };
+        this.ownerDocument._domAgent.removeAttributeAsync(this, name, callback);
+    },
+
+    _setAttributesPayload: function(attrs)
+    {
+        for (var i = 0; i < attrs.length; i += 2)
+            this._addAttribute(attrs[i], attrs[i + 1]);
+    },
+
+    _insertChild: function(prev, payload)
+    {
+        var node = new WebInspector.DOMNode(this.ownerDocument, payload);
+        if (!prev)
+            // First node
+            this.children = [ node ];
+        else
+            this.children.splice(this.children.indexOf(prev) + 1, 0, node);
+        this._renumber();
+        return node;
+    },
+
+    removeChild_: function(node)
+    {
+        this.children.splice(this.children.indexOf(node), 1);
+        node.parentNode = null;
+        this._renumber();
+    },
+
+    _setChildrenPayload: function(payloads)
+    {
+        this.children = [];
+        for (var i = 0; i < payloads.length; ++i) {
+            var payload = payloads[i];
+            var node = new WebInspector.DOMNode(this.ownerDocument, payload);
+            this.children.push(node);
+        }
+        this._renumber();
+    },
+
+    _renumber: function()
+    {
+        this._childNodeCount = this.children.length;
+        if (this._childNodeCount == 0) {
+            this.firstChild = null;
+            this.lastChild = null;
+            return;
+        }
+        this.firstChild = this.children[0];
+        this.lastChild = this.children[this._childNodeCount - 1];
+        for (var i = 0; i < this._childNodeCount; ++i) {
+            var child = this.children[i];
+            child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
+            child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null;
+            child.parentNode = this;
+        }
+    },
+
+    _addAttribute: function(name, value)
+    {
+        var attr = {
+            "name": name,
+            "value": value,
+            "_node": this
+        };
+        this._attributesMap[name] = attr;
+        this.attributes.push(attr);
+    },
+
+    _setStyles: function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules)
+    {
+        this._computedStyle = new WebInspector.CSSStyleDeclaration(computedStyle);
+        this.style = new WebInspector.CSSStyleDeclaration(inlineStyle);
+
+        for (var name in styleAttributes) {
+            if (this._attributesMap[name])
+                this._attributesMap[name].style = new WebInspector.CSSStyleDeclaration(styleAttributes[name]);
+        }
+
+        this._matchedCSSRules = [];
+        for (var i = 0; i < matchedCSSRules.length; i++)
+            this._matchedCSSRules.push(WebInspector.CSSStyleDeclaration.parseRule(matchedCSSRules[i]));
+    },
+
+    _clearStyles: function()
+    {
+        this.computedStyle = null;
+        this.style = null;
+        for (var name in this._attributesMap)
+            this._attributesMap[name].style = null;
+        this._matchedCSSRules = null;
+    }
+}
+
+WebInspector.DOMDocument = function(domAgent, defaultView, payload)
+{
+    WebInspector.DOMNode.call(this, this, payload);
+    this._listeners = {};
+    this._domAgent = domAgent;
+    this.defaultView = defaultView;
+}
+
+WebInspector.DOMDocument.prototype = {
+
+    addEventListener: function(name, callback)
+    {
+        var listeners = this._listeners[name];
+        if (!listeners) {
+            listeners = [];
+            this._listeners[name] = listeners;
+        }
+        listeners.push(callback);
+    },
+
+    removeEventListener: function(name, callback)
+    {
+        var listeners = this._listeners[name];
+        if (!listeners)
+            return;
+
+        var index = listeners.indexOf(callback);
+        if (index != -1)
+            listeners.splice(index, 1);
+    },
+
+    _fireDomEvent: function(name, event)
+    {
+        var listeners = this._listeners[name];
+        if (!listeners)
+            return;
+
+        for (var i = 0; i < listeners.length; ++i) {
+            var listener = listeners[i];
+            listener.call(this, event);
+        }
+    }
+}
+
+WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;
+
+
+WebInspector.DOMWindow = function(domAgent)
+{
+    this._domAgent = domAgent;
+}
+
+WebInspector.DOMWindow.prototype = {
+    get document()
+    {
+        return this._domAgent.document;
+    },
+
+    get Node()
+    {
+        return WebInspector.DOMNode;
+    },
+
+    get Element()
+    {
+        return WebInspector.DOMNode;
+    },
+
+    Object: function()
+    {
+    },
+
+    getComputedStyle: function(node)
+    {
+        return node._computedStyle;
+    },
+
+    getMatchedCSSRules: function(node, pseudoElement, authorOnly)
+    {
+        return node._matchedCSSRules;
+    }
+}
+
+WebInspector.DOMAgent = function() {
+    this._window = new WebInspector.DOMWindow(this);
+    this._idToDOMNode = null;
+    this.document = null;
+
+    // TODO: update ElementsPanel to not track embedded iframes - it is already being handled
+    // in the agent backend.
+
+    // Whitespace is ignored in InspectorDOMAgent already -> no need to filter.
+    // TODO: Either remove all of its usages or push value into the agent backend.
+    Preferences.ignoreWhitespace = false;
+}
+
+WebInspector.DOMAgent.prototype = {
+    get domWindow()
+    {
+        return this._window;
+    },
+
+    getChildNodesAsync: function(parent, callback)
+    {
+        var children = parent.children;
+        if (children) {
+            callback(children);
+            return;
+        }
+        function mycallback() {
+            callback(parent.children);
+        }
+        var callId = WebInspector.Callback.wrap(mycallback);
+        InspectorController.getChildNodes(callId, parent.id);
+    },
+
+    setAttributeAsync: function(node, name, value, callback)
+    {
+        var mycallback = this._didApplyDomChange.bind(this, node, callback);
+        InspectorController.setAttribute(WebInspector.Callback.wrap(mycallback), node.id, name, value);
+    },
+
+    removeAttributeAsync: function(node, name, callback)
+    {
+        var mycallback = this._didApplyDomChange.bind(this, node, callback);
+        InspectorController.removeAttribute(WebInspector.Callback.wrap(mycallback), node.id, name);
+    },
+
+    setTextNodeValueAsync: function(node, text, callback)
+    {
+        var mycallback = this._didApplyDomChange.bind(this, node, callback);
+        InspectorController.setTextNodeValue(WebInspector.Callback.wrap(mycallback), node.id, text);
+    },
+
+    _didApplyDomChange: function(node, callback, success)
+    {
+        if (!success)
+            return;
+        callback();
+        // TODO(pfeldman): Fix this hack.
+        var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node);
+        if (elem) {
+            elem._updateTitle();
+        }
+    },
+
+    _attributesUpdated: function(nodeId, attrsArray)
+    {
+        var node = this._idToDOMNode[nodeId];
+        node._setAttributesPayload(attrsArray);
+    },
+
+    nodeForId: function(nodeId) {
+        return this._idToDOMNode[nodeId];
+    },
+
+    _setDocument: function(payload)
+    {
+        this.document = new WebInspector.DOMDocument(this, this._window, payload);
+        this._idToDOMNode = {};
+        this._idToDOMNode[payload.id] = this.document;
+        this._bindNodes(this.document.children);
+        WebInspector.panels.elements.reset();
+    },
+    
+    _setDetachedRoot: function(payload)
+    {
+        var root = new WebInspector.DOMNode(this.document, payload);
+        this._idToDOMNode[payload.id] = root;
+    },
+
+    _setChildNodes: function(parentId, payloads)
+    {
+        var parent = this._idToDOMNode[parentId];
+        parent._setChildrenPayload(payloads);
+        this._bindNodes(parent.children);
+    },
+
+    _bindNodes: function(children)
+    {
+        for (var i = 0; i < children.length; ++i) {
+            var child = children[i];
+            this._idToDOMNode[child.id] = child;
+            if (child.children)
+                this._bindNodes(child.children);
+        }
+    },
+
+    _childNodeCountUpdated: function(nodeId, newValue)
+    {
+        var node = this._idToDOMNode[nodeId];
+        node._childNodeCount = newValue;
+        var outline = WebInspector.panels.elements.treeOutline;
+        var treeElement = outline.findTreeElement(node);
+        if (treeElement) {
+            treeElement.hasChildren = newValue;
+            treeElement.whitespaceIgnored = Preferences.ignoreWhitespace;
+        }
+    },
+
+    _childNodeInserted: function(parentId, prevId, payload)
+    {
+        var parent = this._idToDOMNode[parentId];
+        var prev = this._idToDOMNode[prevId];
+        var node = parent._insertChild(prev, payload);
+        this._idToDOMNode[node.id] = node;
+        var event = { target : node, relatedNode : parent };
+        this.document._fireDomEvent("DOMNodeInserted", event);
+    },
+
+    _childNodeRemoved: function(parentId, nodeId)
+    {
+        var parent = this._idToDOMNode[parentId];
+        var node = this._idToDOMNode[nodeId];
+        parent.removeChild_(node);
+        var event = { target : node, relatedNode : parent };
+        this.document._fireDomEvent("DOMNodeRemoved", event);
+        delete this._idToDOMNode[nodeId];
+    }
+}
+
+WebInspector.Cookies = {}
+
+WebInspector.Cookies.getCookiesAsync = function(callback)
+{
+    function mycallback(cookies, cookiesString) {
+        if (cookiesString)
+            callback(WebInspector.Cookies.buildCookiesFromString(cookiesString), false);
+        else
+            callback(cookies, true);
+    }
+    var callId = WebInspector.Callback.wrap(mycallback);
+    InspectorController.getCookies(callId);
+}
+
+WebInspector.Cookies.buildCookiesFromString = function(rawCookieString)
+{
+    var rawCookies = rawCookieString.split(/;\s*/);
+    var cookies = [];
+
+    if (!(/^\s*$/.test(rawCookieString))) {
+        for (var i = 0; i < rawCookies.length; ++i) {
+            var cookie = rawCookies[i];
+            var delimIndex = cookie.indexOf("=");
+            var name = cookie.substring(0, delimIndex);
+            var value = cookie.substring(delimIndex + 1);
+            var size = name.length + value.length;
+            cookies.push({ name: name, value: value, size: size });
+        }
+    }
+
+    return cookies;
+}
+
+WebInspector.CSSStyleDeclaration = function(payload)
+{
+    this.id = payload.id;
+    this.width = payload.width;
+    this.height = payload.height;
+    this.__disabledProperties = payload.__disabledProperties;
+    this.__disabledPropertyValues = payload.__disabledPropertyValues;
+    this.__disabledPropertyPriorities = payload.__disabledPropertyPriorities;
+    this.uniqueStyleProperties = payload.uniqueStyleProperties;
+    this._shorthandValues = payload.shorthandValues;
+    this._propertyMap = {};
+    this._longhandProperties = {};
+    this.length = payload.properties.length;
+
+    for (var i = 0; i < this.length; ++i) {
+        var property = payload.properties[i];
+        var name = property.name;
+        this[i] = name;
+        this._propertyMap[name] = property;
+    }
+
+    // Index longhand properties.
+    for (var i = 0; i < this.uniqueStyleProperties.length; ++i) {
+        var name = this.uniqueStyleProperties[i];
+        var property = this._propertyMap[name];
+        if (property.shorthand) {
+            var longhands = this._longhandProperties[property.shorthand];
+            if (!longhands) {
+                longhands = [];
+                this._longhandProperties[property.shorthand] = longhands;
+            }
+            longhands.push(name);
+        }
+    }
+}
+
+WebInspector.CSSStyleDeclaration.parseStyle = function(payload)
+{
+    return new WebInspector.CSSStyleDeclaration(payload);
+}
+
+WebInspector.CSSStyleDeclaration.parseRule = function(payload)
+{
+    var rule = {};
+    rule.id = payload.id;
+    rule.selectorText = payload.selectorText;
+    rule.style = new WebInspector.CSSStyleDeclaration(payload.style);
+    rule.style.parentRule = rule;
+    rule.isUserAgent = payload.isUserAgent;
+    rule.isUser = payload.isUser;
+    rule.isViaInspector = payload.isViaInspector;
+    if (payload.parentStyleSheet)
+        rule.parentStyleSheet = { href: payload.parentStyleSheet.href };
+
+    return rule;
+}
+
+WebInspector.CSSStyleDeclaration.prototype = {
+    getPropertyValue: function(name)
+    {
+        var property = this._propertyMap[name];
+        return property ? property.value : "";
+    },
+
+    getPropertyPriority: function(name)
+    {
+        var property = this._propertyMap[name];
+        return property ? property.priority : "";
+    },
+
+    getPropertyShorthand: function(name)
+    {
+        var property = this._propertyMap[name];
+        return property ? property.shorthand : "";
+    },
+
+    isPropertyImplicit: function(name)
+    {
+        var property = this._propertyMap[name];
+        return property ? property.implicit : "";
+    },
+
+    styleTextWithShorthands: function()
+    {
+        var cssText = "";
+        var foundProperties = {};
+        for (var i = 0; i < this.length; ++i) {
+            var individualProperty = this[i];
+            var shorthandProperty = this.getPropertyShorthand(individualProperty);
+            var propertyName = (shorthandProperty || individualProperty);
+
+            if (propertyName in foundProperties)
+                continue;
+
+            if (shorthandProperty) {
+                var value = this.getPropertyValue(shorthandProperty);
+                var priority = this.getShorthandPriority(shorthandProperty);
+            } else {
+                var value = this.getPropertyValue(individualProperty);
+                var priority = this.getPropertyPriority(individualProperty);
+            }
+
+            foundProperties[propertyName] = true;
+
+            cssText += propertyName + ": " + value;
+            if (priority)
+                cssText += " !" + priority;
+            cssText += "; ";
+        }
+
+        return cssText;
+    },
+
+    getLonghandProperties: function(name)
+    {
+        return this._longhandProperties[name] || [];
+    },
+
+    getShorthandValue: function(shorthandProperty)
+    {
+        return this._shorthandValues[shorthandProperty];
+    },
+
+    getShorthandPriority: function(shorthandProperty)
+    {
+        var priority = this.getPropertyPriority(shorthandProperty);
+        if (priority)
+            return priority;
+
+        var longhands = this._longhandProperties[shorthandProperty];
+        return longhands ? this.getPropertyPriority(longhands[0]) : null;
+    }
+}
+
+WebInspector.attributesUpdated = function()
+{
+    this.domAgent._attributesUpdated.apply(this.domAgent, arguments);
+}
+
+WebInspector.setDocument = function()
+{
+    this.domAgent._setDocument.apply(this.domAgent, arguments);
+}
+
+WebInspector.setDetachedRoot = function()
+{
+    this.domAgent._setDetachedRoot.apply(this.domAgent, arguments);
+}
+
+WebInspector.setChildNodes = function()
+{
+    this.domAgent._setChildNodes.apply(this.domAgent, arguments);
+}
+
+WebInspector.childNodeCountUpdated = function()
+{
+    this.domAgent._childNodeCountUpdated.apply(this.domAgent, arguments);
+}
+
+WebInspector.childNodeInserted = function()
+{
+    this.domAgent._childNodeInserted.apply(this.domAgent, arguments);
+}
+
+WebInspector.childNodeRemoved = function()
+{
+    this.domAgent._childNodeRemoved.apply(this.domAgent, arguments);
+}
+
+WebInspector.didGetCookies = WebInspector.Callback.processCallback;
+WebInspector.didGetChildNodes = WebInspector.Callback.processCallback;
+WebInspector.didPerformSearch = WebInspector.Callback.processCallback;
+WebInspector.didApplyDomChange = WebInspector.Callback.processCallback;
+WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback;
+WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorage.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorage.js
new file mode 100644
index 0000000..5207b69
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorage.js
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008 Nokia Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 "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 APPLE OR ITS 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.
+ */
+
+WebInspector.DOMStorage = function(domStorage, domain, isLocalStorage)
+{
+    this.domStorage = domStorage;
+    this.domain = domain;
+    this.isLocalStorage = isLocalStorage;
+}
+
+WebInspector.DOMStorage.prototype = {
+    get domStorage()
+    {
+        return this._domStorage;
+    },
+
+    set domStorage(x)
+    {
+        if (this._domStorage === x)
+            return;
+        this._domStorage = x;
+    },
+
+    get domain()
+    {
+        return this._domain;
+    },
+
+    set domain(x)
+    {
+        if (this._domain === x)
+            return;
+        this._domain = x;
+    },
+    
+    get isLocalStorage()
+    {
+        return this._isLocalStorage;
+    },
+    
+    set isLocalStorage(x)
+    {
+        if (this._isLocalStorage === x)
+            return;
+        this._isLocalStorage = x;
+    }
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageDataGrid.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageDataGrid.js
new file mode 100644
index 0000000..efdd090c
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageDataGrid.js
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2009 Nokia Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *        notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.DOMStorageDataGrid = function(columns)
+{
+    WebInspector.DataGrid.call(this, columns);
+    this.dataTableBody.addEventListener("dblclick", this._ondblclick.bind(this), false);
+}
+
+WebInspector.DOMStorageDataGrid.prototype = {
+    _ondblclick: function(event)
+    {
+        if (this._editing)
+            return;
+        if (this._editingNode)
+            return;
+        this._startEditing(event);
+    },
+
+    _startEditingColumnOfDataGridNode: function(node, column)
+    {
+        this._editing = true;
+        this._editingNode = node;
+        this._editingNode.select();
+        WebInspector.panels.storage._unregisterStorageEventListener();
+
+        var element = this._editingNode._element.children[column];
+        WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent);
+        window.getSelection().setBaseAndExtent(element, 0, element, 1);
+    },
+
+    _startEditing: function(event)
+    {
+        var element = event.target.enclosingNodeOrSelfWithNodeName("td");
+        if (!element)
+            return;
+
+        this._editingNode = this.dataGridNodeFromEvent(event);
+        if (!this._editingNode) {
+            if (!this.creationNode)
+                return;
+            this._editingNode = this.creationNode;
+        }
+
+        // Force editing the "Key" column when editing the creation node
+        if (this._editingNode.isCreationNode)
+            return this._startEditingColumnOfDataGridNode(this._editingNode, 0);
+
+        this._editing = true;
+        WebInspector.panels.storage._unregisterStorageEventListener();
+        WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent);
+        window.getSelection().setBaseAndExtent(element, 0, element, 1);
+    },
+
+    _editingCommitted: function(element, newText, oldText, context, moveDirection)
+    {
+        var columnIdentifier = (element.hasStyleClass("0-column") ? 0 : 1);
+        var textBeforeEditing = this._editingNode.data[columnIdentifier];
+        var currentEditingNode = this._editingNode;
+
+        function moveToNextIfNeeded(wasChange) {
+            if (!moveDirection)
+                return;
+
+            if (moveDirection === "forward") {
+                if (currentEditingNode.isCreationNode && columnIdentifier === 0 && !wasChange)
+                    return;
+
+                if (columnIdentifier === 0)
+                    return this._startEditingColumnOfDataGridNode(currentEditingNode, 1);
+
+                var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true);
+                if (nextDataGridNode)
+                    return this._startEditingColumnOfDataGridNode(nextDataGridNode, 0);
+                if (currentEditingNode.isCreationNode && wasChange) {
+                    addCreationNode(false);
+                    return this._startEditingColumnOfDataGridNode(this.creationNode, 0);
+                }
+                return;
+            }
+
+            if (moveDirection === "backward") {
+                if (columnIdentifier === 1)
+                    return this._startEditingColumnOfDataGridNode(currentEditingNode, 0);
+                    var nextDataGridNode = currentEditingNode.traversePreviousNode(true, null, true);
+
+                if (nextDataGridNode)
+                    return this._startEditingColumnOfDataGridNode(nextDataGridNode, 1);
+                return;
+            }
+        }
+
+        if (textBeforeEditing == newText) {
+            this._editingCancelled(element);
+            moveToNextIfNeeded.call(this, false);
+            return;
+        }
+
+        var domStorage = WebInspector.panels.storage.visibleView.domStorage.domStorage;
+        if (domStorage) {
+            if (columnIdentifier == 0) {
+                if (domStorage.getItem(newText) != null) {
+                    element.textContent = this._editingNode.data[0];
+                    this._editingCancelled(element);
+                    moveToNextIfNeeded.call(this, false);
+                    return;
+                }
+                domStorage.removeItem(this._editingNode.data[0]);
+                domStorage.setItem(newText, this._editingNode.data[1]);
+                this._editingNode.data[0] = newText;            
+            } else {
+                domStorage.setItem(this._editingNode.data[0], newText);
+                this._editingNode.data[1] = newText;
+            }
+        }
+
+        if (this._editingNode.isCreationNode)
+            this.addCreationNode(false);
+
+        this._editingCancelled(element);
+        moveToNextIfNeeded.call(this, true);
+    },
+
+    _editingCancelled: function(element, context)
+    {
+        delete this._editing;
+        this._editingNode = null;
+        WebInspector.panels.storage._registerStorageEventListener();
+    },
+
+    deleteSelectedRow: function()
+    {
+        var node = this.selectedNode;
+        if (this.selectedNode.isCreationNode)
+            return;
+
+        var domStorage = WebInspector.panels.storage.visibleView.domStorage.domStorage;
+        if (node && domStorage)
+            domStorage.removeItem(node.data[0]);
+    }
+}
+
+WebInspector.DOMStorageDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageItemsView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageItemsView.js
new file mode 100644
index 0000000..8617d60
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageItemsView.js
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2008 Nokia Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 APPLE INC. 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.
+ */
+
+WebInspector.DOMStorageItemsView = function(domStorage)
+{
+    WebInspector.View.call(this);
+
+    this.domStorage = domStorage;
+
+    this.element.addStyleClass("storage-view");
+    this.element.addStyleClass("table");
+
+    this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item");
+    this.deleteButton.visible = false;
+    this.deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);
+
+    this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
+    this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
+}
+
+WebInspector.DOMStorageItemsView.prototype = {
+    get statusBarItems()
+    {
+        return [this.refreshButton.element, this.deleteButton.element];
+    },
+
+    show: function(parentElement)
+    {
+        WebInspector.View.prototype.show.call(this, parentElement);
+        this.update();
+    },
+
+    hide: function()
+    {
+        WebInspector.View.prototype.hide.call(this);
+        this.deleteButton.visible = false;
+    },
+
+    update: function()
+    {
+        this.element.removeChildren();
+        var hasDOMStorage = this.domStorage;
+        if (hasDOMStorage)
+            hasDOMStorage = this.domStorage.domStorage;
+
+        if (hasDOMStorage) {
+            var dataGrid = WebInspector.panels.storage.dataGridForDOMStorage(this.domStorage.domStorage);
+            if (!dataGrid)
+                hasDOMStorage = 0;
+            else {
+                this._dataGrid = dataGrid;
+                this.element.appendChild(dataGrid.element);
+                this._dataGrid.updateWidths();
+                this.deleteButton.visible = true;
+            }
+        }
+
+        if (!hasDOMStorage) {
+            var emptyMsgElement = document.createElement("div");
+            emptyMsgElement.className = "storage-table-empty";
+            if (this.domStorage)
+            emptyMsgElement.textContent = WebInspector.UIString("This storage is empty.");
+            this.element.appendChild(emptyMsgElement);
+            this._dataGrid = null;
+            this.deleteButton.visible = false;
+        }
+    },
+    
+    resize: function()
+    {
+        if (this._dataGrid)
+            this._dataGrid.updateWidths();
+    },
+
+    _deleteButtonClicked: function(event)
+    {
+        if (this._dataGrid) {
+            this._dataGrid.deleteSelectedRow();
+            
+            this.show();
+        }
+    },
+
+    _refreshButtonClicked: function(event)
+    {
+        this.update();
+    }
+}
+
+WebInspector.DOMStorageItemsView.prototype.__proto__ = WebInspector.View.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DataGrid.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DataGrid.js
new file mode 100644
index 0000000..ce61548
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DataGrid.js
@@ -0,0 +1,1041 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *        notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.DataGrid = function(columns)
+{
+    this.element = document.createElement("div");
+    this.element.className = "data-grid";
+    this.element.tabIndex = 0;
+    this.element.addEventListener("keydown", this._keyDown.bind(this), false);
+
+    this._headerTable = document.createElement("table");
+    this._headerTable.className = "header";
+
+    this._dataTable = document.createElement("table");
+    this._dataTable.className = "data";
+
+    this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true);
+    this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true);
+
+    this.aligned = {};
+
+    var scrollContainer = document.createElement("div");
+    scrollContainer.className = "data-container";
+    scrollContainer.appendChild(this._dataTable);
+
+    this.element.appendChild(this._headerTable);
+    this.element.appendChild(scrollContainer);
+
+    var headerRow = document.createElement("tr");
+    var columnGroup = document.createElement("colgroup");
+    var columnCount = 0;
+
+    for (var columnIdentifier in columns) {
+        var column = columns[columnIdentifier];
+        if (column.disclosure)
+            this.disclosureColumnIdentifier = columnIdentifier;
+
+        var col = document.createElement("col");
+        if (column.width)
+            col.style.width = column.width;
+        columnGroup.appendChild(col);
+
+        var cell = document.createElement("th");
+        cell.className = columnIdentifier + "-column";
+        cell.columnIdentifier = columnIdentifier;
+
+        var div = document.createElement("div");
+        div.textContent = column.title;
+        cell.appendChild(div);
+
+        if (column.sort) {
+            cell.addStyleClass("sort-" + column.sort);
+            this._sortColumnCell = cell;
+        }
+
+        if (column.sortable) {
+            cell.addEventListener("click", this._clickInHeaderCell.bind(this), false);
+            cell.addStyleClass("sortable");
+        }
+
+        if (column.aligned) {
+            cell.addStyleClass(column.aligned);
+            this.aligned[columnIdentifier] = column.aligned;
+        }
+
+        headerRow.appendChild(cell);
+
+        ++columnCount;
+    }
+
+    columnGroup.span = columnCount;
+
+    var cell = document.createElement("th");
+    cell.className = "corner";
+    headerRow.appendChild(cell);
+
+    this._headerTableColumnGroup = columnGroup;
+    this._headerTable.appendChild(this._headerTableColumnGroup);
+    this.headerTableBody.appendChild(headerRow);
+
+    var fillerRow = document.createElement("tr");
+    fillerRow.className = "filler";
+
+    for (var i = 0; i < columnCount; ++i) {
+        var cell = document.createElement("td");
+        fillerRow.appendChild(cell);
+    }
+    
+    this._dataTableColumnGroup = columnGroup.cloneNode(true);
+    this._dataTable.appendChild(this._dataTableColumnGroup);
+    this.dataTableBody.appendChild(fillerRow);
+
+    this.columns = columns || {};
+    this.children = [];
+    this.selectedNode = null;
+    this.expandNodesWhenArrowing = false;
+    this.root = true;
+    this.hasChildren = false;
+    this.expanded = true;
+    this.revealed = true;
+    this.selected = false;
+    this.dataGrid = this;
+    this.indentWidth = 15;
+    this.resizers = [];
+    this.columnWidthsInitialized = false;
+}
+
+WebInspector.DataGrid.prototype = {
+    get sortColumnIdentifier()
+    {
+        if (!this._sortColumnCell)
+            return null;
+        return this._sortColumnCell.columnIdentifier;
+    },
+
+    get sortOrder()
+    {
+        if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending"))
+            return "ascending";
+        if (this._sortColumnCell.hasStyleClass("sort-descending"))
+            return "descending";
+        return null;
+    },
+
+    get headerTableBody()
+    {
+        if ("_headerTableBody" in this)
+            return this._headerTableBody;
+
+        this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0];
+        if (!this._headerTableBody) {
+            this._headerTableBody = this.element.ownerDocument.createElement("tbody");
+            this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot);
+        }
+
+        return this._headerTableBody;
+    },
+
+    get dataTableBody()
+    {
+        if ("_dataTableBody" in this)
+            return this._dataTableBody;
+
+        this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0];
+        if (!this._dataTableBody) {
+            this._dataTableBody = this.element.ownerDocument.createElement("tbody");
+            this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot);
+        }
+
+        return this._dataTableBody;
+    },
+ 
+    // Updates the widths of the table, including the positions of the column
+    // resizers.
+    //
+    // IMPORTANT: This function MUST be called once after the element of the
+    // DataGrid is attached to its parent element and every subsequent time the
+    // width of the parent element is changed in order to make it possible to
+    // resize the columns.
+    //
+    // If this function is not called after the DataGrid is attached to its
+    // parent element, then the DataGrid's columns will not be resizable.
+    updateWidths: function()
+    {
+        var headerTableColumns = this._headerTableColumnGroup.children;
+        
+        var left = 0;
+        var tableWidth = this._dataTable.offsetWidth;
+        var numColumns = headerTableColumns.length;
+        
+        if (!this.columnWidthsInitialized) {
+            // Give all the columns initial widths now so that during a resize,
+            // when the two columns that get resized get a percent value for
+            // their widths, all the other columns already have percent values
+            // for their widths.
+            for (var i = 0; i < numColumns; i++) {
+                var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth;
+                var percentWidth = ((columnWidth / tableWidth) * 100) + "%";
+                this._headerTableColumnGroup.children[i].style.width = percentWidth;
+                this._dataTableColumnGroup.children[i].style.width = percentWidth;
+            }
+            this.columnWidthsInitialized = true;
+        }
+        
+        // Make n - 1 resizers for n columns. 
+        for (var i = 0; i < numColumns - 1; i++) {
+            var resizer = this.resizers[i];
+
+            if (!resizer) {
+                // This is the first call to updateWidth, so the resizers need
+                // to be created.
+                resizer = document.createElement("div");
+                resizer.addStyleClass("data-grid-resizer");
+                // This resizer is associated with the column to its right.
+                resizer.rightNeighboringColumnID = i + 1;
+                resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false);
+                this.element.appendChild(resizer);
+                this.resizers[i] = resizer;
+            }
+
+            // Get the width of the cell in the first (and only) row of the
+            // header table in order to determine the width of the column, since
+            // it is not possible to query a column for its width.
+            left += this.headerTableBody.rows[0].cells[i].offsetWidth;
+            
+            resizer.style.left = left + "px";
+        }
+    },
+
+    addCreationNode: function(hasChildren)
+    {
+        if (this.creationNode)
+            this.creationNode.makeNormal();
+
+        var emptyData = {};
+        for (var column in this.columns)
+            emptyData[column] = '';
+        this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren);
+        this.appendChild(this.creationNode);
+    },
+
+    appendChild: function(child)
+    {
+        this.insertChild(child, this.children.length);
+    },
+
+    insertChild: function(child, index)
+    {
+        if (!child)
+            throw("insertChild: Node can't be undefined or null.");
+        if (child.parent === this)
+            throw("insertChild: Node is already a child of this node.");
+
+        if (child.parent)
+            child.parent.removeChild(child);
+
+        this.children.splice(index, 0, child);
+        this.hasChildren = true;
+
+        child.parent = this;
+        child.dataGrid = this.dataGrid;
+        child._recalculateSiblings(index);
+
+        delete child._depth;
+        delete child._revealed;
+        delete child._attached;
+
+        var current = child.children[0];
+        while (current) {
+            current.dataGrid = this.dataGrid;
+            delete current._depth;
+            delete current._revealed;
+            delete current._attached;
+            current = current.traverseNextNode(false, child, true);
+        }
+
+        if (this.expanded)
+            child._attach();
+    },
+
+    removeChild: function(child)
+    {
+        if (!child)
+            throw("removeChild: Node can't be undefined or null.");
+        if (child.parent !== this)
+            throw("removeChild: Node is not a child of this node.");
+
+        child.deselect();
+
+        this.children.remove(child, true);
+
+        if (child.previousSibling)
+            child.previousSibling.nextSibling = child.nextSibling;
+        if (child.nextSibling)
+            child.nextSibling.previousSibling = child.previousSibling;
+
+        child.dataGrid = null;
+        child.parent = null;
+        child.nextSibling = null;
+        child.previousSibling = null;
+
+        if (this.children.length <= 0)
+            this.hasChildren = false;
+    },
+
+    removeChildren: function()
+    {
+        for (var i = 0; i < this.children.length; ++i) {
+            var child = this.children[i];
+            child.deselect();
+            child._detach();
+
+            child.dataGrid = null;
+            child.parent = null;
+            child.nextSibling = null;
+            child.previousSibling = null;
+        }
+
+        this.children = [];
+        this.hasChildren = false;
+    },
+
+    removeChildrenRecursive: function()
+    {
+        var childrenToRemove = this.children;
+
+        var child = this.children[0];
+        while (child) {
+            if (child.children.length)
+                childrenToRemove = childrenToRemove.concat(child.children);
+            child = child.traverseNextNode(false, this, true);
+        }
+
+        for (var i = 0; i < childrenToRemove.length; ++i) {
+            var child = childrenToRemove[i];
+            child.deselect();
+            child._detach();
+
+            child.children = [];
+            child.dataGrid = null;
+            child.parent = null;
+            child.nextSibling = null;
+            child.previousSibling = null;
+        }
+
+        this.children = [];
+    },
+
+    handleKeyEvent: function(event)
+    {
+        if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey)
+            return false;
+
+        var handled = false;
+        var nextSelectedNode;
+        if (event.keyIdentifier === "Up" && !event.altKey) {
+            nextSelectedNode = this.selectedNode.traversePreviousNode(true);
+            while (nextSelectedNode && !nextSelectedNode.selectable)
+                nextSelectedNode = nextSelectedNode.traversePreviousNode(!this.expandTreeNodesWhenArrowing);
+            handled = nextSelectedNode ? true : false;
+        } else if (event.keyIdentifier === "Down" && !event.altKey) {
+            nextSelectedNode = this.selectedNode.traverseNextNode(true);
+            while (nextSelectedNode && !nextSelectedNode.selectable)
+                nextSelectedNode = nextSelectedNode.traverseNextNode(!this.expandTreeNodesWhenArrowing);
+            handled = nextSelectedNode ? true : false;
+        } else if (event.keyIdentifier === "Left") {
+            if (this.selectedNode.expanded) {
+                if (event.altKey)
+                    this.selectedNode.collapseRecursively();
+                else
+                    this.selectedNode.collapse();
+                handled = true;
+            } else if (this.selectedNode.parent && !this.selectedNode.parent.root) {
+                handled = true;
+                if (this.selectedNode.parent.selectable) {
+                    nextSelectedNode = this.selectedNode.parent;
+                    handled = nextSelectedNode ? true : false;
+                } else if (this.selectedNode.parent)
+                    this.selectedNode.parent.collapse();
+            }
+        } else if (event.keyIdentifier === "Right") {
+            if (!this.selectedNode.revealed) {
+                this.selectedNode.reveal();
+                handled = true;
+            } else if (this.selectedNode.hasChildren) {
+                handled = true;
+                if (this.selectedNode.expanded) {
+                    nextSelectedNode = this.selectedNode.children[0];
+                    handled = nextSelectedNode ? true : false;
+                } else {
+                    if (event.altKey)
+                        this.selectedNode.expandRecursively();
+                    else
+                        this.selectedNode.expand();
+                }
+            }
+        }
+
+        if (nextSelectedNode) {
+            nextSelectedNode.reveal();
+            nextSelectedNode.select();
+        }
+
+        if (handled) {
+            event.preventDefault();
+            event.stopPropagation();
+        }
+
+        return handled;
+    },
+
+    expand: function()
+    {
+        // This is the root, do nothing.
+    },
+
+    collapse: function()
+    {
+        // This is the root, do nothing.
+    },
+
+    reveal: function()
+    {
+        // This is the root, do nothing.
+    },
+
+    dataGridNodeFromEvent: function(event)
+    {
+        var rowElement = event.target.enclosingNodeOrSelfWithNodeName("tr");
+        return rowElement._dataGridNode;
+    },
+
+    dataGridNodeFromPoint: function(x, y)
+    {
+        var node = this._dataTable.ownerDocument.elementFromPoint(x, y);
+        var rowElement = node.enclosingNodeOrSelfWithNodeName("tr");
+        return rowElement._dataGridNode;
+    },
+
+    _keyDown: function(event)
+    {
+        this.handleKeyEvent(event);
+    },
+
+    _clickInHeaderCell: function(event)
+    {
+        var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
+        if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable"))
+            return;
+
+        var sortOrder = this.sortOrder;
+
+        if (this._sortColumnCell) {
+            this._sortColumnCell.removeStyleClass("sort-ascending");
+            this._sortColumnCell.removeStyleClass("sort-descending");
+        }
+
+        if (cell == this._sortColumnCell) {
+            if (sortOrder == "ascending")
+                sortOrder = "descending";
+            else
+                sortOrder = "ascending";
+        }
+
+        this._sortColumnCell = cell;
+
+        cell.addStyleClass("sort-" + sortOrder);
+
+        this.dispatchEventToListeners("sorting changed");
+    },
+
+    _mouseDownInDataTable: function(event)
+    {
+        var gridNode = this.dataGridNodeFromEvent(event);
+        if (!gridNode || !gridNode.selectable)
+            return;
+
+        if (gridNode.isEventWithinDisclosureTriangle(event))
+            return;
+
+        if (event.metaKey) {
+            if (gridNode.selected)
+                gridNode.deselect();
+            else
+                gridNode.select();
+        } else
+            gridNode.select();
+    },
+
+    _clickInDataTable: function(event)
+    {
+        var gridNode = this.dataGridNodeFromEvent(event);
+        if (!gridNode || !gridNode.hasChildren)
+            return;
+
+        if (!gridNode.isEventWithinDisclosureTriangle(event))
+            return;
+
+        if (gridNode.expanded) {
+            if (event.altKey)
+                gridNode.collapseRecursively();
+            else
+                gridNode.collapse();
+        } else {
+            if (event.altKey)
+                gridNode.expandRecursively();
+            else
+                gridNode.expand();
+        }
+    },
+    
+    _startResizerDragging: function(event)
+    {
+        this.currentResizer = event.target;
+        if (!this.currentResizer.rightNeighboringColumnID)
+            return;
+        WebInspector.elementDragStart(this.lastResizer, this._resizerDragging.bind(this),
+            this._endResizerDragging.bind(this), event, "col-resize");
+    },
+    
+    _resizerDragging: function(event)
+    {
+        var resizer = this.currentResizer;
+        if (!resizer)
+            return;
+        
+        // Constrain the dragpoint to be within the containing div of the
+        // datagrid.
+        var dragPoint = event.clientX - this.element.totalOffsetLeft;
+        // Constrain the dragpoint to be within the space made up by the
+        // column directly to the left and the column directly to the right.
+        var leftEdgeOfPreviousColumn = 0;
+        var firstRowCells = this.headerTableBody.rows[0].cells;
+        for (var i = 0; i < resizer.rightNeighboringColumnID - 1; i++)
+            leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
+            
+        var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[resizer.rightNeighboringColumnID - 1].offsetWidth + firstRowCells[resizer.rightNeighboringColumnID].offsetWidth;
+        
+        // Give each column some padding so that they don't disappear.               
+        var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding;
+        var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding;
+        
+        dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum);
+        
+        resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px";
+        
+        var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%";
+        this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn;
+        this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn;
+        
+        var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%";
+        this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width =  percentRightColumn;
+        this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width = percentRightColumn;
+        
+        event.preventDefault();
+    },
+    
+    _endResizerDragging: function(event)
+    {
+        WebInspector.elementDragEnd(event);
+        this.currentResizer = null;
+    },
+    
+    ColumnResizePadding: 10,
+    
+    CenterResizerOverBorderAdjustment: 3,
+}
+
+WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype;
+
+WebInspector.DataGridNode = function(data, hasChildren)
+{
+    this._expanded = false;
+    this._selected = false;
+    this._shouldRefreshChildren = true;
+    this._data = data || {};
+    this.hasChildren = hasChildren || false;
+    this.children = [];
+    this.dataGrid = null;
+    this.parent = null;
+    this.previousSibling = null;
+    this.nextSibling = null;
+    this.disclosureToggleWidth = 10;
+}
+
+WebInspector.DataGridNode.prototype = {
+    selectable: true,
+
+    get element()
+    {
+        if (this._element)
+            return this._element;
+
+        if (!this.dataGrid)
+            return null;
+
+        this._element = document.createElement("tr");
+        this._element._dataGridNode = this;
+
+        if (this.hasChildren)
+            this._element.addStyleClass("parent");
+        if (this.expanded)
+            this._element.addStyleClass("expanded");
+        if (this.selected)
+            this._element.addStyleClass("selected");
+        if (this.revealed)
+            this._element.addStyleClass("revealed");
+
+        for (var columnIdentifier in this.dataGrid.columns) {
+            var cell = this.createCell(columnIdentifier);
+            this._element.appendChild(cell);
+        }
+
+        return this._element;
+    },
+
+    get data()
+    {
+        return this._data;
+    },
+
+    set data(x)
+    {
+        this._data = x || {};
+        this.refresh();
+    },
+
+    get revealed()
+    {
+        if ("_revealed" in this)
+            return this._revealed;
+
+        var currentAncestor = this.parent;
+        while (currentAncestor && !currentAncestor.root) {
+            if (!currentAncestor.expanded) {
+                this._revealed = false;
+                return false;
+            }
+
+            currentAncestor = currentAncestor.parent;
+        }
+
+        this._revealed = true;
+        return true;
+    },
+
+    set hasChildren(x)
+    {
+        if (this._hasChildren === x)
+            return;
+
+        this._hasChildren = x;
+
+        if (!this._element)
+            return;
+
+        if (this._hasChildren)
+        {
+            this._element.addStyleClass("parent");
+            if (this.expanded)
+                this._element.addStyleClass("expanded");
+        }
+        else
+        {
+            this._element.removeStyleClass("parent");
+            this._element.removeStyleClass("expanded");
+        }
+    },
+
+    get hasChildren()
+    {
+        return this._hasChildren;
+    },
+
+    set revealed(x)
+    {
+        if (this._revealed === x)
+            return;
+
+        this._revealed = x;
+
+        if (this._element) {
+            if (this._revealed)
+                this._element.addStyleClass("revealed");
+            else
+                this._element.removeStyleClass("revealed");
+        }
+
+        for (var i = 0; i < this.children.length; ++i)
+            this.children[i].revealed = x && this.expanded;
+    },
+
+    get depth()
+    {
+        if ("_depth" in this)
+            return this._depth;
+        if (this.parent && !this.parent.root)
+            this._depth = this.parent.depth + 1;
+        else
+            this._depth = 0;
+        return this._depth;
+    },
+
+    get shouldRefreshChildren()
+    {
+        return this._shouldRefreshChildren;
+    },
+
+    set shouldRefreshChildren(x)
+    {
+        this._shouldRefreshChildren = x;
+        if (x && this.expanded)
+            this.expand();
+    },
+
+    get selected()
+    {
+        return this._selected;
+    },
+
+    set selected(x)
+    {
+        if (x)
+            this.select();
+        else
+            this.deselect();
+    },
+
+    get expanded()
+    {
+        return this._expanded;
+    },
+
+    set expanded(x)
+    {
+        if (x)
+            this.expand();
+        else
+            this.collapse();
+    },
+
+    refresh: function()
+    {
+        if (!this._element || !this.dataGrid)
+            return;
+
+        this._element.removeChildren();
+
+        for (var columnIdentifier in this.dataGrid.columns) {
+            var cell = this.createCell(columnIdentifier);
+            this._element.appendChild(cell);
+        }
+    },
+
+    createCell: function(columnIdentifier)
+    {
+        var cell = document.createElement("td");
+        cell.className = columnIdentifier + "-column";
+
+        var alignment = this.dataGrid.aligned[columnIdentifier];
+        if (alignment)
+            cell.addStyleClass(alignment);
+
+        var div = document.createElement("div");
+        div.textContent = this.data[columnIdentifier];
+        cell.appendChild(div);
+
+        if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) {
+            cell.addStyleClass("disclosure");
+            if (this.depth)
+                cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px");
+        }
+
+        return cell;
+    },
+
+    // Share these functions with DataGrid. They are written to work with a DataGridNode this object.
+    appendChild: WebInspector.DataGrid.prototype.appendChild,
+    insertChild: WebInspector.DataGrid.prototype.insertChild,
+    removeChild: WebInspector.DataGrid.prototype.removeChild,
+    removeChildren: WebInspector.DataGrid.prototype.removeChildren,
+    removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive,
+
+    _recalculateSiblings: function(myIndex)
+    {
+        if (!this.parent)
+            return;
+
+        var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null);
+
+        if (previousChild) {
+            previousChild.nextSibling = this;
+            this.previousSibling = previousChild;
+        } else
+            this.previousSibling = null;
+
+        var nextChild = this.parent.children[myIndex + 1];
+
+        if (nextChild) {
+            nextChild.previousSibling = this;
+            this.nextSibling = nextChild;
+        } else
+            this.nextSibling = null;
+    },
+
+    collapse: function()
+    {
+        if (this._element)
+            this._element.removeStyleClass("expanded");
+
+        this._expanded = false;
+
+        for (var i = 0; i < this.children.length; ++i)
+            this.children[i].revealed = false;
+
+        this.dispatchEventToListeners("collapsed");
+    },
+
+    collapseRecursively: function()
+    {
+        var item = this;
+        while (item) {
+            if (item.expanded)
+                item.collapse();
+            item = item.traverseNextNode(false, this, true);
+        }
+    },
+
+    expand: function()
+    {
+        if (!this.hasChildren || this.expanded)
+            return;
+
+        if (this.revealed && !this._shouldRefreshChildren)
+            for (var i = 0; i < this.children.length; ++i)
+                this.children[i].revealed = true;
+
+        if (this._shouldRefreshChildren) {
+            for (var i = 0; i < this.children.length; ++i)
+                this.children[i]._detach();
+
+            this.dispatchEventToListeners("populate");
+
+            if (this._attached) {
+                for (var i = 0; i < this.children.length; ++i) {
+                    var child = this.children[i];
+                    if (this.revealed)
+                        child.revealed = true;
+                    child._attach();
+                }
+            }
+
+            delete this._shouldRefreshChildren;
+        }
+
+        if (this._element)
+            this._element.addStyleClass("expanded");
+
+        this._expanded = true;
+
+        this.dispatchEventToListeners("expanded");
+    },
+
+    expandRecursively: function()
+    {
+        var item = this;
+        while (item) {
+            item.expand();
+            item = item.traverseNextNode(false, this);
+        }
+    },
+
+    reveal: function()
+    {
+        var currentAncestor = this.parent;
+        while (currentAncestor && !currentAncestor.root) {
+            if (!currentAncestor.expanded)
+                currentAncestor.expand();
+            currentAncestor = currentAncestor.parent;
+        }
+
+        this.element.scrollIntoViewIfNeeded(false);
+
+        this.dispatchEventToListeners("revealed");
+    },
+
+    select: function(supressSelectedEvent)
+    {
+        if (!this.dataGrid || !this.selectable || this.selected)
+            return;
+
+        if (this.dataGrid.selectedNode)
+            this.dataGrid.selectedNode.deselect();
+
+        this._selected = true;
+        this.dataGrid.selectedNode = this;
+
+        if (this._element)
+            this._element.addStyleClass("selected");
+
+        if (!supressSelectedEvent)
+            this.dispatchEventToListeners("selected");
+    },
+
+    deselect: function(supressDeselectedEvent)
+    {
+        if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)
+            return;
+
+        this._selected = false;
+        this.dataGrid.selectedNode = null;
+
+        if (this._element)
+            this._element.removeStyleClass("selected");
+
+        if (!supressDeselectedEvent)
+            this.dispatchEventToListeners("deselected");
+    },
+
+    traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info)
+    {
+        if (!dontPopulate && this.hasChildren)
+            this.dispatchEventToListeners("populate");
+
+        if (info)
+            info.depthChange = 0;
+
+        var node = (!skipHidden || this.revealed) ? this.children[0] : null;
+        if (node && (!skipHidden || this.expanded)) {
+            if (info)
+                info.depthChange = 1;
+            return node;
+        }
+
+        if (this === stayWithin)
+            return null;
+
+        node = (!skipHidden || this.revealed) ? this.nextSibling : null;
+        if (node)
+            return node;
+
+        node = this;
+        while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) {
+            if (info)
+                info.depthChange -= 1;
+            node = node.parent;
+        }
+
+        if (!node)
+            return null;
+
+        return (!skipHidden || node.revealed) ? node.nextSibling : null;
+    },
+
+    traversePreviousNode: function(skipHidden, dontPopulate)
+    {
+        var node = (!skipHidden || this.revealed) ? this.previousSibling : null;
+        if (!dontPopulate && node && node.hasChildren)
+            node.dispatchEventToListeners("populate");
+
+        while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) {
+            if (!dontPopulate && node.hasChildren)
+                node.dispatchEventToListeners("populate");
+            node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null);
+        }
+
+        if (node)
+            return node;
+
+        if (!this.parent || this.parent.root)
+            return null;
+
+        return this.parent;
+    },
+
+    isEventWithinDisclosureTriangle: function(event)
+    {
+        if (!this.hasChildren)
+            return false;
+        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
+        if (!cell.hasStyleClass("disclosure"))
+            return false;
+        var computedLeftPadding = window.getComputedStyle(cell).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX);
+        var left = cell.totalOffsetLeft + computedLeftPadding;
+        return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth;
+    },
+
+    _attach: function()
+    {
+        if (!this.dataGrid || this._attached)
+            return;
+
+        this._attached = true;
+
+        var nextNode = null;
+        var previousNode = this.traversePreviousNode(true, true);
+        if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling)
+            var nextNode = previousNode.element.nextSibling;
+        if (!nextNode)
+            nextNode = this.dataGrid.dataTableBody.lastChild;
+        this.dataGrid.dataTableBody.insertBefore(this.element, nextNode);
+
+        if (this.expanded)
+            for (var i = 0; i < this.children.length; ++i)
+                this.children[i]._attach();
+    },
+
+    _detach: function()
+    {
+        if (!this._attached)
+            return;
+
+        this._attached = false;
+
+        if (this._element && this._element.parentNode)
+            this._element.parentNode.removeChild(this._element);
+
+        for (var i = 0; i < this.children.length; ++i)
+            this.children[i]._detach();
+    }
+}
+
+WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype;
+
+WebInspector.CreationDataGridNode = function(data, hasChildren)
+{
+    WebInspector.DataGridNode.call(this, data, hasChildren);
+    this.isCreationNode = true;
+}
+
+WebInspector.CreationDataGridNode.prototype = {
+    makeNormal: function()
+    {
+        delete this.isCreationNode;
+        delete this.makeNormal;
+    }
+}
+
+WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Database.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Database.js
new file mode 100644
index 0000000..dcab2ab89
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Database.js
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.Database = function(database, domain, name, version)
+{
+    this._database = database;
+    this.domain = domain;
+    this.name = name;
+    this.version = version;
+}
+
+WebInspector.Database.prototype = {
+    isDatabase: function(db)
+    {
+        return this._database === db;
+    },
+
+    get name()
+    {
+        return this._name;
+    },
+
+    set name(x)
+    {
+        if (this._name === x)
+            return;
+        this._name = x;
+    },
+
+    get version()
+    {
+        return this._version;
+    },
+
+    set version(x)
+    {
+        if (this._version === x)
+            return;
+        this._version = x;
+    },
+
+    get domain()
+    {
+        return this._domain;
+    },
+
+    set domain(x)
+    {
+        if (this._domain === x)
+            return;
+        this._domain = x;
+    },
+
+    get displayDomain()
+    {
+        return WebInspector.Resource.prototype.__lookupGetter__("displayDomain").call(this);
+    },
+
+    getTableNames: function(callback)
+    {
+        var names = InspectorController.databaseTableNames(this._database);
+        function sortingCallback()
+        {
+            callback(names.sort());
+        }
+        setTimeout(sortingCallback, 0);
+    },
+    
+    executeSql: function(query, onSuccess, onError)
+    {
+        function successCallback(tx, result)
+        {
+            onSuccess(result);
+        }
+
+        function errorCallback(tx, error)
+        {
+            onError(error);
+        }
+
+        var self = this;
+        function queryTransaction(tx)
+        {
+            tx.executeSql(query, null, InspectorController.wrapCallback(successCallback.bind(self)), InspectorController.wrapCallback(errorCallback.bind(self)));
+        }
+        this._database.transaction(InspectorController.wrapCallback(queryTransaction.bind(this)), InspectorController.wrapCallback(errorCallback.bind(this)));
+    }
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseQueryView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseQueryView.js
new file mode 100644
index 0000000..6c5fa020
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseQueryView.js
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.DatabaseQueryView = function(database)
+{
+    WebInspector.View.call(this);
+
+    this.database = database;
+
+    this.element.addStyleClass("storage-view");
+    this.element.addStyleClass("query");
+    this.element.tabIndex = 0;
+
+    this.element.addEventListener("selectstart", this._selectStart.bind(this), false);
+
+    this.promptElement = document.createElement("div");
+    this.promptElement.className = "database-query-prompt";
+    this.promptElement.appendChild(document.createElement("br"));
+    this.promptElement.handleKeyEvent = this._promptKeyDown.bind(this);
+    this.element.appendChild(this.promptElement);
+
+    this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), " ");
+}
+
+WebInspector.DatabaseQueryView.prototype = {
+    show: function(parentElement)
+    {
+        WebInspector.View.prototype.show.call(this, parentElement);
+
+        function moveBackIfOutside()
+        {
+            if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
+                this.prompt.moveCaretToEndOfPrompt();
+        }
+
+        setTimeout(moveBackIfOutside.bind(this), 0);
+    },
+
+    completions: function(wordRange, bestMatchOnly, completionsReadyCallback)
+    {
+        var prefix = wordRange.toString().toLowerCase();
+        if (!prefix.length)
+            return;
+
+        var results = [];
+
+        function accumulateMatches(textArray)
+        {
+            if (bestMatchOnly && results.length)
+                return;
+            for (var i = 0; i < textArray.length; ++i) {
+                var text = textArray[i].toLowerCase();
+                if (text.length < prefix.length)
+                    continue;
+                if (text.indexOf(prefix) !== 0)
+                    continue;
+                results.push(textArray[i]);
+                if (bestMatchOnly)
+                    return;
+            }
+        }
+        
+        function tableNamesCallback(tableNames)
+        {
+            accumulateMatches(tableNames.map(function(name) { return name + " " }));
+            accumulateMatches(["SELECT ", "FROM ", "WHERE ", "LIMIT ", "DELETE FROM ", "CREATE ", "DROP ", "TABLE ", "INDEX ", "UPDATE ", "INSERT INTO ", "VALUES ("]);
+
+            completionsReadyCallback(results);
+        }
+        this.database.getTableNames(tableNamesCallback);
+    },
+
+    _promptKeyDown: function(event)
+    {
+        switch (event.keyIdentifier) {
+            case "Enter":
+                this._enterKeyPressed(event);
+                return;
+        }
+
+        this.prompt.handleKeyEvent(event);
+    },
+
+    _selectStart: function(event)
+    {
+        if (this._selectionTimeout)
+            clearTimeout(this._selectionTimeout);
+
+        this.prompt.clearAutoComplete();
+
+        function moveBackIfOutside()
+        {
+            delete this._selectionTimeout;
+            if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
+                this.prompt.moveCaretToEndOfPrompt();
+            this.prompt.autoCompleteSoon();
+        }
+
+        this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
+    },
+
+    _enterKeyPressed: function(event)
+    {
+        event.preventDefault();
+        event.stopPropagation();
+
+        this.prompt.clearAutoComplete(true);
+
+        var query = this.prompt.text;
+        if (!query.length)
+            return;
+
+        this.prompt.history.push(query);
+        this.prompt.historyOffset = 0;
+        this.prompt.text = "";
+
+        this.database.executeSql(query, this._queryFinished.bind(this, query), this._queryError.bind(this, query));
+    },
+
+    _queryFinished: function(query, result)
+    {
+        var dataGrid = WebInspector.panels.storage.dataGridForResult(result);
+        if (!dataGrid)
+            return;
+        dataGrid.element.addStyleClass("inline");
+        this._appendQueryResult(query, dataGrid.element);
+
+        if (query.match(/^create /i) || query.match(/^drop table /i))
+            WebInspector.panels.storage.updateDatabaseTables(this.database);
+    },
+
+    _queryError: function(query, error)
+    {
+        if (error.code == 1)
+            var message = error.message;
+        else if (error.code == 2)
+            var message = WebInspector.UIString("Database no longer has expected version.");
+        else
+            var message = WebInspector.UIString("An unexpected error %s occurred.", error.code);
+
+        this._appendQueryResult(query, message, "error");
+    },
+
+    _appendQueryResult: function(query, result, resultClassName)
+    {
+        var element = document.createElement("div");
+        element.className = "database-user-query";
+
+        var commandTextElement = document.createElement("span");
+        commandTextElement.className = "database-query-text";
+        commandTextElement.textContent = query;
+        element.appendChild(commandTextElement);
+
+        var resultElement = document.createElement("div");
+        resultElement.className = "database-query-result";
+
+        if (resultClassName)
+            resultElement.addStyleClass(resultClassName);
+
+        if (typeof result === "string" || result instanceof String)
+            resultElement.textContent = result;
+        else if (result && result.nodeName)
+            resultElement.appendChild(result);
+
+        if (resultElement.childNodes.length)
+            element.appendChild(resultElement);
+
+        this.element.insertBefore(element, this.promptElement);
+        this.promptElement.scrollIntoView(false);
+    }
+}
+
+WebInspector.DatabaseQueryView.prototype.__proto__ = WebInspector.View.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseTableView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseTableView.js
new file mode 100644
index 0000000..aa76794f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseTableView.js
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.DatabaseTableView = function(database, tableName)
+{
+    WebInspector.View.call(this);
+
+    this.database = database;
+    this.tableName = tableName;
+
+    this.element.addStyleClass("storage-view");
+    this.element.addStyleClass("table");
+
+    this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
+    this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false);
+}
+
+WebInspector.DatabaseTableView.prototype = {
+    show: function(parentElement)
+    {
+        WebInspector.View.prototype.show.call(this, parentElement);
+        this.update();
+    },
+
+    get statusBarItems()
+    {
+        return [this.refreshButton];
+    },
+
+    update: function()
+    {
+        this.database.executeSql("SELECT * FROM " + this.tableName, this._queryFinished.bind(this), this._queryError.bind(this));
+    },
+
+    _queryFinished: function(result)
+    {
+        this.element.removeChildren();
+
+        var dataGrid = WebInspector.panels.storage.dataGridForResult(result);
+        if (!dataGrid) {
+            var emptyMsgElement = document.createElement("div");
+            emptyMsgElement.className = "storage-table-empty";
+            emptyMsgElement.textContent = WebInspector.UIString("The “%s”\ntable is empty.", this.tableName);
+            this.element.appendChild(emptyMsgElement);
+            return;
+        }
+
+        this.element.appendChild(dataGrid.element);
+    },
+
+    _queryError: function(error)
+    {
+        this.element.removeChildren();
+
+        var errorMsgElement = document.createElement("div");
+        errorMsgElement.className = "storage-table-error";
+        errorMsgElement.textContent = WebInspector.UIString("An error occurred trying to\nread the “%s” table.", this.tableName);
+        this.element.appendChild(errorMsgElement);
+    },
+
+    _refreshButtonClicked: function(event)
+    {
+        this.update();
+    }
+}
+
+WebInspector.DatabaseTableView.prototype.__proto__ = WebInspector.View.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Drawer.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Drawer.js
new file mode 100644
index 0000000..1b50f91
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Drawer.js
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.Drawer = function()
+{
+    WebInspector.View.call(this, document.getElementById("drawer"));
+
+    this.mainStatusBar = document.getElementById("main-status-bar");
+    this.mainStatusBar.addEventListener("mousedown", this._startStatusBarDragging.bind(this), true);
+    this.viewStatusBar = document.getElementById("other-drawer-status-bar-items");
+}
+
+WebInspector.Drawer.prototype = {
+    get visibleView()
+    {
+        return this._visibleView;
+    },
+
+    set visibleView(x)
+    {
+        if (this._visibleView === x) {
+            this.visible = !this.visible;
+            return;
+        }
+
+        var firstTime = !this._visibleView;
+        if (this._visibleView)
+            this._visibleView.hide();
+
+        this._visibleView = x;
+
+        if (x && !firstTime) {
+            this._safelyRemoveChildren();
+            this.viewStatusBar.removeChildren(); // optimize this? call old.detach()
+            x.attach(this.element, this.viewStatusBar);
+            x.show();
+            this.visible = true;
+        }
+    },
+
+    showView: function(view)
+    {
+        if (!this.visible || this.visibleView !== view)
+            this.visibleView = view;
+    },
+
+    show: function()
+    {
+        if (this._animating || this.visible)
+            return;
+
+        if (this.visibleView)
+            this.visibleView.show();
+
+        WebInspector.View.prototype.show.call(this);
+
+        this._animating = true;
+
+        document.body.addStyleClass("drawer-visible");
+
+        var anchoredItems = document.getElementById("anchored-status-bar-items");
+
+        var animations = [
+            {element: document.getElementById("main"), end: {bottom: this.element.offsetHeight}},
+            {element: document.getElementById("main-status-bar"), start: {"padding-left": anchoredItems.offsetWidth - 1}, end: {"padding-left": 0}},
+            {element: document.getElementById("other-drawer-status-bar-items"), start: {opacity: 0}, end: {opacity: 1}}
+        ];
+
+        var consoleStatusBar = document.getElementById("drawer-status-bar");
+        consoleStatusBar.insertBefore(anchoredItems, consoleStatusBar.firstChild);
+
+        function animationFinished()
+        {
+            if ("updateStatusBarItems" in WebInspector.currentPanel)
+                WebInspector.currentPanel.updateStatusBarItems();
+            if (this.visibleView.afterShow)
+                this.visibleView.afterShow();
+            delete this._animating;
+        }
+
+        WebInspector.animateStyle(animations, window.event && window.event.shiftKey ? 2000 : 250, animationFinished.bind(this));
+    },
+
+    hide: function()
+    {
+        if (this._animating || !this.visible)
+            return;
+
+        WebInspector.View.prototype.hide.call(this);
+        
+        if (this.visibleView)
+            this.visibleView.hide();
+
+        this._animating = true;
+
+        if (this.element === WebInspector.currentFocusElement || this.element.isAncestor(WebInspector.currentFocusElement))
+            WebInspector.currentFocusElement = WebInspector.previousFocusElement;
+
+        var anchoredItems = document.getElementById("anchored-status-bar-items");
+
+        // Temporally set properties and classes to mimic the post-animation values so panels
+        // like Elements in their updateStatusBarItems call will size things to fit the final location.
+        document.getElementById("main-status-bar").style.setProperty("padding-left", (anchoredItems.offsetWidth - 1) + "px");
+        document.body.removeStyleClass("drawer-visible");
+        if ("updateStatusBarItems" in WebInspector.currentPanel)
+            WebInspector.currentPanel.updateStatusBarItems();
+        document.body.addStyleClass("drawer-visible");
+
+        var animations = [
+            {element: document.getElementById("main"), end: {bottom: 0}},
+            {element: document.getElementById("main-status-bar"), start: {"padding-left": 0}, end: {"padding-left": anchoredItems.offsetWidth - 1}},
+            {element: document.getElementById("other-drawer-status-bar-items"), start: {opacity: 1}, end: {opacity: 0}}
+        ];
+
+        function animationFinished()
+        {
+            var mainStatusBar = document.getElementById("main-status-bar");
+            mainStatusBar.insertBefore(anchoredItems, mainStatusBar.firstChild);
+            mainStatusBar.style.removeProperty("padding-left");
+            document.body.removeStyleClass("drawer-visible");
+            delete this._animating;
+        }
+
+        WebInspector.animateStyle(animations, window.event && window.event.shiftKey ? 2000 : 250, animationFinished.bind(this));
+    },
+
+    _safelyRemoveChildren: function()
+    {
+        var child = this.element.firstChild;
+        while (child) {
+            if (child.id !== "drawer-status-bar") {
+                var moveTo = child.nextSibling;
+                this.element.removeChild(child);
+                child = moveTo;
+            } else
+                child = child.nextSibling;
+        }
+    },
+
+    _startStatusBarDragging: function(event)
+    {
+        if (!this.visible || event.target !== document.getElementById("main-status-bar"))
+            return;
+
+        WebInspector.elementDragStart(document.getElementById("main-status-bar"), this._statusBarDragging.bind(this), this._endStatusBarDragging.bind(this), event, "row-resize");
+
+        this._statusBarDragOffset = event.pageY - this.element.totalOffsetTop;
+
+        event.stopPropagation();
+    },
+
+    _statusBarDragging: function(event)
+    {
+        var mainElement = document.getElementById("main");
+
+        var height = window.innerHeight - event.pageY + this._statusBarDragOffset;
+        height = Number.constrain(height, Preferences.minConsoleHeight, window.innerHeight - mainElement.totalOffsetTop - Preferences.minConsoleHeight);
+
+        mainElement.style.bottom = height + "px";
+        this.element.style.height = height + "px";
+
+        event.preventDefault();
+        event.stopPropagation();
+    },
+
+    _endStatusBarDragging: function(event)
+    {
+        WebInspector.elementDragEnd(event);
+
+        delete this._statusBarDragOffset;
+
+        event.stopPropagation();
+    }
+}
+
+WebInspector.Drawer.prototype.__proto__ = WebInspector.View.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsPanel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsPanel.js
new file mode 100644
index 0000000..49a1188
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsPanel.js
@@ -0,0 +1,1045 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2008 Matt Lilek <[email protected]>
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.ElementsPanel = function()
+{
+    WebInspector.Panel.call(this);
+
+    this.element.addStyleClass("elements");
+
+    this.contentElement = document.createElement("div");
+    this.contentElement.id = "elements-content";
+    this.contentElement.className = "outline-disclosure";
+
+    this.treeOutline = new WebInspector.ElementsTreeOutline();
+    this.treeOutline.panel = this;
+    this.treeOutline.includeRootDOMNode = false;
+    this.treeOutline.selectEnabled = true;
+
+    this.treeOutline.focusedNodeChanged = function(forceUpdate)
+    {
+        if (this.panel.visible && WebInspector.currentFocusElement !== document.getElementById("search"))
+            WebInspector.currentFocusElement = document.getElementById("main-panels");
+
+        this.panel.updateBreadcrumb(forceUpdate);
+
+        for (var pane in this.panel.sidebarPanes)
+           this.panel.sidebarPanes[pane].needsUpdate = true;
+
+        this.panel.updateStyles(true);
+        this.panel.updateMetrics();
+        this.panel.updateProperties();
+
+        if (InspectorController.searchingForNode()) {
+            InspectorController.toggleNodeSearch();
+            this.panel.nodeSearchButton.removeStyleClass("toggled-on");
+        }
+        if (this._focusedDOMNode)
+            InjectedScriptAccess.addInspectedNode(this._focusedDOMNode.id, function() {});
+    };
+
+    this.contentElement.appendChild(this.treeOutline.element);
+
+    this.crumbsElement = document.createElement("div");
+    this.crumbsElement.className = "crumbs";
+    this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bind(this), false);
+    this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.bind(this), false);
+
+    this.sidebarPanes = {};
+    this.sidebarPanes.styles = new WebInspector.StylesSidebarPane();
+    this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane();
+    this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane();
+
+    this.sidebarPanes.styles.onexpand = this.updateStyles.bind(this);
+    this.sidebarPanes.metrics.onexpand = this.updateMetrics.bind(this);
+    this.sidebarPanes.properties.onexpand = this.updateProperties.bind(this);
+
+    this.sidebarPanes.styles.expanded = true;
+
+    this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEdited, this);
+    this.sidebarPanes.styles.addEventListener("style property toggled", this._stylesPaneEdited, this);
+    this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPaneEdited, this);
+
+    this.sidebarElement = document.createElement("div");
+    this.sidebarElement.id = "elements-sidebar";
+
+    this.sidebarElement.appendChild(this.sidebarPanes.styles.element);
+    this.sidebarElement.appendChild(this.sidebarPanes.metrics.element);
+    this.sidebarElement.appendChild(this.sidebarPanes.properties.element);
+
+    this.sidebarResizeElement = document.createElement("div");
+    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
+    this.sidebarResizeElement.addEventListener("mousedown", this.rightSidebarResizerDragStart.bind(this), false);
+
+    this.nodeSearchButton = new WebInspector.StatusBarButton(WebInspector.UIString("Select an element in the page to inspect it."), "node-search-status-bar-item");
+    this.nodeSearchButton.addEventListener("click", this._nodeSearchButtonClicked.bind(this), false);
+
+    this.searchingForNode = false;
+
+    this.element.appendChild(this.contentElement);
+    this.element.appendChild(this.sidebarElement);
+    this.element.appendChild(this.sidebarResizeElement);
+
+    this._changedStyles = {};
+
+    this.reset();
+}
+
+WebInspector.ElementsPanel.prototype = {
+    toolbarItemClass: "elements",
+
+    get toolbarItemLabel()
+    {
+        return WebInspector.UIString("Elements");
+    },
+
+    get statusBarItems()
+    {
+        return [this.nodeSearchButton.element, this.crumbsElement];
+    },
+
+    updateStatusBarItems: function()
+    {
+        this.updateBreadcrumbSizes();
+    },
+
+    show: function()
+    {
+        WebInspector.Panel.prototype.show.call(this);
+        this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";
+        this.updateBreadcrumb();
+        this.treeOutline.updateSelection();
+        if (this.recentlyModifiedNodes.length)
+            this._updateModifiedNodes();
+    },
+
+    hide: function()
+    {
+        WebInspector.Panel.prototype.hide.call(this);
+
+        WebInspector.hoveredDOMNode = null;
+
+        if (InspectorController.searchingForNode()) {
+            InspectorController.toggleNodeSearch();
+            this.nodeSearchButton.toggled = false;
+        }
+    },
+
+    resize: function()
+    {
+        this.treeOutline.updateSelection();
+        this.updateBreadcrumbSizes();
+    },
+
+    reset: function()
+    {
+        this.rootDOMNode = null;
+        this.focusedDOMNode = null;
+
+        WebInspector.hoveredDOMNode = null;
+
+        if (InspectorController.searchingForNode()) {
+            InspectorController.toggleNodeSearch();
+            this.nodeSearchButton.toggled = false;
+        }
+
+        this.recentlyModifiedNodes = [];
+
+        delete this.currentQuery;
+        this.searchCanceled();
+
+        var domWindow = WebInspector.domAgent.domWindow;
+        if (!domWindow || !domWindow.document || !domWindow.document.firstChild)
+            return;
+
+        // If the window isn't visible, return early so the DOM tree isn't built
+        // and mutation event listeners are not added.
+        if (!InspectorController.isWindowVisible())
+            return;
+
+        var inspectedRootDocument = domWindow.document;
+        inspectedRootDocument.addEventListener("DOMNodeInserted", this._nodeInserted.bind(this));
+        inspectedRootDocument.addEventListener("DOMNodeRemoved", this._nodeRemoved.bind(this));
+
+        this.rootDOMNode = inspectedRootDocument;
+
+        var canidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement;
+        if (canidateFocusNode) {
+            this.treeOutline.suppressSelectHighlight = true;
+            this.focusedDOMNode = canidateFocusNode;
+            this.treeOutline.suppressSelectHighlight = false;
+
+            if (this.treeOutline.selectedTreeElement)
+                this.treeOutline.selectedTreeElement.expand();
+        }
+    },
+
+    searchCanceled: function()
+    {
+        if (this._searchResults) {
+            for (var i = 0; i < this._searchResults.length; ++i) {
+                var treeElement = this.treeOutline.findTreeElement(this._searchResults[i]);
+                if (treeElement)
+                    treeElement.highlighted = false;
+            }
+        }
+
+        WebInspector.updateSearchMatchesCount(0, this);
+
+        this._currentSearchResultIndex = 0;
+        this._searchResults = [];
+        InjectedScriptAccess.searchCanceled(function() {});
+    },
+
+    performSearch: function(query)
+    {
+        // Call searchCanceled since it will reset everything we need before doing a new search.
+        this.searchCanceled();
+
+        const whitespaceTrimmedQuery = query.trimWhitespace();
+        if (!whitespaceTrimmedQuery.length)
+            return;
+
+        this._updatedMatchCountOnce = false;
+        this._matchesCountUpdateTimeout = null;
+
+        InjectedScriptAccess.performSearch(whitespaceTrimmedQuery, function() {});
+    },
+
+    _updateMatchesCount: function()
+    {
+        WebInspector.updateSearchMatchesCount(this._searchResults.length, this);
+        this._matchesCountUpdateTimeout = null;
+        this._updatedMatchCountOnce = true;
+    },
+
+    _updateMatchesCountSoon: function()
+    {
+        if (!this._updatedMatchCountOnce)
+            return this._updateMatchesCount();
+        if (this._matchesCountUpdateTimeout)
+            return;
+        // Update the matches count every half-second so it doesn't feel twitchy.
+        this._matchesCountUpdateTimeout = setTimeout(this._updateMatchesCount.bind(this), 500);
+    },
+
+    addNodesToSearchResult: function(nodeIds)
+    {
+        if (!nodeIds)
+            return;
+
+        var nodeIdsArray = nodeIds.split(",");
+        for (var i = 0; i < nodeIdsArray.length; ++i) {
+            var nodeId = nodeIdsArray[i];
+            var node = WebInspector.domAgent.nodeForId(nodeId);
+            if (!node)
+                continue;
+
+            if (!this._searchResults.length) {
+                this._currentSearchResultIndex = 0;
+                this.focusedDOMNode = node;
+            }
+
+            this._searchResults.push(node);
+
+            // Highlight the tree element to show it matched the search.
+            // FIXME: highlight the substrings in text nodes and attributes.
+            var treeElement = this.treeOutline.findTreeElement(node);
+            if (treeElement)
+                treeElement.highlighted = true;
+        }
+
+        this._updateMatchesCountSoon();
+    },
+
+    jumpToNextSearchResult: function()
+    {
+        if (!this._searchResults || !this._searchResults.length)
+            return;
+        if (++this._currentSearchResultIndex >= this._searchResults.length)
+            this._currentSearchResultIndex = 0;
+        this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex];
+    },
+
+    jumpToPreviousSearchResult: function()
+    {
+        if (!this._searchResults || !this._searchResults.length)
+            return;
+        if (--this._currentSearchResultIndex < 0)
+            this._currentSearchResultIndex = (this._searchResults.length - 1);
+        this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex];
+    },
+
+    renameSelector: function(oldIdentifier, newIdentifier, oldSelector, newSelector)
+    {
+        // TODO: Implement Shifting the oldSelector, and its contents to a newSelector
+    },
+
+    addStyleChange: function(identifier, style, property)
+    {
+        if (!style.parentRule)
+            return;
+
+        var selector = style.parentRule.selectorText;
+        if (!this._changedStyles[identifier])
+            this._changedStyles[identifier] = {};
+
+        if (!this._changedStyles[identifier][selector])
+            this._changedStyles[identifier][selector] = {};
+
+        if (!this._changedStyles[identifier][selector][property])
+            WebInspector.styleChanges += 1;
+
+        this._changedStyles[identifier][selector][property] = style.getPropertyValue(property);
+    },
+
+    removeStyleChange: function(identifier, style, property)
+    {
+        if (!style.parentRule)
+            return;
+
+        var selector = style.parentRule.selectorText;
+        if (!this._changedStyles[identifier] || !this._changedStyles[identifier][selector])
+            return;
+
+        if (this._changedStyles[identifier][selector][property]) {
+            delete this._changedStyles[identifier][selector][property];
+            WebInspector.styleChanges -= 1;
+        }
+    },
+
+    generateStylesheet: function()
+    {
+        if (!WebInspector.styleChanges)
+            return;
+
+        // Merge Down to Just Selectors
+        var mergedSelectors = {};
+        for (var identifier in this._changedStyles) {
+            for (var selector in this._changedStyles[identifier]) {
+                if (!mergedSelectors[selector])
+                    mergedSelectors[selector] = this._changedStyles[identifier][selector];
+                else { // merge on selector
+                    var merge = {};
+                    for (var property in mergedSelectors[selector])
+                        merge[property] = mergedSelectors[selector][property];
+                    for (var property in this._changedStyles[identifier][selector]) {
+                        if (!merge[property])
+                            merge[property] = this._changedStyles[identifier][selector][property];
+                        else { // merge on property within a selector, include comment to notify user
+                            var value1 = merge[property];
+                            var value2 = this._changedStyles[identifier][selector][property];
+
+                            if (value1 === value2)
+                                merge[property] = [value1];
+                            else if (value1 instanceof Array)
+                                merge[property].push(value2);
+                            else
+                                merge[property] = [value1, value2];
+                        }
+                    }
+                    mergedSelectors[selector] = merge;
+                }
+            }
+        }
+
+        var builder = [];
+        builder.push("/**");
+        builder.push(" * Inspector Generated Stylesheet"); // UIString?
+        builder.push(" */\n");
+
+        var indent = "  ";
+        function displayProperty(property, value, comment) {
+            if (comment)
+                return indent + "/* " + property + ": " + value + "; */";
+            else
+                return indent + property + ": " + value + ";";
+        }
+
+        for (var selector in mergedSelectors) {
+            var psuedoStyle = mergedSelectors[selector];
+            var properties = Object.properties(psuedoStyle);
+            if (properties.length) {
+                builder.push(selector + " {");
+                for (var i = 0; i < properties.length; ++i) {
+                    var property = properties[i];
+                    var value = psuedoStyle[property];
+                    if (!(value instanceof Array))
+                        builder.push(displayProperty(property, value));
+                    else {
+                        if (value.length === 1)
+                            builder.push(displayProperty(property, value) + " /* merged from equivalent edits */"); // UIString?
+                        else {                        
+                            builder.push(indent + "/* There was a Conflict... There were Multiple Edits for '" + property + "' */"); // UIString?
+                            for (var j = 0; j < value.length; ++j)
+                                builder.push(displayProperty(property, value, true));
+                        }
+                    }
+                }
+                builder.push("}\n");
+            }
+        }
+
+        WebInspector.showConsole();
+        WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage(builder.join("\n")));
+    },
+
+    get rootDOMNode()
+    {
+        return this.treeOutline.rootDOMNode;
+    },
+
+    set rootDOMNode(x)
+    {
+        this.treeOutline.rootDOMNode = x;
+    },
+
+    get focusedDOMNode()
+    {
+        return this.treeOutline.focusedDOMNode;
+    },
+
+    set focusedDOMNode(x)
+    {
+        this.treeOutline.focusedDOMNode = x;
+    },
+
+    _nodeInserted: function(event)
+    {
+        this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, inserted: true});
+        if (this.visible)
+            this._updateModifiedNodesSoon();
+    },
+
+    _nodeRemoved: function(event)
+    {
+        this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, removed: true});
+        if (this.visible)
+            this._updateModifiedNodesSoon();
+    },
+
+    _updateModifiedNodesSoon: function()
+    {
+        if ("_updateModifiedNodesTimeout" in this)
+            return;
+        this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind(this), 0);
+    },
+
+    _updateModifiedNodes: function()
+    {
+        if ("_updateModifiedNodesTimeout" in this) {
+            clearTimeout(this._updateModifiedNodesTimeout);
+            delete this._updateModifiedNodesTimeout;
+        }
+
+        var updatedParentTreeElements = [];
+        var updateBreadcrumbs = false;
+
+        for (var i = 0; i < this.recentlyModifiedNodes.length; ++i) {
+            var replaced = this.recentlyModifiedNodes[i].replaced;
+            var parent = this.recentlyModifiedNodes[i].parent;
+            if (!parent)
+                continue;
+
+            var parentNodeItem = this.treeOutline.findTreeElement(parent);
+            if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) {
+                parentNodeItem.updateChildren(replaced);
+                parentNodeItem.alreadyUpdatedChildren = true;
+                updatedParentTreeElements.push(parentNodeItem);
+            }
+
+            if (!updateBreadcrumbs && (this.focusedDOMNode === parent || isAncestor(this.focusedDOMNode, parent)))
+                updateBreadcrumbs = true;
+        }
+
+        for (var i = 0; i < updatedParentTreeElements.length; ++i)
+            delete updatedParentTreeElements[i].alreadyUpdatedChildren;
+
+        this.recentlyModifiedNodes = [];
+
+        if (updateBreadcrumbs)
+            this.updateBreadcrumb(true);
+    },
+
+    _stylesPaneEdited: function()
+    {
+        this.sidebarPanes.metrics.needsUpdate = true;
+        this.updateMetrics();
+    },
+
+    _metricsPaneEdited: function()
+    {
+        this.sidebarPanes.styles.needsUpdate = true;
+        this.updateStyles(true);
+    },
+
+    _mouseMovedInCrumbs: function(event)
+    {
+        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
+        var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb");
+
+        WebInspector.hoveredDOMNode = (crumbElement ? crumbElement.representedObject : null);
+
+        if ("_mouseOutOfCrumbsTimeout" in this) {
+            clearTimeout(this._mouseOutOfCrumbsTimeout);
+            delete this._mouseOutOfCrumbsTimeout;
+        }
+    },
+
+    _mouseMovedOutOfCrumbs: function(event)
+    {
+        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
+        if (nodeUnderMouse.isDescendant(this.crumbsElement))
+            return;
+
+        WebInspector.hoveredDOMNode = null;
+
+        this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bind(this), 1000);
+    },
+
+    updateBreadcrumb: function(forceUpdate)
+    {
+        if (!this.visible)
+            return;
+
+        var crumbs = this.crumbsElement;
+
+        var handled = false;
+        var foundRoot = false;
+        var crumb = crumbs.firstChild;
+        while (crumb) {
+            if (crumb.representedObject === this.rootDOMNode)
+                foundRoot = true;
+
+            if (foundRoot)
+                crumb.addStyleClass("dimmed");
+            else
+                crumb.removeStyleClass("dimmed");
+
+            if (crumb.representedObject === this.focusedDOMNode) {
+                crumb.addStyleClass("selected");
+                handled = true;
+            } else {
+                crumb.removeStyleClass("selected");
+            }
+
+            crumb = crumb.nextSibling;
+        }
+
+        if (handled && !forceUpdate) {
+            // We don't need to rebuild the crumbs, but we need to adjust sizes
+            // to reflect the new focused or root node.
+            this.updateBreadcrumbSizes();
+            return;
+        }
+
+        crumbs.removeChildren();
+
+        var panel = this;
+
+        function selectCrumbFunction(event)
+        {
+            var crumb = event.currentTarget;
+            if (crumb.hasStyleClass("collapsed")) {
+                // Clicking a collapsed crumb will expose the hidden crumbs.
+                if (crumb === panel.crumbsElement.firstChild) {
+                    // If the focused crumb is the first child, pick the farthest crumb
+                    // that is still hidden. This allows the user to expose every crumb.
+                    var currentCrumb = crumb;
+                    while (currentCrumb) {
+                        var hidden = currentCrumb.hasStyleClass("hidden");
+                        var collapsed = currentCrumb.hasStyleClass("collapsed");
+                        if (!hidden && !collapsed)
+                            break;
+                        crumb = currentCrumb;
+                        currentCrumb = currentCrumb.nextSibling;
+                    }
+                }
+
+                panel.updateBreadcrumbSizes(crumb);
+            } else {
+                // Clicking a dimmed crumb or double clicking (event.detail >= 2)
+                // will change the root node in addition to the focused node.
+                if (event.detail >= 2 || crumb.hasStyleClass("dimmed"))
+                    panel.rootDOMNode = crumb.representedObject.parentNode;
+                panel.focusedDOMNode = crumb.representedObject;
+            }
+
+            event.preventDefault();
+        }
+
+        foundRoot = false;
+        for (var current = this.focusedDOMNode; current; current = current.parentNode) {
+            if (current.nodeType === Node.DOCUMENT_NODE)
+                continue;
+
+            if (current === this.rootDOMNode)
+                foundRoot = true;
+
+            var crumb = document.createElement("span");
+            crumb.className = "crumb";
+            crumb.representedObject = current;
+            crumb.addEventListener("mousedown", selectCrumbFunction, false);
+
+            var crumbTitle;
+            switch (current.nodeType) {
+                case Node.ELEMENT_NODE:
+                    crumbTitle = current.nodeName.toLowerCase();
+
+                    var nameElement = document.createElement("span");
+                    nameElement.textContent = crumbTitle;
+                    crumb.appendChild(nameElement);
+
+                    var idAttribute = current.getAttribute("id");
+                    if (idAttribute) {
+                        var idElement = document.createElement("span");
+                        crumb.appendChild(idElement);
+
+                        var part = "#" + idAttribute;
+                        crumbTitle += part;
+                        idElement.appendChild(document.createTextNode(part));
+
+                        // Mark the name as extra, since the ID is more important.
+                        nameElement.className = "extra";
+                    }
+
+                    var classAttribute = current.getAttribute("class");
+                    if (classAttribute) {
+                        var classes = classAttribute.split(/\s+/);
+                        var foundClasses = {};
+
+                        if (classes.length) {
+                            var classesElement = document.createElement("span");
+                            classesElement.className = "extra";
+                            crumb.appendChild(classesElement);
+
+                            for (var i = 0; i < classes.length; ++i) {
+                                var className = classes[i];
+                                if (className && !(className in foundClasses)) {
+                                    var part = "." + className;
+                                    crumbTitle += part;
+                                    classesElement.appendChild(document.createTextNode(part));
+                                    foundClasses[className] = true;
+                                }
+                            }
+                        }
+                    }
+
+                    break;
+
+                case Node.TEXT_NODE:
+                    if (isNodeWhitespace.call(current))
+                        crumbTitle = WebInspector.UIString("(whitespace)");
+                    else
+                        crumbTitle = WebInspector.UIString("(text)");
+                    break
+
+                case Node.COMMENT_NODE:
+                    crumbTitle = "<!-->";
+                    break;
+
+                case Node.DOCUMENT_TYPE_NODE:
+                    crumbTitle = "<!DOCTYPE>";
+                    break;
+
+                default:
+                    crumbTitle = current.nodeName.toLowerCase();
+            }
+
+            if (!crumb.childNodes.length) {
+                var nameElement = document.createElement("span");
+                nameElement.textContent = crumbTitle;
+                crumb.appendChild(nameElement);
+            }
+
+            crumb.title = crumbTitle;
+
+            if (foundRoot)
+                crumb.addStyleClass("dimmed");
+            if (current === this.focusedDOMNode)
+                crumb.addStyleClass("selected");
+            if (!crumbs.childNodes.length)
+                crumb.addStyleClass("end");
+
+            crumbs.appendChild(crumb);
+        }
+
+        if (crumbs.hasChildNodes())
+            crumbs.lastChild.addStyleClass("start");
+
+        this.updateBreadcrumbSizes();
+    },
+
+    updateBreadcrumbSizes: function(focusedCrumb)
+    {
+        if (!this.visible)
+            return;
+
+        if (document.body.offsetWidth <= 0) {
+            // The stylesheet hasn't loaded yet or the window is closed,
+            // so we can't calculate what is need. Return early.
+            return;
+        }
+
+        var crumbs = this.crumbsElement;
+        if (!crumbs.childNodes.length || crumbs.offsetWidth <= 0)
+            return; // No crumbs, do nothing.
+
+        // A Zero index is the right most child crumb in the breadcrumb.
+        var selectedIndex = 0;
+        var focusedIndex = 0;
+        var selectedCrumb;
+
+        var i = 0;
+        var crumb = crumbs.firstChild;
+        while (crumb) {
+            // Find the selected crumb and index. 
+            if (!selectedCrumb && crumb.hasStyleClass("selected")) {
+                selectedCrumb = crumb;
+                selectedIndex = i;
+            }
+
+            // Find the focused crumb index. 
+            if (crumb === focusedCrumb)
+                focusedIndex = i;
+
+            // Remove any styles that affect size before
+            // deciding to shorten any crumbs.
+            if (crumb !== crumbs.lastChild)
+                crumb.removeStyleClass("start");
+            if (crumb !== crumbs.firstChild)
+                crumb.removeStyleClass("end");
+
+            crumb.removeStyleClass("compact");
+            crumb.removeStyleClass("collapsed");
+            crumb.removeStyleClass("hidden");
+
+            crumb = crumb.nextSibling;
+            ++i;
+        }
+
+        // Restore the start and end crumb classes in case they got removed in coalesceCollapsedCrumbs().
+        // The order of the crumbs in the document is opposite of the visual order.
+        crumbs.firstChild.addStyleClass("end");
+        crumbs.lastChild.addStyleClass("start");
+
+        function crumbsAreSmallerThanContainer()
+        {
+            var rightPadding = 20;
+            var errorWarningElement = document.getElementById("error-warning-count");
+            if (!WebInspector.drawer.visible && errorWarningElement)
+                rightPadding += errorWarningElement.offsetWidth;
+            return ((crumbs.totalOffsetLeft + crumbs.offsetWidth + rightPadding) < window.innerWidth);
+        }
+
+        if (crumbsAreSmallerThanContainer())
+            return; // No need to compact the crumbs, they all fit at full size.
+
+        var BothSides = 0;
+        var AncestorSide = -1;
+        var ChildSide = 1;
+
+        function makeCrumbsSmaller(shrinkingFunction, direction, significantCrumb)
+        {
+            if (!significantCrumb)
+                significantCrumb = (focusedCrumb || selectedCrumb);
+
+            if (significantCrumb === selectedCrumb)
+                var significantIndex = selectedIndex;
+            else if (significantCrumb === focusedCrumb)
+                var significantIndex = focusedIndex;
+            else {
+                var significantIndex = 0;
+                for (var i = 0; i < crumbs.childNodes.length; ++i) {
+                    if (crumbs.childNodes[i] === significantCrumb) {
+                        significantIndex = i;
+                        break;
+                    }
+                }
+            }
+
+            function shrinkCrumbAtIndex(index)
+            {
+                var shrinkCrumb = crumbs.childNodes[index];
+                if (shrinkCrumb && shrinkCrumb !== significantCrumb)
+                    shrinkingFunction(shrinkCrumb);
+                if (crumbsAreSmallerThanContainer())
+                    return true; // No need to compact the crumbs more.
+                return false;
+            }
+
+            // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs
+            // fit in the container or we run out of crumbs to shrink.
+            if (direction) {
+                // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb.
+                var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1);
+                while (index !== significantIndex) {
+                    if (shrinkCrumbAtIndex(index))
+                        return true;
+                    index += (direction > 0 ? 1 : -1);
+                }
+            } else {
+                // Crumbs are shrunk in order of descending distance from the signifcant crumb,
+                // with a tie going to child crumbs.
+                var startIndex = 0;
+                var endIndex = crumbs.childNodes.length - 1;
+                while (startIndex != significantIndex || endIndex != significantIndex) {
+                    var startDistance = significantIndex - startIndex;
+                    var endDistance = endIndex - significantIndex;
+                    if (startDistance >= endDistance)
+                        var index = startIndex++;
+                    else
+                        var index = endIndex--;
+                    if (shrinkCrumbAtIndex(index))
+                        return true;
+                }
+            }
+
+            // We are not small enough yet, return false so the caller knows.
+            return false;
+        }
+
+        function coalesceCollapsedCrumbs()
+        {
+            var crumb = crumbs.firstChild;
+            var collapsedRun = false;
+            var newStartNeeded = false;
+            var newEndNeeded = false;
+            while (crumb) {
+                var hidden = crumb.hasStyleClass("hidden");
+                if (!hidden) {
+                    var collapsed = crumb.hasStyleClass("collapsed"); 
+                    if (collapsedRun && collapsed) {
+                        crumb.addStyleClass("hidden");
+                        crumb.removeStyleClass("compact");
+                        crumb.removeStyleClass("collapsed");
+
+                        if (crumb.hasStyleClass("start")) {
+                            crumb.removeStyleClass("start");
+                            newStartNeeded = true;
+                        }
+
+                        if (crumb.hasStyleClass("end")) {
+                            crumb.removeStyleClass("end");
+                            newEndNeeded = true;
+                        }
+
+                        continue;
+                    }
+
+                    collapsedRun = collapsed;
+
+                    if (newEndNeeded) {
+                        newEndNeeded = false;
+                        crumb.addStyleClass("end");
+                    }
+                } else
+                    collapsedRun = true;
+                crumb = crumb.nextSibling;
+            }
+
+            if (newStartNeeded) {
+                crumb = crumbs.lastChild;
+                while (crumb) {
+                    if (!crumb.hasStyleClass("hidden")) {
+                        crumb.addStyleClass("start");
+                        break;
+                    }
+                    crumb = crumb.previousSibling;
+                }
+            }
+        }
+
+        function compact(crumb)
+        {
+            if (crumb.hasStyleClass("hidden"))
+                return;
+            crumb.addStyleClass("compact");
+        }
+
+        function collapse(crumb, dontCoalesce)
+        {
+            if (crumb.hasStyleClass("hidden"))
+                return;
+            crumb.addStyleClass("collapsed");
+            crumb.removeStyleClass("compact");
+            if (!dontCoalesce)
+                coalesceCollapsedCrumbs();
+        }
+
+        function compactDimmed(crumb)
+        {
+            if (crumb.hasStyleClass("dimmed"))
+                compact(crumb);
+        }
+
+        function collapseDimmed(crumb)
+        {
+            if (crumb.hasStyleClass("dimmed"))
+                collapse(crumb);
+        }
+
+        if (!focusedCrumb) {
+            // When not focused on a crumb we can be biased and collapse less important
+            // crumbs that the user might not care much about.
+
+            // Compact child crumbs.
+            if (makeCrumbsSmaller(compact, ChildSide))
+                return;
+
+            // Collapse child crumbs.
+            if (makeCrumbsSmaller(collapse, ChildSide))
+                return;
+
+            // Compact dimmed ancestor crumbs.
+            if (makeCrumbsSmaller(compactDimmed, AncestorSide))
+                return;
+
+            // Collapse dimmed ancestor crumbs.
+            if (makeCrumbsSmaller(collapseDimmed, AncestorSide))
+                return;
+        }
+
+        // Compact ancestor crumbs, or from both sides if focused.
+        if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide)))
+            return;
+
+        // Collapse ancestor crumbs, or from both sides if focused.
+        if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide)))
+            return;
+
+        if (!selectedCrumb)
+            return;
+
+        // Compact the selected crumb.
+        compact(selectedCrumb);
+        if (crumbsAreSmallerThanContainer())
+            return;
+
+        // Collapse the selected crumb as a last resort. Pass true to prevent coalescing.
+        collapse(selectedCrumb, true);
+    },
+
+    updateStyles: function(forceUpdate)
+    {
+        var stylesSidebarPane = this.sidebarPanes.styles;
+        if (!stylesSidebarPane.expanded || !stylesSidebarPane.needsUpdate)
+            return;
+
+        stylesSidebarPane.update(this.focusedDOMNode, null, forceUpdate);
+        stylesSidebarPane.needsUpdate = false;
+    },
+
+    updateMetrics: function()
+    {
+        var metricsSidebarPane = this.sidebarPanes.metrics;
+        if (!metricsSidebarPane.expanded || !metricsSidebarPane.needsUpdate)
+            return;
+
+        metricsSidebarPane.update(this.focusedDOMNode);
+        metricsSidebarPane.needsUpdate = false;
+    },
+
+    updateProperties: function()
+    {
+        var propertiesSidebarPane = this.sidebarPanes.properties;
+        if (!propertiesSidebarPane.expanded || !propertiesSidebarPane.needsUpdate)
+            return;
+
+        propertiesSidebarPane.update(this.focusedDOMNode);
+        propertiesSidebarPane.needsUpdate = false;
+    },
+
+    handleKeyEvent: function(event)
+    {
+        this.treeOutline.handleKeyEvent(event);
+    },
+
+    handleCopyEvent: function(event)
+    {
+        // Don't prevent the normal copy if the user has a selection.
+        if (!window.getSelection().isCollapsed)
+            return;
+
+        switch (this.focusedDOMNode.nodeType) {
+            case Node.ELEMENT_NODE:
+                // TODO: Introduce InspectorController.copyEvent that pushes appropriate markup into the clipboard.
+                var data = null;
+                break;
+
+            case Node.COMMENT_NODE:
+                var data = "<!--" + this.focusedDOMNode.nodeValue + "-->";
+                break;
+
+            default:
+            case Node.TEXT_NODE:
+                var data = this.focusedDOMNode.nodeValue;
+        }
+
+        event.clipboardData.clearData();
+        event.preventDefault();
+
+        if (data)
+            event.clipboardData.setData("text/plain", data);
+    },
+
+    rightSidebarResizerDragStart: function(event)
+    {
+        WebInspector.elementDragStart(this.sidebarElement, this.rightSidebarResizerDrag.bind(this), this.rightSidebarResizerDragEnd.bind(this), event, "col-resize");
+    },
+
+    rightSidebarResizerDragEnd: function(event)
+    {
+        WebInspector.elementDragEnd(event);
+    },
+
+    rightSidebarResizerDrag: function(event)
+    {
+        var x = event.pageX;
+        var newWidth = Number.constrain(window.innerWidth - x, Preferences.minElementsSidebarWidth, window.innerWidth * 0.66);
+
+        this.sidebarElement.style.width = newWidth + "px";
+        this.contentElement.style.right = newWidth + "px";
+        this.sidebarResizeElement.style.right = (newWidth - 3) + "px";
+
+        this.treeOutline.updateSelection();
+
+        event.preventDefault();
+    },
+
+    _nodeSearchButtonClicked: function(event)
+    {
+        InspectorController.toggleNodeSearch();
+
+        this.nodeSearchButton.toggled = InspectorController.searchingForNode();
+    }
+}
+
+WebInspector.ElementsPanel.prototype.__proto__ = WebInspector.Panel.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsTreeOutline.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsTreeOutline.js
new file mode 100644
index 0000000..08ba1c2
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsTreeOutline.js
@@ -0,0 +1,742 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2008 Matt Lilek <[email protected]>
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.ElementsTreeOutline = function() {
+    this.element = document.createElement("ol");
+    this.element.addEventListener("mousedown", this._onmousedown.bind(this), false);
+    this.element.addEventListener("dblclick", this._ondblclick.bind(this), false);
+    this.element.addEventListener("mousemove", this._onmousemove.bind(this), false);
+    this.element.addEventListener("mouseout", this._onmouseout.bind(this), false);
+
+    TreeOutline.call(this, this.element);
+
+    this.includeRootDOMNode = true;
+    this.selectEnabled = false;
+    this.rootDOMNode = null;
+    this.focusedDOMNode = null;
+}
+
+WebInspector.ElementsTreeOutline.prototype = {
+    get rootDOMNode()
+    {
+        return this._rootDOMNode;
+    },
+
+    set rootDOMNode(x)
+    {
+        if (this._rootDOMNode === x)
+            return;
+
+        this._rootDOMNode = x;
+
+        this.update();
+    },
+
+    get focusedDOMNode()
+    {
+        return this._focusedDOMNode;
+    },
+
+    set focusedDOMNode(x)
+    {
+        if (this._focusedDOMNode === x) {
+            this.revealAndSelectNode(x);
+            return;
+        }
+
+        this._focusedDOMNode = x;
+
+        this.revealAndSelectNode(x);
+
+        // The revealAndSelectNode() method might find a different element if there is inlined text,
+        // and the select() call would change the focusedDOMNode and reenter this setter. So to
+        // avoid calling focusedNodeChanged() twice, first check if _focusedDOMNode is the same
+        // node as the one passed in.
+        if (this._focusedDOMNode === x) {
+            this.focusedNodeChanged();
+
+            if (x && !this.suppressSelectHighlight) {
+                InspectorController.highlightDOMNode(x.id);
+
+                if ("_restorePreviousHighlightNodeTimeout" in this)
+                    clearTimeout(this._restorePreviousHighlightNodeTimeout);
+
+                function restoreHighlightToHoveredNode()
+                {
+                    var hoveredNode = WebInspector.hoveredDOMNode;
+                    if (hoveredNode)
+                        InspectorController.highlightDOMNode(hoveredNode.id);
+                    else
+                        InspectorController.hideDOMNodeHighlight();
+                }
+
+                this._restorePreviousHighlightNodeTimeout = setTimeout(restoreHighlightToHoveredNode, 2000);
+            }
+        }
+    },
+
+    update: function()
+    {
+        this.removeChildren();
+
+        if (!this.rootDOMNode)
+            return;
+
+        var treeElement;
+        if (this.includeRootDOMNode) {
+            treeElement = new WebInspector.ElementsTreeElement(this.rootDOMNode);
+            treeElement.selectable = this.selectEnabled;
+            this.appendChild(treeElement);
+        } else {
+            // FIXME: this could use findTreeElement to reuse a tree element if it already exists
+            var node = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(this.rootDOMNode) : this.rootDOMNode.firstChild);
+            while (node) {
+                treeElement = new WebInspector.ElementsTreeElement(node);
+                treeElement.selectable = this.selectEnabled;
+                this.appendChild(treeElement);
+                node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling;
+            }
+        }
+
+        this.updateSelection();
+    },
+
+    updateSelection: function()
+    {
+        if (!this.selectedTreeElement)
+            return;
+        var element = this.treeOutline.selectedTreeElement;
+        element.updateSelection();
+    },
+
+    focusedNodeChanged: function(forceUpdate) {},
+
+    findTreeElement: function(node)
+    {
+        var treeElement = TreeOutline.prototype.findTreeElement.call(this, node, isAncestorNode, parentNode);
+        if (!treeElement && node.nodeType === Node.TEXT_NODE) {
+            // The text node might have been inlined if it was short, so try to find the parent element.
+            treeElement = TreeOutline.prototype.findTreeElement.call(this, node.parentNode, isAncestorNode, parentNode);
+        }
+
+        return treeElement;
+    },
+
+    revealAndSelectNode: function(node)
+    {
+        if (!node)
+            return;
+
+        var treeElement = this.findTreeElement(node);
+        if (!treeElement)
+            return;
+
+        treeElement.reveal();
+        treeElement.select();
+    },
+
+    _treeElementFromEvent: function(event)
+    {
+        var root = this.element;
+
+        // We choose this X coordinate based on the knowledge that our list
+        // items extend nearly to the right edge of the outer <ol>.
+        var x = root.totalOffsetLeft + root.offsetWidth - 20;
+
+        var y = event.pageY;
+
+        // Our list items have 1-pixel cracks between them vertically. We avoid
+        // the cracks by checking slightly above and slightly below the mouse
+        // and seeing if we hit the same element each time.
+        var elementUnderMouse = this.treeElementFromPoint(x, y);
+        var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
+        var element;
+        if (elementUnderMouse === elementAboveMouse)
+            element = elementUnderMouse;
+        else
+            element = this.treeElementFromPoint(x, y + 2);
+
+        return element;
+    },
+
+    _ondblclick: function(event)
+    {
+        var element = this._treeElementFromEvent(event);
+
+        if (!element || !element.ondblclick)
+            return;
+
+        element.ondblclick(element, event);
+    },
+
+    _onmousedown: function(event)
+    {
+        var element = this._treeElementFromEvent(event);
+
+        if (!element || element.isEventWithinDisclosureTriangle(event))
+            return;
+
+        element.select();
+    },
+
+    _onmousemove: function(event)
+    {
+        if (this._previousHoveredElement) {
+            this._previousHoveredElement.hovered = false;
+            delete this._previousHoveredElement;
+        }
+
+        var element = this._treeElementFromEvent(event);
+        if (element && !element.elementCloseTag) {
+            element.hovered = true;
+            this._previousHoveredElement = element;
+        }
+
+        WebInspector.hoveredDOMNode = (element && !element.elementCloseTag ? element.representedObject : null);
+    },
+
+    _onmouseout: function(event)
+    {
+        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
+        if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element))
+            return;
+
+        if (this._previousHoveredElement) {
+            this._previousHoveredElement.hovered = false;
+            delete this._previousHoveredElement;
+        }
+
+        WebInspector.hoveredDOMNode = null;
+    }
+}
+
+WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype;
+
+WebInspector.ElementsTreeElement = function(node)
+{
+    var hasChildren = Preferences.ignoreWhitespace ? (firstChildSkippingWhitespace.call(node) ? true : false) : node.hasChildNodes();
+    var titleInfo = nodeTitleInfo.call(node, hasChildren, WebInspector.linkifyURL);
+
+    if (titleInfo.hasChildren) 
+        this.whitespaceIgnored = Preferences.ignoreWhitespace;
+
+    // The title will be updated in onattach.
+    TreeElement.call(this, "", node, titleInfo.hasChildren);
+
+    if (this.representedObject.nodeType == Node.ELEMENT_NODE)
+        this._canAddAttributes = true;
+}
+
+WebInspector.ElementsTreeElement.prototype = {
+    get highlighted()
+    {
+        return this._highlighted;
+    },
+
+    set highlighted(x)
+    {
+        if (this._highlighted === x)
+            return;
+
+        this._highlighted = x;
+
+        if (this.listItemElement) {
+            if (x)
+                this.listItemElement.addStyleClass("highlighted");
+            else
+                this.listItemElement.removeStyleClass("highlighted");
+        }
+    },
+
+    get hovered()
+    {
+        return this._hovered;
+    },
+
+    set hovered(x)
+    {
+        if (this._hovered === x)
+            return;
+
+        this._hovered = x;
+
+        if (this.listItemElement) {
+            if (x) {
+                this.updateSelection();
+                this.listItemElement.addStyleClass("hovered");
+            } else
+                this.listItemElement.removeStyleClass("hovered");
+            if (this._canAddAttributes)
+                this.toggleNewAttributeButton();
+        }
+    },
+
+    toggleNewAttributeButton: function()
+    {
+        function removeWhenEditing(event)
+        {
+            if (this._addAttributeElement && this._addAttributeElement.parentNode)
+                this._addAttributeElement.parentNode.removeChild(this._addAttributeElement);
+            delete this._addAttributeElement;
+        }
+
+        if (!this._addAttributeElement && this._hovered && !this._editing) {
+            var span = document.createElement("span");
+            span.className = "add-attribute";
+            span.textContent = "\u2026";
+            span.addEventListener("dblclick", removeWhenEditing.bind(this), false);
+            this._addAttributeElement = span;
+
+            var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0];
+            this._insertInLastAttributePosition(tag, span);
+        } else if (!this._hovered && this._addAttributeElement) {
+            if (this._addAttributeElement.parentNode)
+                this._addAttributeElement.parentNode.removeChild(this._addAttributeElement);
+            delete this._addAttributeElement;
+        }
+    },
+    
+    updateSelection: function()
+    {
+        var listItemElement = this.listItemElement;
+        if (!listItemElement)
+            return;
+
+        if (document.body.offsetWidth <= 0) {
+            // The stylesheet hasn't loaded yet or the window is closed,
+            // so we can't calculate what is need. Return early.
+            return;
+        }
+
+        if (!this.selectionElement) {
+            this.selectionElement = document.createElement("div");
+            this.selectionElement.className = "selection selected";
+            listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild);
+        }
+
+        this.selectionElement.style.height = listItemElement.offsetHeight + "px";
+    },
+
+    onattach: function()
+    {
+        this.listItemElement.addEventListener("mousedown", this.onmousedown.bind(this), false);
+
+        if (this._highlighted)
+            this.listItemElement.addStyleClass("highlighted");
+
+        if (this._hovered) {
+            this.updateSelection();
+            this.listItemElement.addStyleClass("hovered");
+        }
+
+        this._updateTitle();
+
+        this._preventFollowingLinksOnDoubleClick();
+    },
+
+    _preventFollowingLinksOnDoubleClick: function()
+    {
+        var links = this.listItemElement.querySelectorAll("li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-external-link, li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-resource-link");
+        if (!links)
+            return;
+
+        for (var i = 0; i < links.length; ++i)
+            links[i].preventFollowOnDoubleClick = true;
+    },
+
+    onpopulate: function()
+    {
+        if (this.children.length || this.whitespaceIgnored !== Preferences.ignoreWhitespace)
+            return;
+
+        this.whitespaceIgnored = Preferences.ignoreWhitespace;
+
+        this.updateChildren();
+    },
+    
+    updateChildren: function(fullRefresh)
+    {
+        WebInspector.domAgent.getChildNodesAsync(this.representedObject, this._updateChildren.bind(this, fullRefresh));
+    },
+
+    _updateChildren: function(fullRefresh)
+    {
+        if (fullRefresh) {
+            var selectedTreeElement = this.treeOutline.selectedTreeElement;
+            if (selectedTreeElement && selectedTreeElement.hasAncestor(this))
+                this.select();
+            this.removeChildren();
+        }
+
+        var treeElement = this;
+        var treeChildIndex = 0;
+
+        function updateChildrenOfNode(node)
+        {
+            var treeOutline = treeElement.treeOutline;
+            var child = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(node) : node.firstChild);
+            while (child) {
+                var currentTreeElement = treeElement.children[treeChildIndex];
+                if (!currentTreeElement || currentTreeElement.representedObject !== child) {
+                    // Find any existing element that is later in the children list.
+                    var existingTreeElement = null;
+                    for (var i = (treeChildIndex + 1); i < treeElement.children.length; ++i) {
+                        if (treeElement.children[i].representedObject === child) {
+                            existingTreeElement = treeElement.children[i];
+                            break;
+                        }
+                    }
+
+                    if (existingTreeElement && existingTreeElement.parent === treeElement) {
+                        // If an existing element was found and it has the same parent, just move it.
+                        var wasSelected = existingTreeElement.selected;
+                        treeElement.removeChild(existingTreeElement);
+                        treeElement.insertChild(existingTreeElement, treeChildIndex);
+                        if (wasSelected)
+                            existingTreeElement.select();
+                    } else {
+                        // No existing element found, insert a new element.
+                        var newElement = new WebInspector.ElementsTreeElement(child);
+                        newElement.selectable = treeOutline.selectEnabled;
+                        treeElement.insertChild(newElement, treeChildIndex);
+                    }
+                }
+
+                child = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(child) : child.nextSibling;
+                ++treeChildIndex;
+            }
+        }
+
+        // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent.
+        for (var i = (this.children.length - 1); i >= 0; --i) {
+            if ("elementCloseTag" in this.children[i])
+                continue;
+
+            var currentChild = this.children[i];
+            var currentNode = currentChild.representedObject;
+            var currentParentNode = currentNode.parentNode;
+
+            if (currentParentNode === this.representedObject)
+                continue;
+
+            var selectedTreeElement = this.treeOutline.selectedTreeElement;
+            if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild)))
+                this.select();
+
+            this.removeChildAtIndex(i);
+        }
+
+        updateChildrenOfNode(this.representedObject);
+
+        var lastChild = this.children[this.children.length - 1];
+        if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild || !lastChild.elementCloseTag)) {
+            var title = "<span class=\"webkit-html-tag close\">&lt;/" + this.representedObject.nodeName.toLowerCase().escapeHTML() + "&gt;</span>";
+            var item = new TreeElement(title, null, false);
+            item.selectable = false;
+            item.elementCloseTag = true;
+            this.appendChild(item);
+        }
+    },
+
+    onexpand: function()
+    {
+        this.treeOutline.updateSelection();
+    },
+
+    oncollapse: function()
+    {
+        this.treeOutline.updateSelection();
+    },
+
+    onreveal: function()
+    {
+        if (this.listItemElement)
+            this.listItemElement.scrollIntoViewIfNeeded(false);
+    },
+
+    onselect: function()
+    {
+        this.treeOutline.focusedDOMNode = this.representedObject;
+        this.updateSelection();
+    },
+
+    onmousedown: function(event)
+    {
+        if (this._editing)
+            return;
+
+        // Prevent selecting the nearest word on double click.
+        if (event.detail >= 2)
+            event.preventDefault();
+    },
+
+    ondblclick: function(treeElement, event)
+    {
+        if (this._editing)
+            return;
+
+        if (this._startEditing(event, treeElement))
+            return;
+
+        if (this.treeOutline.panel) {
+            this.treeOutline.rootDOMNode = this.representedObject.parentNode;
+            this.treeOutline.focusedDOMNode = this.representedObject;
+        }
+
+        if (this.hasChildren && !this.expanded)
+            this.expand();
+    },
+
+    _insertInLastAttributePosition: function(tag, node)
+    {
+        if (tag.getElementsByClassName("webkit-html-attribute").length > 0)
+            tag.insertBefore(node, tag.lastChild);
+        else {
+            var nodeName = tag.textContent.match(/^<(.*?)>$/)[1];
+            tag.textContent = '';
+            tag.appendChild(document.createTextNode('<'+nodeName));
+            tag.appendChild(node);
+            tag.appendChild(document.createTextNode('>'));
+        }
+    },
+
+    _startEditing: function(event, treeElement)
+    {
+        if (this.treeOutline.focusedDOMNode != this.representedObject)
+            return;
+
+        if (this.representedObject.nodeType != Node.ELEMENT_NODE && this.representedObject.nodeType != Node.TEXT_NODE)
+            return false;
+
+        var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node");
+        if (textNode)
+            return this._startEditingTextNode(textNode);
+
+        var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute");
+        if (attribute)
+            return this._startEditingAttribute(attribute, event.target);
+
+        var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute");
+        if (newAttribute)
+            return this._addNewAttribute(treeElement.listItemElement);
+
+        return false;
+    },
+
+    _addNewAttribute: function(listItemElement)
+    {
+        var attr = document.createElement("span");
+        attr.className = "webkit-html-attribute";
+        attr.style.marginLeft = "2px"; // overrides the .editing margin rule
+        attr.style.marginRight = "2px"; // overrides the .editing margin rule
+        var name = document.createElement("span");
+        name.className = "webkit-html-attribute-name new-attribute";
+        name.textContent = " ";
+        var value = document.createElement("span");
+        value.className = "webkit-html-attribute-value";
+        attr.appendChild(name);
+        attr.appendChild(value);
+
+        var tag = listItemElement.getElementsByClassName("webkit-html-tag")[0];
+        this._insertInLastAttributePosition(tag, attr);
+        return this._startEditingAttribute(attr, attr);
+    },
+
+    _triggerEditAttribute: function(attributeName)
+    {
+        var attributeElements = this.listItemElement.getElementsByClassName("webkit-html-attribute-name");
+        for (var i = 0, len = attributeElements.length; i < len; ++i) {
+            if (attributeElements[i].textContent === attributeName) {
+                for (var elem = attributeElements[i].nextSibling; elem; elem = elem.nextSibling) {
+                    if (elem.nodeType !== Node.ELEMENT_NODE)
+                        continue;
+
+                    if (elem.hasStyleClass("webkit-html-attribute-value"))
+                        return this._startEditingAttribute(attributeElements[i].parentNode, elem);
+                }
+            }
+        }
+    },
+
+    _startEditingAttribute: function(attribute, elementForSelection)
+    {
+        if (WebInspector.isBeingEdited(attribute))
+            return true;
+
+        var attributeNameElement = attribute.getElementsByClassName("webkit-html-attribute-name")[0];
+        if (!attributeNameElement)
+            return false;
+
+        var attributeName = attributeNameElement.innerText;
+
+        function removeZeroWidthSpaceRecursive(node)
+        {
+            if (node.nodeType === Node.TEXT_NODE) {
+                node.nodeValue = node.nodeValue.replace(/\u200B/g, "");
+                return;
+            }
+
+            if (node.nodeType !== Node.ELEMENT_NODE)
+                return;
+
+            for (var child = node.firstChild; child; child = child.nextSibling)
+                removeZeroWidthSpaceRecursive(child);
+        }
+
+        // Remove zero-width spaces that were added by nodeTitleInfo.
+        removeZeroWidthSpaceRecursive(attribute);
+
+        this._editing = true;
+
+        WebInspector.startEditing(attribute, this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName);
+        window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1);
+
+        return true;
+    },
+
+    _startEditingTextNode: function(textNode)
+    {
+        if (WebInspector.isBeingEdited(textNode))
+            return true;
+
+        this._editing = true;
+
+        WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this));
+        window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1);
+
+        return true;
+    },
+
+    _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection)
+    {
+        delete this._editing;
+
+        // Before we do anything, determine where we should move
+        // next based on the current element's settings
+        var moveToAttribute;
+        var newAttribute;
+        if (moveDirection) {
+            var found = false;
+            var attributes = this.representedObject.attributes;
+            for (var i = 0, len = attributes.length; i < len; ++i) {
+                if (attributes[i].name === attributeName) {
+                    found = true;
+                    if (moveDirection === "backward" && i > 0)
+                        moveToAttribute = attributes[i - 1].name;
+                    else if (moveDirection === "forward" && i < attributes.length - 1)
+                        moveToAttribute = attributes[i + 1].name;
+                    else if (moveDirection === "forward" && i === attributes.length - 1)
+                        newAttribute = true;
+                }
+            }
+
+            if (!found && moveDirection === "backward")
+                moveToAttribute = attributes[attributes.length - 1].name;
+            else if (!found && moveDirection === "forward" && !/^\s*$/.test(newText))
+                newAttribute = true;
+        }
+
+        function moveToNextAttributeIfNeeded() {
+            if (moveToAttribute)
+                this._triggerEditAttribute(moveToAttribute);
+            else if (newAttribute)
+                this._addNewAttribute(this.listItemElement);
+        }
+
+        var parseContainerElement = document.createElement("span");
+        parseContainerElement.innerHTML = "<span " + newText + "></span>";
+        var parseElement = parseContainerElement.firstChild;
+
+        if (!parseElement) {
+            this._editingCancelled(element, attributeName);
+            moveToNextAttributeIfNeeded.call(this);
+            return;
+        }
+
+        if (!parseElement.hasAttributes()) {
+            this.representedObject.removeAttribute(attributeName);
+            this._updateTitle();
+            moveToNextAttributeIfNeeded.call(this);
+            return;
+        }
+
+        var foundOriginalAttribute = false;
+        for (var i = 0; i < parseElement.attributes.length; ++i) {
+            var attr = parseElement.attributes[i];
+            foundOriginalAttribute = foundOriginalAttribute || attr.name === attributeName;
+            try {
+                this.representedObject.setAttribute(attr.name, attr.value);
+            } catch(e) {} // ignore invalid attribute (innerHTML doesn't throw errors, but this can)
+        }
+
+        if (!foundOriginalAttribute)
+            this.representedObject.removeAttribute(attributeName);
+
+        this._updateTitle();
+
+        this.treeOutline.focusedNodeChanged(true);
+
+        moveToNextAttributeIfNeeded.call(this);
+    },
+
+    _textNodeEditingCommitted: function(element, newText)
+    {
+        delete this._editing;
+
+        var textNode;
+        if (this.representedObject.nodeType == Node.ELEMENT_NODE) {
+            // We only show text nodes inline in elements if the element only
+            // has a single child, and that child is a text node.
+            textNode = this.representedObject.firstChild;
+        } else if (this.representedObject.nodeType == Node.TEXT_NODE)
+            textNode = this.representedObject;
+
+        textNode.nodeValue = newText;
+        this._updateTitle();
+    },
+
+    _editingCancelled: function(element, context)
+    {
+        delete this._editing;
+
+        this._updateTitle();
+    },
+
+    _updateTitle: function()
+    {
+        var title = nodeTitleInfo.call(this.representedObject, this.hasChildren, WebInspector.linkifyURL).title;
+        this.title = "<span class=\"highlight\">" + title + "</span>";
+        delete this.selectionElement;
+        this.updateSelection();
+        this._preventFollowingLinksOnDoubleClick();
+    },
+}
+
+WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/FontView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/FontView.js
new file mode 100644
index 0000000..4e1c931
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/FontView.js
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.FontView = function(resource)
+{
+    WebInspector.ResourceView.call(this, resource);
+
+    this.element.addStyleClass("font");
+
+    var uniqueFontName = "WebInspectorFontPreview" + this.resource.identifier;
+
+    this.fontStyleElement = document.createElement("style");
+    this.fontStyleElement.textContent = "@font-face { font-family: \"" + uniqueFontName + "\"; src: url(" + this.resource.url + "); }";
+    document.getElementsByTagName("head").item(0).appendChild(this.fontStyleElement);
+
+    this.fontPreviewElement = document.createElement("div");
+    this.fontPreviewElement.className = "preview";
+    this.contentElement.appendChild(this.fontPreviewElement);
+
+    this.fontPreviewElement.style.setProperty("font-family", uniqueFontName, null);
+    this.fontPreviewElement.innerHTML = "ABCDEFGHIJKLM<br>NOPQRSTUVWXYZ<br>abcdefghijklm<br>nopqrstuvwxyz<br>1234567890";
+
+    this.updateFontPreviewSize();
+}
+
+WebInspector.FontView.prototype = {
+    show: function(parentElement)
+    {
+        WebInspector.ResourceView.prototype.show.call(this, parentElement);
+        this.updateFontPreviewSize();
+    },
+
+    resize: function()
+    {
+        this.updateFontPreviewSize();
+    },
+
+    updateFontPreviewSize: function ()
+    {
+        if (!this.fontPreviewElement || !this.visible)
+            return;
+
+        this.fontPreviewElement.removeStyleClass("preview");
+
+        var measureFontSize = 50;
+        this.fontPreviewElement.style.setProperty("position", "absolute", null);
+        this.fontPreviewElement.style.setProperty("font-size", measureFontSize + "px", null);
+        this.fontPreviewElement.style.removeProperty("height");
+
+        var height = this.fontPreviewElement.offsetHeight;
+        var width = this.fontPreviewElement.offsetWidth;
+
+        var containerWidth = this.contentElement.offsetWidth;
+
+        // Subtract some padding. This should match the padding in the CSS plus room for the scrollbar.
+        containerWidth -= 40;
+
+        if (!height || !width || !containerWidth) {
+            this.fontPreviewElement.style.removeProperty("font-size");
+            this.fontPreviewElement.style.removeProperty("position");
+            this.fontPreviewElement.addStyleClass("preview");
+            return;
+        }
+
+        var lineCount = this.fontPreviewElement.getElementsByTagName("br").length + 1;
+        var realLineHeight = Math.floor(height / lineCount);
+        var fontSizeLineRatio = measureFontSize / realLineHeight;
+        var widthRatio = containerWidth / width;
+        var finalFontSize = Math.floor(realLineHeight * widthRatio * fontSizeLineRatio) - 1;
+
+        this.fontPreviewElement.style.setProperty("font-size", finalFontSize + "px", null);
+        this.fontPreviewElement.style.setProperty("height", this.fontPreviewElement.offsetHeight + "px", null);
+        this.fontPreviewElement.style.removeProperty("position");
+
+        this.fontPreviewElement.addStyleClass("preview");
+    }
+}
+
+WebInspector.FontView.prototype.__proto__ = WebInspector.ResourceView.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ImageView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ImageView.js
new file mode 100644
index 0000000..001ffdde
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ImageView.js
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.ImageView = function(resource)
+{
+    WebInspector.ResourceView.call(this, resource);
+
+    this.element.addStyleClass("image");
+
+    var container = document.createElement("div");
+    container.className = "image";
+    this.contentElement.appendChild(container);
+
+    this.imagePreviewElement = document.createElement("img");
+    this.imagePreviewElement.setAttribute("src", this.resource.url);
+
+    container.appendChild(this.imagePreviewElement);
+
+    container = document.createElement("div");
+    container.className = "info";
+    this.contentElement.appendChild(container);
+
+    var imageNameElement = document.createElement("h1");
+    imageNameElement.className = "title";
+    imageNameElement.textContent = this.resource.displayName;
+    container.appendChild(imageNameElement);
+
+    var infoListElement = document.createElement("dl");
+    infoListElement.className = "infoList";
+
+    var imageProperties = [
+        { name: WebInspector.UIString("Dimensions"), value: WebInspector.UIString("%d × %d", this.imagePreviewElement.naturalWidth, this.imagePreviewElement.height) },
+        { name: WebInspector.UIString("File size"), value: Number.bytesToString(this.resource.contentLength, WebInspector.UIString.bind(WebInspector)) },
+        { name: WebInspector.UIString("MIME type"), value: this.resource.mimeType }
+    ];
+
+    var listHTML = '';
+    for (var i = 0; i < imageProperties.length; ++i)
+        listHTML += "<dt>" + imageProperties[i].name + "</dt><dd>" + imageProperties[i].value + "</dd>";
+
+    infoListElement.innerHTML = listHTML;
+    container.appendChild(infoListElement);
+}
+
+WebInspector.ImageView.prototype = {
+    
+}
+
+WebInspector.ImageView.prototype.__proto__ = WebInspector.ResourceView.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScript.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScript.js
new file mode 100644
index 0000000..b3d9566
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScript.js
@@ -0,0 +1,1133 @@
+/*
+ * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+var InjectedScript = {};
+
+// Called from within InspectorController on the 'inspected page' side.
+InjectedScript.reset = function()
+{
+    InjectedScript._styles = {};
+    InjectedScript._styleRules = {};
+    InjectedScript._lastStyleId = 0;
+    InjectedScript._lastStyleRuleId = 0;
+    InjectedScript._searchResults = [];
+    InjectedScript._includedInSearchResultsPropertyName = "__includedInInspectorSearchResults";
+}
+
+InjectedScript.reset();
+
+InjectedScript.dispatch = function(methodName, args)
+{
+    var result = InjectedScript[methodName].apply(InjectedScript, JSON.parse(args));
+    return JSON.stringify(result);
+}
+
+InjectedScript.getStyles = function(nodeId, authorOnly)
+{
+    var node = InjectedScript._nodeForId(nodeId);
+    if (!node)
+        return false;
+    var matchedRules = InjectedScript._window().getMatchedCSSRules(node, "", authorOnly);
+    var matchedCSSRules = [];
+    for (var i = 0; matchedRules && i < matchedRules.length; ++i)
+        matchedCSSRules.push(InjectedScript._serializeRule(matchedRules[i]));
+
+    var styleAttributes = {};
+    var attributes = node.attributes;
+    for (var i = 0; attributes && i < attributes.length; ++i) {
+        if (attributes[i].style)
+            styleAttributes[attributes[i].name] = InjectedScript._serializeStyle(attributes[i].style, true);
+    }
+    var result = {};
+    result.inlineStyle = InjectedScript._serializeStyle(node.style, true);
+    result.computedStyle = InjectedScript._serializeStyle(InjectedScript._window().getComputedStyle(node));
+    result.matchedCSSRules = matchedCSSRules;
+    result.styleAttributes = styleAttributes;
+    return result;
+}
+
+InjectedScript.getComputedStyle = function(nodeId)
+{
+    var node = InjectedScript._nodeForId(nodeId);
+    if (!node)
+        return false;
+    return InjectedScript._serializeStyle(InjectedScript._window().getComputedStyle(node));
+}
+
+InjectedScript.getInlineStyle = function(nodeId)
+{
+    var node = InjectedScript._nodeForId(nodeId);
+    if (!node)
+        return false;
+    return InjectedScript._serializeStyle(node.style, true);
+}
+
+InjectedScript.applyStyleText = function(styleId, styleText, propertyName)
+{
+    var style = InjectedScript._styles[styleId];
+    if (!style)
+        return false;
+
+    var styleTextLength = styleText.length;
+
+    // Create a new element to parse the user input CSS.
+    var parseElement = document.createElement("span");
+    parseElement.setAttribute("style", styleText);
+
+    var tempStyle = parseElement.style;
+    if (tempStyle.length || !styleTextLength) {
+        // The input was parsable or the user deleted everything, so remove the
+        // original property from the real style declaration. If this represents
+        // a shorthand remove all the longhand properties.
+        if (style.getPropertyShorthand(propertyName)) {
+            var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName);
+            for (var i = 0; i < longhandProperties.length; ++i)
+                style.removeProperty(longhandProperties[i]);
+        } else
+            style.removeProperty(propertyName);
+    }
+
+    // Notify caller that the property was successfully deleted.
+    if (!styleTextLength)
+        return [null, [propertyName]];
+
+    if (!tempStyle.length)
+        return false;
+
+    // Iterate of the properties on the test element's style declaration and
+    // add them to the real style declaration. We take care to move shorthands.
+    var foundShorthands = {};
+    var changedProperties = [];
+    var uniqueProperties = InjectedScript._getUniqueStyleProperties(tempStyle);
+    for (var i = 0; i < uniqueProperties.length; ++i) {
+        var name = uniqueProperties[i];
+        var shorthand = tempStyle.getPropertyShorthand(name);
+
+        if (shorthand && shorthand in foundShorthands)
+            continue;
+
+        if (shorthand) {
+            var value = InjectedScript._getShorthandValue(tempStyle, shorthand);
+            var priority = InjectedScript._getShorthandPriority(tempStyle, shorthand);
+            foundShorthands[shorthand] = true;
+        } else {
+            var value = tempStyle.getPropertyValue(name);
+            var priority = tempStyle.getPropertyPriority(name);
+        }
+
+        // Set the property on the real style declaration.
+        style.setProperty((shorthand || name), value, priority);
+        changedProperties.push(shorthand || name);
+    }
+    return [InjectedScript._serializeStyle(style, true), changedProperties];
+}
+
+InjectedScript.setStyleText = function(style, cssText)
+{
+    style.cssText = cssText;
+}
+
+InjectedScript.toggleStyleEnabled = function(styleId, propertyName, disabled)
+{
+    var style = InjectedScript._styles[styleId];
+    if (!style)
+        return false;
+
+    if (disabled) {
+        if (!style.__disabledPropertyValues || !style.__disabledPropertyPriorities) {
+            var inspectedWindow = InjectedScript._window();
+            style.__disabledProperties = new inspectedWindow.Object;
+            style.__disabledPropertyValues = new inspectedWindow.Object;
+            style.__disabledPropertyPriorities = new inspectedWindow.Object;
+        }
+
+        style.__disabledPropertyValues[propertyName] = style.getPropertyValue(propertyName);
+        style.__disabledPropertyPriorities[propertyName] = style.getPropertyPriority(propertyName);
+
+        if (style.getPropertyShorthand(propertyName)) {
+            var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName);
+            for (var i = 0; i < longhandProperties.length; ++i) {
+                style.__disabledProperties[longhandProperties[i]] = true;
+                style.removeProperty(longhandProperties[i]);
+            }
+        } else {
+            style.__disabledProperties[propertyName] = true;
+            style.removeProperty(propertyName);
+        }
+    } else if (style.__disabledProperties && style.__disabledProperties[propertyName]) {
+        var value = style.__disabledPropertyValues[propertyName];
+        var priority = style.__disabledPropertyPriorities[propertyName];
+
+        style.setProperty(propertyName, value, priority);
+        delete style.__disabledProperties[propertyName];
+        delete style.__disabledPropertyValues[propertyName];
+        delete style.__disabledPropertyPriorities[propertyName];
+    }
+    return InjectedScript._serializeStyle(style, true);
+}
+
+InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNodeId)
+{
+    var rule = InjectedScript._styleRules[ruleId];
+    if (!rule)
+        return false;
+
+    var selectedNode = InjectedScript._nodeForId(selectedNodeId);
+
+    try {
+        var stylesheet = rule.parentStyleSheet;
+        stylesheet.addRule(newContent);
+        var newRule = stylesheet.cssRules[stylesheet.cssRules.length - 1];
+        newRule.style.cssText = rule.style.cssText;
+
+        var parentRules = stylesheet.cssRules;
+        for (var i = 0; i < parentRules.length; ++i) {
+            if (parentRules[i] === rule) {
+                rule.parentStyleSheet.removeRule(i);
+                break;
+            }
+        }
+
+        return [InjectedScript._serializeRule(newRule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode)];
+    } catch(e) {
+        // Report invalid syntax.
+        return false;
+    }
+}
+
+InjectedScript.addStyleSelector = function(newContent, selectedNodeId)
+{
+    var stylesheet = InjectedScript.stylesheet;
+    if (!stylesheet) {
+        var inspectedDocument = InjectedScript._window().document;
+        var head = inspectedDocument.getElementsByTagName("head")[0];
+        var styleElement = inspectedDocument.createElement("style");
+        styleElement.type = "text/css";
+        head.appendChild(styleElement);
+        stylesheet = inspectedDocument.styleSheets[inspectedDocument.styleSheets.length - 1];
+        InjectedScript.stylesheet = stylesheet;
+    }
+
+    try {
+        stylesheet.addRule(newContent);
+    } catch (e) {
+        // Invalid Syntax for a Selector
+        return false;
+    }
+
+    var selectedNode = InjectedScript._nodeForId(selectedNodeId);
+    var rule = stylesheet.cssRules[stylesheet.cssRules.length - 1];
+    rule.__isViaInspector = true;
+
+    return [ InjectedScript._serializeRule(rule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode) ];
+}
+
+InjectedScript._doesSelectorAffectNode = function(selectorText, node)
+{
+    if (!node)
+        return false;
+    var nodes = node.ownerDocument.querySelectorAll(selectorText);
+    for (var i = 0; i < nodes.length; ++i) {
+        if (nodes[i] === node) {
+            return true;
+        }
+    }
+    return false;
+}
+
+InjectedScript.setStyleProperty = function(styleId, name, value)
+{
+    var style = InjectedScript._styles[styleId];
+    if (!style)
+        return false;
+
+    style.setProperty(name, value, "");
+    return true;
+}
+
+InjectedScript._serializeRule = function(rule)
+{
+    var parentStyleSheet = rule.parentStyleSheet;
+
+    var ruleValue = {};
+    ruleValue.selectorText = rule.selectorText;
+    if (parentStyleSheet) {
+        ruleValue.parentStyleSheet = {};
+        ruleValue.parentStyleSheet.href = parentStyleSheet.href;
+    }
+    ruleValue.isUserAgent = parentStyleSheet && !parentStyleSheet.ownerNode && !parentStyleSheet.href;
+    ruleValue.isUser = parentStyleSheet && parentStyleSheet.ownerNode && parentStyleSheet.ownerNode.nodeName == "#document";
+    ruleValue.isViaInspector = !!rule.__isViaInspector;
+
+    // Bind editable scripts only.
+    var doBind = !ruleValue.isUserAgent && !ruleValue.isUser;
+    ruleValue.style = InjectedScript._serializeStyle(rule.style, doBind);
+
+    if (doBind) {
+        if (!rule.id) {
+            rule.id = InjectedScript._lastStyleRuleId++;
+            InjectedScript._styleRules[rule.id] = rule;
+        }
+        ruleValue.id = rule.id;
+    }
+    return ruleValue;
+}
+
+InjectedScript._serializeStyle = function(style, doBind)
+{
+    var result = {};
+    result.width = style.width;
+    result.height = style.height;
+    result.__disabledProperties = style.__disabledProperties;
+    result.__disabledPropertyValues = style.__disabledPropertyValues;
+    result.__disabledPropertyPriorities = style.__disabledPropertyPriorities;
+    result.properties = [];
+    result.shorthandValues = {};
+    var foundShorthands = {};
+    for (var i = 0; i < style.length; ++i) {
+        var property = {};
+        var name = style[i];
+        property.name = name;
+        property.priority = style.getPropertyPriority(name);
+        property.implicit = style.isPropertyImplicit(name);
+        var shorthand =  style.getPropertyShorthand(name);
+        property.shorthand = shorthand;
+        if (shorthand && !(shorthand in foundShorthands)) {
+            foundShorthands[shorthand] = true;
+            result.shorthandValues[shorthand] = InjectedScript._getShorthandValue(style, shorthand);
+        }
+        property.value = style.getPropertyValue(name);
+        result.properties.push(property);
+    }
+    result.uniqueStyleProperties = InjectedScript._getUniqueStyleProperties(style);
+
+    if (doBind) {
+        if (!style.id) {
+            style.id = InjectedScript._lastStyleId++;
+            InjectedScript._styles[style.id] = style;
+        }
+        result.id = style.id;
+    }
+    return result;
+}
+
+InjectedScript._getUniqueStyleProperties = function(style)
+{
+    var properties = [];
+    var foundProperties = {};
+
+    for (var i = 0; i < style.length; ++i) {
+        var property = style[i];
+        if (property in foundProperties)
+            continue;
+        foundProperties[property] = true;
+        properties.push(property);
+    }
+
+    return properties;
+}
+
+
+InjectedScript._getLonghandProperties = function(style, shorthandProperty)
+{
+    var properties = [];
+    var foundProperties = {};
+
+    for (var i = 0; i < style.length; ++i) {
+        var individualProperty = style[i];
+        if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
+            continue;
+        foundProperties[individualProperty] = true;
+        properties.push(individualProperty);
+    }
+
+    return properties;
+}
+
+InjectedScript._getShorthandValue = function(style, shorthandProperty)
+{
+    var value = style.getPropertyValue(shorthandProperty);
+    if (!value) {
+        // Some shorthands (like border) return a null value, so compute a shorthand value.
+        // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed.
+
+        var foundProperties = {};
+        for (var i = 0; i < style.length; ++i) {
+            var individualProperty = style[i];
+            if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty)
+                continue;
+
+            var individualValue = style.getPropertyValue(individualProperty);
+            if (style.isPropertyImplicit(individualProperty) || individualValue === "initial")
+                continue;
+
+            foundProperties[individualProperty] = true;
+
+            if (!value)
+                value = "";
+            else if (value.length)
+                value += " ";
+            value += individualValue;
+        }
+    }
+    return value;
+}
+
+InjectedScript._getShorthandPriority = function(style, shorthandProperty)
+{
+    var priority = style.getPropertyPriority(shorthandProperty);
+    if (!priority) {
+        for (var i = 0; i < style.length; ++i) {
+            var individualProperty = style[i];
+            if (style.getPropertyShorthand(individualProperty) !== shorthandProperty)
+                continue;
+            priority = style.getPropertyPriority(individualProperty);
+            break;
+        }
+    }
+    return priority;
+}
+
+InjectedScript.getPrototypes = function(nodeId)
+{
+    var node = InjectedScript._nodeForId(nodeId);
+    if (!node)
+        return false;
+
+    var result = [];
+    for (var prototype = node; prototype; prototype = prototype.__proto__) {
+        var title = Object.describe(prototype);
+        if (title.match(/Prototype$/)) {
+            title = title.replace(/Prototype$/, "");
+        }
+        result.push(title);
+    }
+    return result;
+}
+
+InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty)
+{
+    var object = InjectedScript._resolveObject(objectProxy);
+    if (!object)
+        return false;
+
+    var properties = [];
+
+    // Go over properties, prepare results.
+    for (var propertyName in object) {
+        if (!ignoreHasOwnProperty && "hasOwnProperty" in object && !object.hasOwnProperty(propertyName))
+            continue;
+
+        var property = {};
+        property.name = propertyName;
+        property.parentObjectProxy = objectProxy;
+        var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName);
+        if (!property.isGetter) {
+            var childObject = object[propertyName];
+            var childObjectProxy = new InjectedScript.createProxyObject(childObject, objectProxy.objectId, true);
+            childObjectProxy.path = objectProxy.path ? objectProxy.path.slice() : [];
+            childObjectProxy.path.push(propertyName);
+            childObjectProxy.protoDepth = objectProxy.protoDepth || 0;
+            property.value = childObjectProxy;
+        } else {
+            // FIXME: this should show something like "getter" (bug 16734).
+            property.value = { description: "\u2014" }; // em dash
+            property.isGetter = true;
+        }
+        properties.push(property);
+    }
+    return properties;
+}
+
+InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression)
+{
+    var object = InjectedScript._resolveObject(objectProxy);
+    if (!object)
+        return false;
+
+    var expressionLength = expression.length;
+    if (!expressionLength) {
+        delete object[propertyName];
+        return !(propertyName in object);
+    }
+
+    try {
+        // Surround the expression in parenthesis so the result of the eval is the result
+        // of the whole expression not the last potential sub-expression.
+
+        // There is a regression introduced here: eval is now happening against global object,
+        // not call frame while on a breakpoint.
+        // TODO: bring evaluation against call frame back.
+        var result = InjectedScript._window().eval("(" + expression + ")");
+        // Store the result in the property.
+        object[propertyName] = result;
+        return true;
+    } catch(e) {
+        try {
+            var result = InjectedScript._window().eval("\"" + expression.escapeCharacters("\"") + "\"");
+            object[propertyName] = result;
+            return true;
+        } catch(e) {
+            return false;
+        }
+    }
+}
+
+
+InjectedScript.getCompletions = function(expression, includeInspectorCommandLineAPI)
+{
+    var props = {};
+    try {
+        var expressionResult = InjectedScript._evaluateOn(InjectedScript._window().eval, InjectedScript._window(), expression);
+        for (var prop in expressionResult)
+            props[prop] = true;
+        if (includeInspectorCommandLineAPI)
+            for (var prop in InjectedScript._window()._inspectorCommandLineAPI)
+                if (prop.charAt(0) !== '_')
+                    props[prop] = true;
+    } catch(e) {
+    }
+    return props;
+}
+
+
+InjectedScript.evaluate = function(expression)
+{
+    var result = {};
+    try {
+        var value = InjectedScript._evaluateOn(InjectedScript._window().eval, InjectedScript._window(), expression);
+        if (value === null)
+            return { value: null };
+        if (Object.type(value) === "error") {
+            result.value = Object.describe(value);
+            result.isException = true;
+            return result;
+        }
+
+        var wrapper = InspectorController.wrapObject(value);
+        if (typeof wrapper === "object" && wrapper.exception) {
+            result.value = wrapper.exception;
+            result.isException = true;
+        } else {
+            result.value = wrapper;
+        }
+    } catch (e) {
+        result.value = e.toString();
+        result.isException = true;
+    }
+    return result;
+}
+
+InjectedScript._evaluateOn = function(evalFunction, object, expression)
+{
+    InjectedScript._ensureCommandLineAPIInstalled();
+    // Surround the expression in with statements to inject our command line API so that
+    // the window object properties still take more precedent than our API functions.
+    expression = "with (window._inspectorCommandLineAPI) { with (window) { " + expression + " } }";
+    return evalFunction.call(object, expression);
+}
+
+InjectedScript.addInspectedNode = function(nodeId)
+{
+    var node = InjectedScript._nodeForId(nodeId);
+    if (!node)
+        return false;
+
+    InjectedScript._ensureCommandLineAPIInstalled();
+    var inspectedNodes = InjectedScript._window()._inspectorCommandLineAPI._inspectedNodes;
+    inspectedNodes.unshift(node);
+    if (inspectedNodes.length >= 5)
+        inspectedNodes.pop();
+    return true;
+}
+
+InjectedScript.performSearch = function(whitespaceTrimmedQuery)
+{
+    var tagNameQuery = whitespaceTrimmedQuery;
+    var attributeNameQuery = whitespaceTrimmedQuery;
+    var startTagFound = (tagNameQuery.indexOf("<") === 0);
+    var endTagFound = (tagNameQuery.lastIndexOf(">") === (tagNameQuery.length - 1));
+
+    if (startTagFound || endTagFound) {
+        var tagNameQueryLength = tagNameQuery.length;
+        tagNameQuery = tagNameQuery.substring((startTagFound ? 1 : 0), (endTagFound ? (tagNameQueryLength - 1) : tagNameQueryLength));
+    }
+
+    // Check the tagNameQuery is it is a possibly valid tag name.
+    if (!/^[a-zA-Z0-9\-_:]+$/.test(tagNameQuery))
+        tagNameQuery = null;
+
+    // Check the attributeNameQuery is it is a possibly valid tag name.
+    if (!/^[a-zA-Z0-9\-_:]+$/.test(attributeNameQuery))
+        attributeNameQuery = null;
+
+    const escapedQuery = whitespaceTrimmedQuery.escapeCharacters("'");
+    const escapedTagNameQuery = (tagNameQuery ? tagNameQuery.escapeCharacters("'") : null);
+    const escapedWhitespaceTrimmedQuery = whitespaceTrimmedQuery.escapeCharacters("'");
+    const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName;
+
+    function addNodesToResults(nodes, length, getItem)
+    {
+        if (!length)
+            return;
+
+        var nodeIds = [];
+        for (var i = 0; i < length; ++i) {
+            var node = getItem.call(nodes, i);
+            // Skip this node if it already has the property.
+            if (searchResultsProperty in node)
+                continue;
+
+            if (!InjectedScript._searchResults.length) {
+                InjectedScript._currentSearchResultIndex = 0;
+            }
+
+            node[searchResultsProperty] = true;
+            InjectedScript._searchResults.push(node);
+            var nodeId = InspectorController.pushNodePathToFrontend(node, false);
+            nodeIds.push(nodeId);
+        }
+        InspectorController.addNodesToSearchResult(nodeIds.join(","));
+    }
+
+    function matchExactItems(doc)
+    {
+        matchExactId.call(this, doc);
+        matchExactClassNames.call(this, doc);
+        matchExactTagNames.call(this, doc);
+        matchExactAttributeNames.call(this, doc);
+    }
+
+    function matchExactId(doc)
+    {
+        const result = doc.__proto__.getElementById.call(doc, whitespaceTrimmedQuery);
+        addNodesToResults.call(this, result, (result ? 1 : 0), function() { return this });
+    }
+
+    function matchExactClassNames(doc)
+    {
+        const result = doc.__proto__.getElementsByClassName.call(doc, whitespaceTrimmedQuery);
+        addNodesToResults.call(this, result, result.length, result.item);
+    }
+
+    function matchExactTagNames(doc)
+    {
+        if (!tagNameQuery)
+            return;
+        const result = doc.__proto__.getElementsByTagName.call(doc, tagNameQuery);
+        addNodesToResults.call(this, result, result.length, result.item);
+    }
+
+    function matchExactAttributeNames(doc)
+    {
+        if (!attributeNameQuery)
+            return;
+        const result = doc.__proto__.querySelectorAll.call(doc, "[" + attributeNameQuery + "]");
+        addNodesToResults.call(this, result, result.length, result.item);
+    }
+
+    function matchPartialTagNames(doc)
+    {
+        if (!tagNameQuery)
+            return;
+        const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+    }
+
+    function matchStartOfTagNames(doc)
+    {
+        if (!tagNameQuery)
+            return;
+        const result = doc.__proto__.evaluate.call(doc, "//*[starts-with(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+    }
+
+    function matchPartialTagNamesAndAttributeValues(doc)
+    {
+        if (!tagNameQuery) {
+            matchPartialAttributeValues.call(this, doc);
+            return;
+        }
+
+        const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "') or contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+    }
+
+    function matchPartialAttributeValues(doc)
+    {
+        const result = doc.__proto__.evaluate.call(doc, "//*[contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+    }
+
+    function matchStyleSelector(doc)
+    {
+        const result = doc.__proto__.querySelectorAll.call(doc, whitespaceTrimmedQuery);
+        addNodesToResults.call(this, result, result.length, result.item);
+    }
+
+    function matchPlainText(doc)
+    {
+        const result = doc.__proto__.evaluate.call(doc, "//text()[contains(., '" + escapedQuery + "')] | //comment()[contains(., '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+    }
+
+    function matchXPathQuery(doc)
+    {
+        const result = doc.__proto__.evaluate.call(doc, whitespaceTrimmedQuery, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
+        addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem);
+    }
+
+    function finishedSearching()
+    {
+        // Remove the searchResultsProperty now that the search is finished.
+        for (var i = 0; i < InjectedScript._searchResults.length; ++i)
+            delete InjectedScript._searchResults[i][searchResultsProperty];
+    }
+
+    const mainFrameDocument = InjectedScript._window().document;
+    const searchDocuments = [mainFrameDocument];
+    var searchFunctions;
+    if (tagNameQuery && startTagFound && endTagFound)
+        searchFunctions = [matchExactTagNames, matchPlainText];
+    else if (tagNameQuery && startTagFound)
+        searchFunctions = [matchStartOfTagNames, matchPlainText];
+    else if (tagNameQuery && endTagFound) {
+        // FIXME: we should have a matchEndOfTagNames search function if endTagFound is true but not startTagFound.
+        // This requires ends-with() support in XPath, WebKit only supports starts-with() and contains().
+        searchFunctions = [matchPartialTagNames, matchPlainText];
+    } else if (whitespaceTrimmedQuery === "//*" || whitespaceTrimmedQuery === "*") {
+        // These queries will match every node. Matching everything isn't useful and can be slow for large pages,
+        // so limit the search functions list to plain text and attribute matching.
+        searchFunctions = [matchPartialAttributeValues, matchPlainText];
+    } else
+        searchFunctions = [matchExactItems, matchStyleSelector, matchPartialTagNamesAndAttributeValues, matchPlainText, matchXPathQuery];
+
+    // Find all frames, iframes and object elements to search their documents.
+    const querySelectorAllFunction = InjectedScript._window().Document.prototype.querySelectorAll;
+    const subdocumentResult = querySelectorAllFunction.call(mainFrameDocument, "iframe, frame, object");
+
+    for (var i = 0; i < subdocumentResult.length; ++i) {
+        var element = subdocumentResult.item(i);
+        if (element.contentDocument)
+            searchDocuments.push(element.contentDocument);
+    }
+
+    const panel = InjectedScript;
+    var documentIndex = 0;
+    var searchFunctionIndex = 0;
+    var chunkIntervalIdentifier = null;
+
+    // Split up the work into chunks so we don't block the UI thread while processing.
+
+    function processChunk()
+    {
+        var searchDocument = searchDocuments[documentIndex];
+        var searchFunction = searchFunctions[searchFunctionIndex];
+
+        if (++searchFunctionIndex > searchFunctions.length) {
+            searchFunction = searchFunctions[0];
+            searchFunctionIndex = 0;
+
+            if (++documentIndex > searchDocuments.length) {
+                if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
+                    delete panel._currentSearchChunkIntervalIdentifier;
+                clearInterval(chunkIntervalIdentifier);
+                finishedSearching.call(panel);
+                return;
+            }
+
+            searchDocument = searchDocuments[documentIndex];
+        }
+
+        if (!searchDocument || !searchFunction)
+            return;
+
+        try {
+            searchFunction.call(panel, searchDocument);
+        } catch(err) {
+            // ignore any exceptions. the query might be malformed, but we allow that.
+        }
+    }
+
+    processChunk();
+
+    chunkIntervalIdentifier = setInterval(processChunk, 25);
+    InjectedScript._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
+    return true;
+}
+
+InjectedScript.searchCanceled = function()
+{
+    if (InjectedScript._searchResults) {
+        const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName;
+        for (var i = 0; i < this._searchResults.length; ++i) {
+            var node = this._searchResults[i];
+
+            // Remove the searchResultsProperty since there might be an unfinished search.
+            delete node[searchResultsProperty];
+        }
+    }
+
+    if (InjectedScript._currentSearchChunkIntervalIdentifier) {
+        clearInterval(InjectedScript._currentSearchChunkIntervalIdentifier);
+        delete InjectedScript._currentSearchChunkIntervalIdentifier;
+    }
+    InjectedScript._searchResults = [];
+    return true;
+}
+
+InjectedScript.openInInspectedWindow = function(url)
+{
+    InjectedScript._window().open(url);
+}
+
+InjectedScript.getCallFrames = function()
+{
+    var callFrame = InspectorController.currentCallFrame();
+    if (!callFrame)
+        return false;
+
+    var result = [];
+    var depth = 0;
+    do {
+        result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
+        callFrame = callFrame.caller;
+    } while (callFrame);
+    return result;
+}
+
+InjectedScript.evaluateInCallFrame = function(callFrameId, code)
+{
+    var callFrame = InjectedScript._callFrameForId(callFrameId);
+    if (!callFrame)
+        return false;
+    return InjectedScript._evaluateOn(callFrame.evaluate, callFrame, code);
+}
+
+InjectedScript._callFrameForId = function(id)
+{
+    var callFrame = InspectorController.currentCallFrame();
+    while (--id >= 0 && callFrame)
+        callFrame = callFrame.caller;
+    return callFrame;
+}
+
+InjectedScript._clearConsoleMessages = function()
+{
+    InspectorController.clearMessages(true);
+}
+
+InjectedScript._inspectObject = function(o)
+{
+    if (arguments.length === 0)
+        return;
+
+    var inspectedWindow = InjectedScript._window();
+    inspectedWindow.console.log(o);
+    if (Object.type(o) === "node") {
+        InspectorController.pushNodePathToFrontend(o, true);
+    } else {
+        switch (Object.describe(o)) {
+            case "Database":
+                InspectorController.selectDatabase(o);
+                break;
+            case "Storage":
+                InspectorController.selectDOMStorage(o);
+                break;
+        }
+    }
+}
+
+InjectedScript._ensureCommandLineAPIInstalled = function(inspectedWindow)
+{
+    var inspectedWindow = InjectedScript._window();
+    if (inspectedWindow._inspectorCommandLineAPI)
+        return;
+    
+    inspectedWindow.eval("window._inspectorCommandLineAPI = { \
+        $: function() { return document.getElementById.apply(document, arguments) }, \
+        $$: function() { return document.querySelectorAll.apply(document, arguments) }, \
+        $x: function(xpath, context) { \
+            var nodes = []; \
+            try { \
+                var doc = context || document; \
+                var results = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); \
+                var node; \
+                while (node = results.iterateNext()) nodes.push(node); \
+            } catch (e) {} \
+            return nodes; \
+        }, \
+        dir: function() { return console.dir.apply(console, arguments) }, \
+        dirxml: function() { return console.dirxml.apply(console, arguments) }, \
+        keys: function(o) { var a = []; for (var k in o) a.push(k); return a; }, \
+        values: function(o) { var a = []; for (var k in o) a.push(o[k]); return a; }, \
+        profile: function() { return console.profile.apply(console, arguments) }, \
+        profileEnd: function() { return console.profileEnd.apply(console, arguments) }, \
+        _inspectedNodes: [], \
+        get $0() { return _inspectorCommandLineAPI._inspectedNodes[0] }, \
+        get $1() { return _inspectorCommandLineAPI._inspectedNodes[1] }, \
+        get $2() { return _inspectorCommandLineAPI._inspectedNodes[2] }, \
+        get $3() { return _inspectorCommandLineAPI._inspectedNodes[3] }, \
+        get $4() { return _inspectorCommandLineAPI._inspectedNodes[4] } \
+    };");
+
+    inspectedWindow._inspectorCommandLineAPI.clear = InspectorController.wrapCallback(InjectedScript._clearConsoleMessages);
+    inspectedWindow._inspectorCommandLineAPI.inspect = InspectorController.wrapCallback(InjectedScript._inspectObject);
+}
+
+InjectedScript._resolveObject = function(objectProxy)
+{
+    var object = InjectedScript._objectForId(objectProxy.objectId);
+    var path = objectProxy.path;
+    var protoDepth = objectProxy.protoDepth;
+
+    // Follow the property path.
+    for (var i = 0; object && path && i < path.length; ++i)
+        object = object[path[i]];
+
+    // Get to the necessary proto layer.
+    for (var i = 0; object && protoDepth && i < protoDepth; ++i)
+        object = object.__proto__;
+
+    return object;
+}
+
+InjectedScript._window = function()
+{
+    // TODO: replace with 'return window;' once this script is injected into
+    // the page's context.
+    return InspectorController.inspectedWindow();
+}
+
+InjectedScript._nodeForId = function(nodeId)
+{
+    if (!nodeId)
+        return null;
+    return InspectorController.nodeForId(nodeId);
+}
+
+InjectedScript._objectForId = function(objectId)
+{
+    // There are three types of object ids used:
+    // - numbers point to DOM Node via the InspectorDOMAgent mapping
+    // - strings point to console objects cached in InspectorController for lazy evaluation upon them
+    // - objects contain complex ids and are currently used for scoped objects
+    if (typeof objectId === "number") {
+        return InjectedScript._nodeForId(objectId);
+    } else if (typeof objectId === "string") {
+        return InspectorController.unwrapObject(objectId);
+    } else if (typeof objectId === "object") {
+        var callFrame = InjectedScript._callFrameForId(objectId.callFrame);
+        if (objectId.thisObject)
+            return callFrame.thisObject;
+        else
+            return callFrame.scopeChain[objectId.chainIndex];
+    }
+    return objectId;
+}
+
+InjectedScript.pushNodeToFrontend = function(objectProxy)
+{
+    var object = InjectedScript._resolveObject(objectProxy);
+    if (!object || Object.type(object) !== "node")
+        return false;
+    return InspectorController.pushNodePathToFrontend(object, false);
+}
+
+// Called from within InspectorController on the 'inspected page' side.
+InjectedScript.createProxyObject = function(object, objectId, abbreviate)
+{
+    var result = {};
+    result.objectId = objectId;
+    result.type = Object.type(object);
+
+    var type = typeof object;
+    if ((type === "object" && object !== null) || type === "function") {
+        for (var subPropertyName in object) {
+            result.hasChildren = true;
+            break;
+        }
+    }
+    try {
+        result.description = Object.describe(object, abbreviate);
+    } catch (e) {
+        result.exception = e.toString();
+    }
+    return result;
+}
+
+InjectedScript.CallFrameProxy = function(id, callFrame)
+{
+    this.id = id;
+    this.type = callFrame.type;
+    this.functionName = (this.type === "function" ? callFrame.functionName : "");
+    this.sourceID = callFrame.sourceID;
+    this.line = callFrame.line;
+    this.scopeChain = this._wrapScopeChain(callFrame);
+}
+
+InjectedScript.CallFrameProxy.prototype = {
+    _wrapScopeChain: function(callFrame)
+    {
+        var foundLocalScope = false;
+        var scopeChain = callFrame.scopeChain;
+        var scopeChainProxy = [];
+        for (var i = 0; i < scopeChain.length; ++i) {
+            var scopeObject = scopeChain[i];
+            var scopeObjectProxy = InjectedScript.createProxyObject(scopeObject, { callFrame: this.id, chainIndex: i });
+
+            if (Object.prototype.toString.call(scopeObject) === "[object JSActivation]") {
+                if (!foundLocalScope)
+                    scopeObjectProxy.thisObject = InjectedScript.createProxyObject(callFrame.thisObject, { callFrame: this.id, thisObject: true });
+                else
+                    scopeObjectProxy.isClosure = true;
+                foundLocalScope = true;
+                scopeObjectProxy.isLocal = true;
+            } else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Element)
+                scopeObjectProxy.isElement = true;
+            else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Document)
+                scopeObjectProxy.isDocument = true;
+            else if (!foundLocalScope)
+                scopeObjectProxy.isWithBlock = true;
+            scopeObjectProxy.properties = [];
+            try {
+                for (var propertyName in scopeObject)
+                    scopeObjectProxy.properties.push(propertyName);
+            } catch (e) {
+            }
+            scopeChainProxy.push(scopeObjectProxy);
+        }
+        return scopeChainProxy;
+    }
+}
+
+Object.type = function(obj)
+{
+    if (obj === null)
+        return "null";
+
+    var type = typeof obj;
+    if (type !== "object" && type !== "function")
+        return type;
+
+    var win = InjectedScript._window();
+
+    if (obj instanceof win.Node)
+        return (obj.nodeType === undefined ? type : "node");
+    if (obj instanceof win.String)
+        return "string";
+    if (obj instanceof win.Array)
+        return "array";
+    if (obj instanceof win.Boolean)
+        return "boolean";
+    if (obj instanceof win.Number)
+        return "number";
+    if (obj instanceof win.Date)
+        return "date";
+    if (obj instanceof win.RegExp)
+        return "regexp";
+    if (obj instanceof win.Error)
+        return "error";
+    return type;
+}
+
+Object.hasProperties = function(obj)
+{
+    if (typeof obj === "undefined" || typeof obj === "null")
+        return false;
+    for (var name in obj)
+        return true;
+    return false;
+}
+
+Object.describe = function(obj, abbreviated)
+{
+    var type1 = Object.type(obj);
+    var type2 = Object.className(obj);
+
+    switch (type1) {
+    case "object":
+    case "node":
+        return type2;
+    case "array":
+        return "[" + obj.toString() + "]";
+    case "string":
+        if (obj.length > 100)
+            return "\"" + obj.substring(0, 100) + "\u2026\"";
+        return "\"" + obj + "\"";
+    case "function":
+        var objectText = String(obj);
+        if (!/^function /.test(objectText))
+            objectText = (type2 == "object") ? type1 : type2;
+        else if (abbreviated)
+            objectText = /.*/.exec(obj)[0].replace(/ +$/g, "");
+        return objectText;
+    case "regexp":
+        return String(obj).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);
+    default:
+        return String(obj);
+    }
+}
+
+Object.className = function(obj)
+{
+    return Object.prototype.toString.call(obj).replace(/^\[object (.*)\]$/i, "$1")
+}
+
+// Although Function.prototype.bind and String.prototype.escapeCharacters are defined in utilities.js they will soon become
+// unavailable in the InjectedScript context. So we define them here for the local use.
+// TODO: remove this comment once InjectedScript runs in a separate context.
+Function.prototype.bind = function(thisObject)
+{
+    var func = this;
+    var args = Array.prototype.slice.call(arguments, 1);
+    return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))) };
+}
+
+String.prototype.escapeCharacters = function(chars)
+{
+    var foundChar = false;
+    for (var i = 0; i < chars.length; ++i) {
+        if (this.indexOf(chars.charAt(i)) !== -1) {
+            foundChar = true;
+            break;
+        }
+    }
+
+    if (!foundChar)
+        return this;
+
+    var result = "";
+    for (var i = 0; i < this.length; ++i) {
+        if (chars.indexOf(this.charAt(i)) !== -1)
+            result += "\\";
+        result += this.charAt(i);
+    }
+
+    return result;
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScriptAccess.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScriptAccess.js
new file mode 100644
index 0000000..da85d03
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScriptAccess.js
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * 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.
+ */
+
+var InjectedScriptAccess = {};
+
+InjectedScriptAccess._installHandler = function(methodName)
+{
+    InjectedScriptAccess[methodName] = function()
+    {
+        var allArgs = Array.prototype.slice.call(arguments);
+        var callback = allArgs[allArgs.length - 1];
+        var argsString = JSON.stringify(Array.prototype.slice.call(allArgs, 0, allArgs.length - 1));
+        
+        function myCallback(result, isException)
+        {
+            if (!isException)
+                callback(JSON.parse(result));
+            else
+                WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage("Error dispatching: " + methodName));
+        }
+        var callId = WebInspector.Callback.wrap(myCallback);
+        InspectorController.dispatchOnInjectedScript(callId, methodName, argsString);
+    };
+}
+
+InjectedScriptAccess._installHandler("getStyles");
+InjectedScriptAccess._installHandler("getComputedStyle");
+InjectedScriptAccess._installHandler("getInlineStyle");
+InjectedScriptAccess._installHandler("applyStyleText");
+InjectedScriptAccess._installHandler("setStyleText");
+InjectedScriptAccess._installHandler("toggleStyleEnabled");
+InjectedScriptAccess._installHandler("applyStyleRuleText");
+InjectedScriptAccess._installHandler("addStyleSelector");
+InjectedScriptAccess._installHandler("setStyleProperty");
+InjectedScriptAccess._installHandler("getPrototypes");
+InjectedScriptAccess._installHandler("getProperties");
+InjectedScriptAccess._installHandler("setPropertyValue");
+InjectedScriptAccess._installHandler("getCompletions");
+InjectedScriptAccess._installHandler("evaluate");
+InjectedScriptAccess._installHandler("addInspectedNode");
+InjectedScriptAccess._installHandler("pushNodeToFrontend");
+InjectedScriptAccess._installHandler("evaluate");
+InjectedScriptAccess._installHandler("addInspectedNode");
+InjectedScriptAccess._installHandler("pushNodeToFrontend");
+InjectedScriptAccess._installHandler("performSearch");
+InjectedScriptAccess._installHandler("searchCanceled");
+InjectedScriptAccess._installHandler("openInInspectedWindow");
+InjectedScriptAccess._installHandler("evaluateInCallFrame");
+
+WebInspector.didDispatchOnInjectedScript = WebInspector.Callback.processCallback;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/KeyboardShortcut.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/KeyboardShortcut.js
new file mode 100644
index 0000000..ed28a48
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/KeyboardShortcut.js
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.KeyboardShortcut = function()
+{
+};
+
+/**
+ * Constants for encoding modifier key set as a bit mask.
+ * @see #_makeKeyFromCodeAndModifiers
+ */
+WebInspector.KeyboardShortcut.Modifiers = {
+    None: 0,   // Constant for empty modifiers set.
+    Shift: 1,
+    Ctrl: 2,
+    Alt: 4,
+    Meta: 8    // Command key on Mac, Win key on other platforms.
+};
+
+WebInspector.KeyboardShortcut.KeyCodes = {
+    Esc: 27,
+    Space: 32,
+    PageUp: 33,      // also NUM_NORTH_EAST
+    PageDown: 34,    // also NUM_SOUTH_EAST
+    End: 35,         // also NUM_SOUTH_WEST
+    Home: 36,        // also NUM_NORTH_WEST
+    Left: 37,        // also NUM_WEST
+    Up: 38,          // also NUM_NORTH
+    Right: 39,       // also NUM_EAST
+    Down: 40,        // also NUM_SOUTH
+    F1: 112,
+    F2: 113,
+    F3: 114,
+    F4: 115,
+    F5: 116,
+    F6: 117,
+    F7: 118,
+    F8: 119,
+    F9: 120,
+    F10: 121,
+    F11: 122,
+    F12: 123,
+    Semicolon: 186,    // ;
+    Comma: 188,        // ,
+    Period: 190,       // .
+    Slash: 191,        // /
+    Apostrophe: 192,   // `
+    SingleQuote: 222,  // '
+};
+
+/**
+ * Creates a number encoding keyCode in the lower 8 bits and modifiers mask in the higher 8 bits.
+ * It is usefull for matching pressed keys.
+ * @param {number} keyCode Code of the key.
+ * @param {number} optModifiers Optional list of modifiers passed as additional paramerters.
+ */
+WebInspector.KeyboardShortcut.makeKey = function(keyCode, optModifiers)
+{
+    var modifiers = WebInspector.KeyboardShortcut.Modifiers.None;
+    for (var i = 1; i < arguments.length; i++)
+        modifiers |= arguments[i];
+    return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyCode, modifiers);
+};
+
+WebInspector.KeyboardShortcut.makeKeyFromEvent = function(keyboardEvent)
+{
+    var modifiers = WebInspector.KeyboardShortcut.Modifiers.None;
+    if (keyboardEvent.shiftKey)
+        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Shift;
+    if (keyboardEvent.ctrlKey)
+        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Ctrl;
+    if (keyboardEvent.altKey)
+        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Alt;
+    if (keyboardEvent.metaKey)
+        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Meta;
+    return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyboardEvent.keyCode, modifiers);
+};
+
+WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers = function(keyCode, modifiers)
+{
+    return (keyCode & 255) | (modifiers << 8);
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/MetricsSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/MetricsSidebarPane.js
new file mode 100644
index 0000000..a33653b6
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/MetricsSidebarPane.js
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.MetricsSidebarPane = function()
+{
+    WebInspector.SidebarPane.call(this, WebInspector.UIString("Metrics"));
+    this._inlineStyleId = null;
+}
+
+WebInspector.MetricsSidebarPane.prototype = {
+    update: function(node)
+    {
+        var body = this.bodyElement;
+
+        body.removeChildren();
+
+        if (node)
+            this.node = node;
+        else
+            node = this.node;
+
+        if (!node || !node.ownerDocument.defaultView)
+            return;
+
+        if (node.nodeType !== Node.ELEMENT_NODE)
+            return;
+
+        var self = this;
+        var callback = function(stylePayload) {
+            if (!stylePayload)
+                return;
+            var style = WebInspector.CSSStyleDeclaration.parseStyle(stylePayload);
+            self._update(node, body, style);
+        };
+        InjectedScriptAccess.getComputedStyle(node.id, callback);
+
+        var inlineStyleCallback = function(stylePayload) {
+            if (!stylePayload)
+                return;
+            self._inlineStyleId = stylePayload.id;
+        };
+        InjectedScriptAccess.getInlineStyle(node.id, inlineStyleCallback);
+    },
+
+    _update: function(node, body, style)
+    {
+        var metricsElement = document.createElement("div");
+        metricsElement.className = "metrics";
+
+        function createBoxPartElement(style, name, side, suffix)
+        {
+            var propertyName = (name !== "position" ? name + "-" : "") + side + suffix;
+            var value = style.getPropertyValue(propertyName);
+            if (value === "" || (name !== "position" && value === "0px"))
+                value = "\u2012";
+            else if (name === "position" && value === "auto")
+                value = "\u2012";
+            value = value.replace(/px$/, "");
+
+            var element = document.createElement("div");
+            element.className = side;
+            element.textContent = value;
+            element.addEventListener("dblclick", this.startEditing.bind(this, element, name, propertyName), false);
+            return element;
+        }
+
+        // Display types for which margin is ignored.
+        var noMarginDisplayType = {
+            "table-cell": true,
+            "table-column": true,
+            "table-column-group": true,
+            "table-footer-group": true,
+            "table-header-group": true,
+            "table-row": true,
+            "table-row-group": true
+        };
+
+        // Display types for which padding is ignored.
+        var noPaddingDisplayType = {
+            "table-column": true,
+            "table-column-group": true,
+            "table-footer-group": true,
+            "table-header-group": true,
+            "table-row": true,
+            "table-row-group": true
+        };
+
+        // Position types for which top, left, bottom and right are ignored.
+        var noPositionType = {
+            "static": true
+        };
+
+        var boxes = ["content", "padding", "border", "margin", "position"];
+        var boxLabels = [WebInspector.UIString("content"), WebInspector.UIString("padding"), WebInspector.UIString("border"), WebInspector.UIString("margin"), WebInspector.UIString("position")];
+        var previousBox;
+        for (var i = 0; i < boxes.length; ++i) {
+            var name = boxes[i];
+
+            if (name === "margin" && noMarginDisplayType[style.display])
+                continue;
+            if (name === "padding" && noPaddingDisplayType[style.display])
+                continue;
+            if (name === "position" && noPositionType[style.position])
+                continue;
+
+            var boxElement = document.createElement("div");
+            boxElement.className = name;
+
+            if (name === "content") {
+                var width = style.width.replace(/px$/, "");
+                var widthElement = document.createElement("span");
+                widthElement.textContent = width;
+                widthElement.addEventListener("dblclick", this.startEditing.bind(this, widthElement, "width", "width"), false);
+
+                var height = style.height.replace(/px$/, "");
+                var heightElement = document.createElement("span");
+                heightElement.textContent = height;
+                heightElement.addEventListener("dblclick", this.startEditing.bind(this, heightElement, "height", "height"), false);
+
+                boxElement.appendChild(widthElement);
+                boxElement.appendChild(document.createTextNode(" \u00D7 "));
+                boxElement.appendChild(heightElement);
+            } else {
+                var suffix = (name === "border" ? "-width" : "");
+
+                var labelElement = document.createElement("div");
+                labelElement.className = "label";
+                labelElement.textContent = boxLabels[i];
+                boxElement.appendChild(labelElement);
+
+                boxElement.appendChild(createBoxPartElement.call(this, style, name, "top", suffix));
+                boxElement.appendChild(document.createElement("br"));
+                boxElement.appendChild(createBoxPartElement.call(this, style, name, "left", suffix));
+
+                if (previousBox)
+                    boxElement.appendChild(previousBox);
+
+                boxElement.appendChild(createBoxPartElement.call(this, style, name, "right", suffix));
+                boxElement.appendChild(document.createElement("br"));
+                boxElement.appendChild(createBoxPartElement.call(this, style, name, "bottom", suffix));
+            }
+
+            previousBox = boxElement;
+        }
+
+        metricsElement.appendChild(previousBox);
+        body.appendChild(metricsElement);
+    },
+
+    startEditing: function(targetElement, box, styleProperty)
+    {
+        if (WebInspector.isBeingEdited(targetElement))
+            return;
+
+        var context = { box: box, styleProperty: styleProperty };
+
+        WebInspector.startEditing(targetElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
+    },
+
+    editingCancelled: function(element, context)
+    {
+        this.update();
+    },
+
+    editingCommitted: function(element, userInput, previousContent, context)
+    {
+        if (userInput === previousContent)
+            return this.editingCancelled(element, context); // nothing changed, so cancel
+
+        if (context.box !== "position" && (!userInput || userInput === "\u2012"))
+            userInput = "0px";
+        else if (context.box === "position" && (!userInput || userInput === "\u2012"))
+            userInput = "auto";
+
+        // Append a "px" unit if the user input was just a number.
+        if (/^\d+$/.test(userInput))
+            userInput += "px";
+
+        var self = this;
+        var callback = function(success) {
+            if (!success)
+                return;
+            self.dispatchEventToListeners("metrics edited");
+            self.update();
+        };
+        InjectedScriptAccess.setStyleProperty(this._inlineStyleId, context.styleProperty, userInput, callback);
+    }
+}
+
+WebInspector.MetricsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Object.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Object.js
new file mode 100644
index 0000000..80202b09
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Object.js
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.Object = function() {
+}
+
+WebInspector.Object.prototype = {
+    addEventListener: function(eventType, listener, thisObject) {
+        if (!("_listeners" in this))
+            this._listeners = {};
+        if (!(eventType in this._listeners))
+            this._listeners[eventType] = [];
+        this._listeners[eventType].push({ thisObject: thisObject, listener: listener });
+    },
+
+    removeEventListener: function(eventType, listener, thisObject) {
+        if (!("_listeners" in this) || !(eventType in this._listeners))
+            return;
+        var listeners = this._listeners[eventType];
+        for (var i = 0; i < listeners.length; ++i) {
+            if (listener && listeners[i].listener === listener && listeners[i].thisObject === thisObject)
+                listeners.splice(i, 1);
+            else if (!listener && thisObject && listeners[i].thisObject === thisObject)
+                listeners.splice(i, 1);
+        }
+
+        if (!listeners.length)
+            delete this._listeners[eventType];
+    },
+
+    dispatchEventToListeners: function(eventType) {
+        if (!("_listeners" in this) || !(eventType in this._listeners))
+            return;
+
+        var stoppedPropagation = false;
+
+        function stopPropagation()
+        {
+            stoppedPropagation = true;
+        }
+
+        function preventDefault()
+        {
+            this.defaultPrevented = true;
+        }
+
+        var event = {target: this, type: eventType, defaultPrevented: false};
+        event.stopPropagation = stopPropagation.bind(event);
+        event.preventDefault = preventDefault.bind(event);
+
+        var listeners = this._listeners[eventType];
+        for (var i = 0; i < listeners.length; ++i) {
+            listeners[i].listener.call(listeners[i].thisObject, event);
+            if (stoppedPropagation)
+                break;
+        }
+
+        return event.defaultPrevented;
+    }
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectPropertiesSection.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectPropertiesSection.js
new file mode 100644
index 0000000..8bb4e35
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectPropertiesSection.js
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.ObjectPropertiesSection = function(object, title, subtitle, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, treeElementConstructor)
+{
+    this.emptyPlaceholder = (emptyPlaceholder || WebInspector.UIString("No Properties"));
+    this.object = object;
+    this.ignoreHasOwnProperty = ignoreHasOwnProperty;
+    this.extraProperties = extraProperties;
+    this.treeElementConstructor = treeElementConstructor || WebInspector.ObjectPropertyTreeElement;
+    this.editable = true;
+
+    WebInspector.PropertiesSection.call(this, title, subtitle);
+}
+
+WebInspector.ObjectPropertiesSection.prototype = {
+    onpopulate: function()
+    {
+        this.update();
+    },
+
+    update: function()
+    {
+        var self = this;
+        var callback = function(properties) {
+            if (!properties)
+                return;
+            self.updateProperties(properties);
+        };
+        InjectedScriptAccess.getProperties(this.object, this.ignoreHasOwnProperty, callback);
+    },
+
+    updateProperties: function(properties, rootTreeElementConstructor, rootPropertyComparer)
+    {
+        if (!rootTreeElementConstructor)
+            rootTreeElementConstructor = this.treeElementConstructor;
+            
+        if (!rootPropertyComparer)
+            rootPropertyComparer = WebInspector.ObjectPropertiesSection.CompareProperties;
+            
+        if (this.extraProperties)
+            for (var i = 0; i < this.extraProperties.length; ++i)
+                properties.push(this.extraProperties[i]);
+                
+        properties.sort(rootPropertyComparer);
+
+        this.propertiesTreeOutline.removeChildren();
+
+        for (var i = 0; i < properties.length; ++i)
+            this.propertiesTreeOutline.appendChild(new rootTreeElementConstructor(properties[i]));
+
+        if (!this.propertiesTreeOutline.children.length) {
+            var title = "<div class=\"info\">" + this.emptyPlaceholder + "</div>";
+            var infoElement = new TreeElement(title, null, false);
+            this.propertiesTreeOutline.appendChild(infoElement);
+        }
+    }
+}
+
+WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
+
+WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB) 
+{
+    var a = propertyA.name;
+    var b = propertyB.name;
+
+    // if used elsewhere make sure to
+    //  - convert a and b to strings (not needed here, properties are all strings)
+    //  - check if a == b (not needed here, no two properties can be the same)
+
+    var diff = 0;
+    var chunk = /^\d+|^\D+/;
+    var chunka, chunkb, anum, bnum;
+    while (diff === 0) {
+        if (!a && b)
+            return -1;
+        if (!b && a)
+            return 1;
+        chunka = a.match(chunk)[0];
+        chunkb = b.match(chunk)[0];
+        anum = !isNaN(chunka);
+        bnum = !isNaN(chunkb);
+        if (anum && !bnum)
+            return -1;
+        if (bnum && !anum)
+            return 1;
+        if (anum && bnum) {
+            diff = chunka - chunkb;
+            if (diff === 0 && chunka.length !== chunkb.length) {
+                if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
+                    return chunka.length - chunkb.length;
+                else
+                    return chunkb.length - chunka.length;
+            }
+        } else if (chunka !== chunkb)
+            return (chunka < chunkb) ? -1 : 1;
+        a = a.substring(chunka.length);
+        b = b.substring(chunkb.length);
+    }
+    return diff;
+}
+
+WebInspector.ObjectPropertyTreeElement = function(property)
+{
+    this.property = property;
+
+    // Pass an empty title, the title gets made later in onattach.
+    TreeElement.call(this, "", null, false);
+}
+
+WebInspector.ObjectPropertyTreeElement.prototype = {
+    onpopulate: function()
+    {
+        if (this.children.length && !this.shouldRefreshChildren)
+            return;
+
+        var callback = function(properties) {
+            this.removeChildren();
+            if (!properties)
+                return;
+
+            properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
+            for (var i = 0; i < properties.length; ++i) {
+                this.appendChild(new this.treeOutline.section.treeElementConstructor(properties[i]));
+            }
+        };
+        InjectedScriptAccess.getProperties(this.property.value, false, callback.bind(this));
+    },
+
+    ondblclick: function(element, event)
+    {
+        this.startEditing();
+    },
+
+    onattach: function()
+    {
+        this.update();
+    },
+
+    update: function()
+    {
+        this.nameElement = document.createElement("span");
+        this.nameElement.className = "name";
+        this.nameElement.textContent = this.property.name;
+
+        var separatorElement = document.createElement("span");
+        separatorElement.className = "separator";
+        separatorElement.textContent = ": ";
+        
+        this.valueElement = document.createElement("span");
+        this.valueElement.className = "value";
+        this.valueElement.textContent = this.property.value.description;
+        if (this.property.isGetter)
+           this.valueElement.addStyleClass("dimmed");
+
+        this.listItemElement.removeChildren();
+
+        this.listItemElement.appendChild(this.nameElement);
+        this.listItemElement.appendChild(separatorElement);
+        this.listItemElement.appendChild(this.valueElement);
+        this.hasChildren = this.property.value.hasChildren;
+    },
+
+    updateSiblings: function()
+    {
+        if (this.parent.root)
+            this.treeOutline.section.update();
+        else
+            this.parent.shouldRefreshChildren = true;
+    },
+
+    startEditing: function()
+    {
+        if (WebInspector.isBeingEdited(this.valueElement) || !this.treeOutline.section.editable)
+            return;
+
+        var context = { expanded: this.expanded };
+
+        // Lie about our children to prevent expanding on double click and to collapse subproperties.
+        this.hasChildren = false;
+
+        this.listItemElement.addStyleClass("editing-sub-part");
+
+        WebInspector.startEditing(this.valueElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
+    },
+
+    editingEnded: function(context)
+    {
+        this.listItemElement.scrollLeft = 0;
+        this.listItemElement.removeStyleClass("editing-sub-part");
+        if (context.expanded)
+            this.expand();
+    },
+
+    editingCancelled: function(element, context)
+    {
+        this.update();
+        this.editingEnded(context);
+    },
+
+    editingCommitted: function(element, userInput, previousContent, context)
+    {
+        if (userInput === previousContent)
+            return this.editingCancelled(element, context); // nothing changed, so cancel
+
+        this.applyExpression(userInput, true);
+
+        this.editingEnded(context);
+    },
+
+    applyExpression: function(expression, updateInterface)
+    {
+        expression = expression.trimWhitespace();
+        var expressionLength = expression.length;
+        var self = this;
+        var callback = function(success) {
+            if (!updateInterface)
+                return;
+
+            if (!success)
+                self.update();
+
+            if (!expressionLength) {
+                // The property was deleted, so remove this tree element.
+                self.parent.removeChild(this);
+            } else {
+                // Call updateSiblings since their value might be based on the value that just changed.
+                self.updateSiblings();
+            }
+        };
+        InjectedScriptAccess.setPropertyValue(this.property.parentObjectProxy, this.property.name, expression.trimWhitespace(), callback);
+    }
+}
+
+WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectProxy.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectProxy.js
new file mode 100644
index 0000000..03d16ab0
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectProxy.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2009 Google Inc. 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.
+ */
+
+WebInspector.ObjectProxy = function(objectId, path, protoDepth, description, hasChildren)
+{
+    this.objectId = objectId;
+    this.path = path || [];
+    this.protoDepth = protoDepth || 0;
+    this.description = description;
+    this.hasChildren = hasChildren;
+}
+
+WebInspector.ObjectPropertyProxy = function(name, value)
+{
+    this.name = name;
+    this.value = value;
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Panel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Panel.js
new file mode 100644
index 0000000..5046f6b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Panel.js
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.Panel = function()
+{
+    WebInspector.View.call(this);
+
+    this.element.addStyleClass("panel");
+}
+
+WebInspector.Panel.prototype = {
+    get toolbarItem()
+    {
+        if (this._toolbarItem)
+            return this._toolbarItem;
+
+        // Sample toolbar item as markup:
+        // <button class="toolbar-item resources toggleable">
+        // <div class="toolbar-icon"></div>
+        // <div class="toolbar-label">Resources</div>
+        // </button>
+
+        this._toolbarItem = document.createElement("button");
+        this._toolbarItem.className = "toolbar-item toggleable";
+        this._toolbarItem.panel = this;
+
+        if ("toolbarItemClass" in this)
+            this._toolbarItem.addStyleClass(this.toolbarItemClass);
+
+        var iconElement = document.createElement("div");
+        iconElement.className = "toolbar-icon";
+        this._toolbarItem.appendChild(iconElement);
+
+        if ("toolbarItemLabel" in this) {
+            var labelElement = document.createElement("div");
+            labelElement.className = "toolbar-label";
+            labelElement.textContent = this.toolbarItemLabel;
+            this._toolbarItem.appendChild(labelElement);
+        }
+
+        return this._toolbarItem;
+    },
+
+    show: function()
+    {
+        WebInspector.View.prototype.show.call(this);
+
+        var statusBarItems = this.statusBarItems;
+        if (statusBarItems) {
+            this._statusBarItemContainer = document.createElement("div");
+            for (var i = 0; i < statusBarItems.length; ++i)
+                this._statusBarItemContainer.appendChild(statusBarItems[i]);
+            document.getElementById("main-status-bar").appendChild(this._statusBarItemContainer);
+        }
+
+        if ("_toolbarItem" in this)
+            this._toolbarItem.addStyleClass("toggled-on");
+
+        WebInspector.currentFocusElement = document.getElementById("main-panels");
+    },
+
+    hide: function()
+    {
+        WebInspector.View.prototype.hide.call(this);
+
+        if (this._statusBarItemContainer && this._statusBarItemContainer.parentNode)
+            this._statusBarItemContainer.parentNode.removeChild(this._statusBarItemContainer);
+        delete this._statusBarItemContainer;
+        if ("_toolbarItem" in this)
+            this._toolbarItem.removeStyleClass("toggled-on");
+    },
+
+    attach: function()
+    {
+        if (!this.element.parentNode)
+            document.getElementById("main-panels").appendChild(this.element);
+    },
+
+    searchCanceled: function(startingNewSearch)
+    {
+        if (this._searchResults) {
+            for (var i = 0; i < this._searchResults.length; ++i) {
+                var view = this._searchResults[i];
+                if (view.searchCanceled)
+                    view.searchCanceled();
+                delete view.currentQuery;
+            }
+        }
+
+        WebInspector.updateSearchMatchesCount(0, this);
+
+        if (this._currentSearchChunkIntervalIdentifier) {
+            clearInterval(this._currentSearchChunkIntervalIdentifier);
+            delete this._currentSearchChunkIntervalIdentifier;
+        }
+
+        this._totalSearchMatches = 0;
+        this._currentSearchResultIndex = 0;
+        this._searchResults = [];
+    },
+
+    performSearch: function(query)
+    {
+        // Call searchCanceled since it will reset everything we need before doing a new search.
+        this.searchCanceled(true);
+
+        var searchableViews = this.searchableViews;
+        if (!searchableViews || !searchableViews.length)
+            return;
+
+        var parentElement = this.viewsContainerElement;
+        var visibleView = this.visibleView;
+        var sortFuction = this.searchResultsSortFunction;
+
+        var matchesCountUpdateTimeout = null;
+
+        function updateMatchesCount()
+        {
+            WebInspector.updateSearchMatchesCount(this._totalSearchMatches, this);
+            matchesCountUpdateTimeout = null;
+        }
+
+        function updateMatchesCountSoon()
+        {
+            if (matchesCountUpdateTimeout)
+                return;
+            // Update the matches count every half-second so it doesn't feel twitchy.
+            matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500);
+        }
+
+        function finishedCallback(view, searchMatches)
+        {
+            if (!searchMatches)
+                return;
+
+            this._totalSearchMatches += searchMatches;
+            this._searchResults.push(view);
+
+            if (sortFuction)
+                this._searchResults.sort(sortFuction);
+
+            if (this.searchMatchFound)
+                this.searchMatchFound(view, searchMatches);
+
+            updateMatchesCountSoon.call(this);
+
+            if (view === visibleView)
+                view.jumpToFirstSearchResult();
+        }
+
+        var i = 0;
+        var panel = this;
+        var boundFinishedCallback = finishedCallback.bind(this);
+        var chunkIntervalIdentifier = null;
+
+        // Split up the work into chunks so we don't block the
+        // UI thread while processing.
+
+        function processChunk()
+        {
+            var view = searchableViews[i];
+
+            if (++i >= searchableViews.length) {
+                if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
+                    delete panel._currentSearchChunkIntervalIdentifier;
+                clearInterval(chunkIntervalIdentifier);
+            }
+
+            if (!view)
+                return;
+
+            if (view.element.parentNode !== parentElement && view.element.parentNode && parentElement)
+                view.detach();
+
+            view.currentQuery = query;
+            view.performSearch(query, boundFinishedCallback);
+        }
+
+        processChunk();
+
+        chunkIntervalIdentifier = setInterval(processChunk, 25);
+        this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
+    },
+
+    jumpToNextSearchResult: function()
+    {
+        if (!this.showView || !this._searchResults || !this._searchResults.length)
+            return;
+
+        var showFirstResult = false;
+
+        this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
+        if (this._currentSearchResultIndex === -1) {
+            this._currentSearchResultIndex = 0;
+            showFirstResult = true;
+        }
+
+        var currentView = this._searchResults[this._currentSearchResultIndex];
+
+        if (currentView.showingLastSearchResult()) {
+            if (++this._currentSearchResultIndex >= this._searchResults.length)
+                this._currentSearchResultIndex = 0;
+            currentView = this._searchResults[this._currentSearchResultIndex];
+            showFirstResult = true;
+        }
+
+        if (currentView !== this.visibleView)
+            this.showView(currentView);
+
+        if (showFirstResult)
+            currentView.jumpToFirstSearchResult();
+        else
+            currentView.jumpToNextSearchResult();
+    },
+
+    jumpToPreviousSearchResult: function()
+    {
+        if (!this.showView || !this._searchResults || !this._searchResults.length)
+            return;
+
+        var showLastResult = false;
+
+        this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
+        if (this._currentSearchResultIndex === -1) {
+            this._currentSearchResultIndex = 0;
+            showLastResult = true;
+        }
+
+        var currentView = this._searchResults[this._currentSearchResultIndex];
+
+        if (currentView.showingFirstSearchResult()) {
+            if (--this._currentSearchResultIndex < 0)
+                this._currentSearchResultIndex = (this._searchResults.length - 1);
+            currentView = this._searchResults[this._currentSearchResultIndex];
+            showLastResult = true;
+        }
+
+        if (currentView !== this.visibleView)
+            this.showView(currentView);
+
+        if (showLastResult)
+            currentView.jumpToLastSearchResult();
+        else
+            currentView.jumpToPreviousSearchResult();
+    }
+}
+
+WebInspector.Panel.prototype.__proto__ = WebInspector.View.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PanelEnablerView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PanelEnablerView.js
new file mode 100644
index 0000000..fab6d76
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PanelEnablerView.js
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.PanelEnablerView = function(identifier, headingText, disclaimerText, buttonTitle)
+{
+    WebInspector.View.call(this);
+
+    this.element.addStyleClass("panel-enabler-view");
+    this.element.addStyleClass(identifier);
+
+    this.contentElement = document.createElement("div");
+    this.contentElement.className = "panel-enabler-view-content";
+    this.element.appendChild(this.contentElement);
+
+    this.imageElement = document.createElement("img");
+    this.contentElement.appendChild(this.imageElement);
+
+    this.choicesForm = document.createElement("form");
+    this.contentElement.appendChild(this.choicesForm);
+
+    this.headerElement = document.createElement("h1");
+    this.headerElement.textContent = headingText;
+    this.choicesForm.appendChild(this.headerElement);
+
+    var self = this;
+    function enableOption(text, checked) {
+        var label = document.createElement("label");
+        var option = document.createElement("input");
+        option.type = "radio";
+        option.name = "enable-option";
+        if (checked)
+            option.checked = true;
+        label.appendChild(option);
+        label.appendChild(document.createTextNode(text));
+        self.choicesForm.appendChild(label);
+        return option;
+    };
+
+    this.enabledForSession = enableOption(WebInspector.UIString("Only enable for this session"), true);
+    this.enabledAlways = enableOption(WebInspector.UIString("Always enable"));
+
+    this.disclaimerElement = document.createElement("div");
+    this.disclaimerElement.className = "panel-enabler-disclaimer";
+    this.disclaimerElement.textContent = disclaimerText;
+    this.choicesForm.appendChild(this.disclaimerElement);
+
+    this.enableButton = document.createElement("button");
+    this.enableButton.setAttribute("type", "button");
+    this.enableButton.textContent = buttonTitle;
+    this.enableButton.addEventListener("click", this._enableButtonCicked.bind(this), false);
+    this.choicesForm.appendChild(this.enableButton);
+
+    window.addEventListener("resize", this._windowResized.bind(this), true);
+}
+
+WebInspector.PanelEnablerView.prototype = {
+    _enableButtonCicked: function()
+    {
+        this.dispatchEventToListeners("enable clicked");
+    },
+
+    _windowResized: function()
+    {
+        this.imageElement.removeStyleClass("hidden");
+
+        if (this.element.offsetWidth < (this.choicesForm.offsetWidth + this.imageElement.offsetWidth))
+            this.imageElement.addStyleClass("hidden");
+    },
+
+    get alwaysEnabled() {
+        return this.enabledAlways.checked;
+    }
+}
+
+WebInspector.PanelEnablerView.prototype.__proto__ = WebInspector.View.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Placard.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Placard.js
new file mode 100644
index 0000000..69a168eb
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Placard.js
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.Placard = function(title, subtitle)
+{
+    this.element = document.createElement("div");
+    this.element.className = "placard";
+    this.element.placard = this;
+
+    this.titleElement = document.createElement("div");
+    this.titleElement.className = "title";
+
+    this.subtitleElement = document.createElement("div");
+    this.subtitleElement.className = "subtitle";
+
+    this.element.appendChild(this.subtitleElement);
+    this.element.appendChild(this.titleElement);
+
+    this.title = title;
+    this.subtitle = subtitle;
+    this.selected = false;
+}
+
+WebInspector.Placard.prototype = {
+    get title()
+    {
+        return this._title;
+    },
+
+    set title(x)
+    {
+        if (this._title === x)
+            return;
+        this._title = x;
+        this.titleElement.textContent = x;
+    },
+
+    get subtitle()
+    {
+        return this._subtitle;
+    },
+
+    set subtitle(x)
+    {
+        if (this._subtitle === x)
+            return;
+        this._subtitle = x;
+        this.subtitleElement.innerHTML = x;
+    },
+
+    get selected()
+    {
+        return this._selected;
+    },
+
+    set selected(x)
+    {
+        if (x)
+            this.select();
+        else
+            this.deselect();
+    },
+
+    select: function()
+    {
+        if (this._selected)
+            return;
+        this._selected = true;
+        this.element.addStyleClass("selected");
+    },
+
+    deselect: function()
+    {
+        if (!this._selected)
+            return;
+        this._selected = false;
+        this.element.removeStyleClass("selected");
+    },
+
+    toggleSelected: function()
+    {
+        this.selected = !this.selected;
+    }
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Popup.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Popup.js
new file mode 100644
index 0000000..9c8ef245
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Popup.js
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2009 Google Inc. 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.
+ */
+
+/**
+ * This class provides a popup that can be shown relative to an anchor element
+ * or at an arbitrary absolute position.
+ * Points are Objects: {x: xValue, y: yValue}.
+ * Rectangles are Objects: {x: xValue, y: yValue, width: widthValue, height: heightValue}.
+ *
+ * element is an optional unparented visible element (style.display != "none" AND style.visibility != "hidden").
+ * If the element is absent/undefined, it must have been set with the element(x) setter before the show() method invocation.
+ */
+WebInspector.Popup = function(element)
+{
+    if (element)
+        this.element = element;
+    this._keyHandler = this._keyEventHandler.bind(this);
+    this._mouseDownHandler = this._mouseDownEventHandler.bind(this);
+    this._visible = false;
+    this._autoHide = true;
+}
+
+WebInspector.Popup.prototype = {
+    show: function()
+    {
+        if (this.visible)
+            return;
+        var ownerDocument = this._contentElement.ownerDocument;
+        if (!ownerDocument)
+            return;
+
+        this._glasspaneElement = ownerDocument.createElement("div");
+        this._glasspaneElement.className = "popup-glasspane";
+        ownerDocument.body.appendChild(this._glasspaneElement);
+
+        this._contentElement.positionAt(0, 0);
+        this._contentElement.removeStyleClass("hidden");
+        ownerDocument.body.appendChild(this._contentElement);
+
+        this.positionElement();
+        this._visible = true;
+        ownerDocument.addEventListener("keydown", this._keyHandler, false);
+        ownerDocument.addEventListener("mousedown", this._mouseDownHandler, false);
+    },
+
+    hide: function()
+    {
+        if (this.visible) {
+            this._visible = false;
+            this._contentElement.ownerDocument.removeEventListener("keydown", this._keyHandler, false);
+            this._contentElement.ownerDocument.removeEventListener("mousedown", this._mouseDownHandler, false);
+            this._glasspaneElement.parentElement.removeChild(this._glasspaneElement);
+            this._contentElement.parentElement.removeChild(this._contentElement);
+        }
+    },
+
+    get visible()
+    {
+        return this._visible;
+    },
+
+    set element(x)
+    {
+        this._checkNotVisible();
+        this._contentElement = x;
+        this._contentElement.addStyleClass("hidden");
+    },
+
+    get element()
+    {
+        return this._contentElement;
+    },
+
+    positionElement: function()
+    {
+        var element = this._contentElement;
+        var anchorElement = this._anchorElement;
+
+        var targetDocument = element.ownerDocument;
+        var targetDocumentBody = targetDocument.body;
+        var targetDocumentElement = targetDocument.documentElement;
+        var clippingBox = {x: 0, y: 0, width: targetDocumentElement.clientWidth, height: targetDocumentElement.clientHeight};
+        var parentElement = element.offsetParent || element.parentElement;
+
+        var anchorPosition = {x: anchorElement.totalOffsetLeft, y: anchorElement.totalOffsetTop};
+
+        // FIXME([email protected]): Translate anchorPosition to the element.ownerDocument frame when https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed.
+        var anchorBox = {x: anchorPosition.x, y: anchorPosition.y, width: anchorElement.offsetWidth, height: anchorElement.offsetHeight};
+        var elementBox = {x: element.totalOffsetLeft, y: element.totalOffsetTop, width: element.offsetWidth, height: element.offsetHeight};
+        var newElementPosition = {x: 0, y: 0};
+
+        if (anchorBox.y - elementBox.height >= clippingBox.y)
+            newElementPosition.y = anchorBox.y - elementBox.height;
+        else
+            newElementPosition.y = Math.min(anchorBox.y + anchorBox.height, Math.max(clippingBox.y, clippingBox.y + clippingBox.height - elementBox.height));
+
+        if (anchorBox.x + elementBox.height <= clippingBox.x + clippingBox.height)
+            newElementPosition.x = anchorBox.x;
+        else
+            newElementPosition.x = Math.max(clippingBox.x, clippingBox.x + clippingBox.height - elementBox.height);
+        element.positionAt(newElementPosition.x, newElementPosition.y);
+    },
+
+    set anchor(x)
+    {
+        this._checkNotVisible();
+        this._anchorElement = x;
+    },
+
+    get anchor()
+    {
+        return this._anchorElement;
+    },
+
+    set autoHide(x)
+    {
+        this._autoHide = x;
+    },
+
+    _checkNotVisible: function()
+    {
+        if (this.visible)
+            throw new Error("The popup must not be visible.");
+    },
+
+    _keyEventHandler: function(event)
+    {
+        // Escape hides the popup.
+        if (event.keyIdentifier == "U+001B") {
+            this.hide();
+            event.preventDefault();
+            event.handled = true;
+        }
+    },
+
+    _mouseDownEventHandler: function(event)
+    {
+        if (this._autoHide && event.originalTarget === this._glasspaneElement)
+            this.hide();
+    }
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileDataGridTree.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileDataGridTree.js
new file mode 100644
index 0000000..356f57d
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileDataGridTree.js
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2009 280 North Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.ProfileDataGridNode = function(profileView, profileNode, owningTree, hasChildren)
+{
+    this.profileView = profileView;
+    this.profileNode = profileNode;
+
+    WebInspector.DataGridNode.call(this, null, hasChildren);
+
+    this.addEventListener("populate", this._populate, this);
+
+    this.tree = owningTree;
+
+    this.childrenByCallUID = {};
+    this.lastComparator = null;
+
+    this.callUID = profileNode.callUID;
+    this.selfTime = profileNode.selfTime;
+    this.totalTime = profileNode.totalTime;
+    this.functionName = profileNode.functionName;
+    this.numberOfCalls = profileNode.numberOfCalls;
+    this.url = profileNode.url;
+}
+
+WebInspector.ProfileDataGridNode.prototype = {
+    get data()
+    {
+        function formatMilliseconds(time)
+        {
+            return Number.secondsToString(time / 1000, WebInspector.UIString.bind(WebInspector), !Preferences.samplingCPUProfiler);
+        }
+
+        var data = {};
+
+        data["function"] = this.functionName;
+        data["calls"] = this.numberOfCalls;
+
+        if (this.profileView.showSelfTimeAsPercent)
+            data["self"] = WebInspector.UIString("%.2f%%", this.selfPercent);
+        else
+            data["self"] = formatMilliseconds(this.selfTime);
+
+        if (this.profileView.showTotalTimeAsPercent)
+            data["total"] = WebInspector.UIString("%.2f%%", this.totalPercent);
+        else
+            data["total"] = formatMilliseconds(this.totalTime);
+
+        if (this.profileView.showAverageTimeAsPercent)
+            data["average"] = WebInspector.UIString("%.2f%%", this.averagePercent);
+        else
+            data["average"] = formatMilliseconds(this.averageTime);
+
+        return data;
+    },
+
+    createCell: function(columnIdentifier)
+    {
+        var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
+
+        if (columnIdentifier === "self" && this._searchMatchedSelfColumn)
+            cell.addStyleClass("highlight");
+        else if (columnIdentifier === "total" && this._searchMatchedTotalColumn)
+            cell.addStyleClass("highlight");
+        else if (columnIdentifier === "average" && this._searchMatchedAverageColumn)
+            cell.addStyleClass("highlight");
+        else if (columnIdentifier === "calls" && this._searchMatchedCallsColumn)
+            cell.addStyleClass("highlight");
+
+        if (columnIdentifier !== "function")
+            return cell;
+
+        if (this.profileNode._searchMatchedFunctionColumn)
+            cell.addStyleClass("highlight");
+
+        if (this.profileNode.url) {
+            var fileName = WebInspector.displayNameForURL(this.profileNode.url);
+
+            var urlElement = document.createElement("a");
+            urlElement.className = "profile-node-file webkit-html-resource-link";
+            urlElement.href = this.profileNode.url;
+            urlElement.lineNumber = this.profileNode.lineNumber;
+
+            if (this.profileNode.lineNumber > 0)
+                urlElement.textContent = fileName + ":" + this.profileNode.lineNumber;
+            else
+                urlElement.textContent = fileName;
+
+            cell.insertBefore(urlElement, cell.firstChild);
+        }
+
+        return cell;
+    },
+
+    select: function(supressSelectedEvent)
+    {
+        WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent);
+        this.profileView._dataGridNodeSelected(this);
+    },
+
+    deselect: function(supressDeselectedEvent)
+    {
+        WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent);
+        this.profileView._dataGridNodeDeselected(this);
+    },
+
+    expand: function()
+    {
+        if (!this.parent) {
+            var currentComparator = this.parent.lastComparator;
+
+            if (!currentComparator || (currentComparator === this.lastComparator))
+                return;
+
+            this.sort(currentComparator);
+        }
+
+        WebInspector.DataGridNode.prototype.expand.call(this);
+    },
+
+    sort: function(/*Function*/ comparator, /*Boolean*/ force)
+    {
+        var gridNodeGroups = [[this]];
+
+        for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) {
+            var gridNodes = gridNodeGroups[gridNodeGroupIndex];
+            var count = gridNodes.length;
+
+            for (var index = 0; index < count; ++index) {
+                var gridNode = gridNodes[index];
+
+                // If the grid node is collapsed, then don't sort children (save operation for later).
+                // If the grid node has the same sorting as previously, then there is no point in sorting it again.
+                if (!force && !gridNode.expanded || gridNode.lastComparator === comparator)
+                    continue;
+
+                gridNode.lastComparator = comparator;
+
+                var children = gridNode.children;
+                var childCount = children.length;
+
+                if (childCount) {
+                    children.sort(comparator);
+
+                    for (var childIndex = 0; childIndex < childCount; ++childIndex)
+                        children[childIndex]._recalculateSiblings(childIndex);
+
+                    gridNodeGroups.push(children);
+                }
+            }
+        }
+    },
+
+    insertChild: function(/*ProfileDataGridNode*/ profileDataGridNode, index)
+    {
+        WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index);
+
+        this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode;
+    },
+
+    removeChild: function(/*ProfileDataGridNode*/ profileDataGridNode)
+    {
+        WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode);
+
+        delete this.childrenByCallUID[profileDataGridNode.callUID];
+    },
+
+    removeChildren: function(/*ProfileDataGridNode*/ profileDataGridNode)
+    {
+        WebInspector.DataGridNode.prototype.removeChildren.call(this);
+
+        this.childrenByCallUID = {};
+    },
+
+    findChild: function(/*Node*/ node)
+    {
+        if (!node)
+            return null;
+        return this.childrenByCallUID[node.callUID];
+    },
+
+    get averageTime()
+    {
+        return this.selfTime / Math.max(1, this.numberOfCalls);
+    },
+
+    get averagePercent()
+    {
+        return this.averageTime / this.tree.totalTime * 100.0;
+    },
+
+    get selfPercent()
+    {
+        return this.selfTime / this.tree.totalTime * 100.0;
+    },
+
+    get totalPercent()
+    {
+        return this.totalTime / this.tree.totalTime * 100.0;
+    },
+
+    // When focusing and collapsing we modify lots of nodes in the tree.
+    // This allows us to restore them all to their original state when we revert.
+    _save: function()
+    {
+        if (this._savedChildren)
+            return;
+
+        this._savedSelfTime = this.selfTime;
+        this._savedTotalTime = this.totalTime;
+        this._savedNumberOfCalls = this.numberOfCalls;
+
+        this._savedChildren = this.children.slice();
+    },
+
+    // When focusing and collapsing we modify lots of nodes in the tree.
+    // This allows us to restore them all to their original state when we revert.
+    _restore: function()
+    {
+        if (!this._savedChildren)
+            return;
+
+        this.selfTime = this._savedSelfTime;
+        this.totalTime = this._savedTotalTime;
+        this.numberOfCalls = this._savedNumberOfCalls;
+
+        this.removeChildren();
+
+        var children = this._savedChildren;
+        var count = children.length;
+
+        for (var index = 0; index < count; ++index) {
+            children[index]._restore();
+            this.appendChild(children[index]);
+        }
+    },
+
+    _merge: function(child, shouldAbsorb)
+    {
+        this.selfTime += child.selfTime;
+
+        if (!shouldAbsorb) {
+            this.totalTime += child.totalTime;
+            this.numberOfCalls += child.numberOfCalls;
+        }
+
+        var children = this.children.slice();
+
+        this.removeChildren();
+
+        var count = children.length;
+
+        for (var index = 0; index < count; ++index) {
+            if (!shouldAbsorb || children[index] !== child)
+                this.appendChild(children[index]);
+        }
+
+        children = child.children.slice();
+        count = children.length;
+
+        for (var index = 0; index < count; ++index) {
+            var orphanedChild = children[index],
+                existingChild = this.childrenByCallUID[orphanedChild.callUID];
+
+            if (existingChild)
+                existingChild._merge(orphanedChild, false);
+            else
+                this.appendChild(orphanedChild);
+        }
+    }
+}
+
+WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
+
+WebInspector.ProfileDataGridTree = function(profileView, profileNode)
+{
+    this.tree = this;
+    this.children = [];
+
+    this.profileView = profileView;
+
+    this.totalTime = profileNode.totalTime;
+    this.lastComparator = null;
+
+    this.childrenByCallUID = {};
+}
+
+WebInspector.ProfileDataGridTree.prototype = {
+    get expanded()
+    {
+        return true;
+    },
+
+    appendChild: function(child)
+    {
+        this.insertChild(child, this.children.length);
+    },
+
+    insertChild: function(child, index)
+    {
+        this.children.splice(index, 0, child);
+        this.childrenByCallUID[child.callUID] = child;
+    },
+
+    removeChildren: function()
+    {
+        this.children = [];
+        this.childrenByCallUID = {};
+    },
+
+    findChild: WebInspector.ProfileDataGridNode.prototype.findChild,
+    sort: WebInspector.ProfileDataGridNode.prototype.sort,
+
+    _save: function()
+    {
+        if (this._savedChildren)
+            return;
+
+        this._savedTotalTime = this.totalTime;
+        this._savedChildren = this.children.slice();
+    },
+
+    restore: function()
+    {
+        if (!this._savedChildren)
+            return;
+
+        this.children = this._savedChildren;
+        this.totalTime = this._savedTotalTime;
+
+        var children = this.children;
+        var count = children.length;
+
+        for (var index = 0; index < count; ++index)
+            children[index]._restore();
+
+        this._savedChildren = null;
+    }
+}
+
+WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}];
+
+WebInspector.ProfileDataGridTree.propertyComparator = function(/*String*/ property, /*Boolean*/ isAscending)
+{
+    var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property];
+
+    if (!comparator) {
+        if (isAscending) {
+            comparator = function(lhs, rhs)
+            {
+                if (lhs[property] < rhs[property])
+                    return -1;
+
+                if (lhs[property] > rhs[property])
+                    return 1;
+
+                return 0;
+            }
+        } else {
+            comparator = function(lhs, rhs)
+            {
+                if (lhs[property] > rhs[property])
+                    return -1;
+
+                if (lhs[property] < rhs[property])
+                    return 1;
+
+                return 0;
+            }
+        }
+
+        this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator;
+    }
+
+    return comparator;
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileView.js
new file mode 100644
index 0000000..2b8c6ce0
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileView.js
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.ProfileView = function(profile)
+{
+    WebInspector.View.call(this);
+
+    this.element.addStyleClass("profile-view");
+
+    this.showSelfTimeAsPercent = true;
+    this.showTotalTimeAsPercent = true;
+    this.showAverageTimeAsPercent = true;
+
+    var columns = { "self": { title: WebInspector.UIString("Self"), width: "72px", sort: "descending", sortable: true },
+                    "total": { title: WebInspector.UIString("Total"), width: "72px", sortable: true },
+                    "average": { title: WebInspector.UIString("Average"), width: "72px", sortable: true },
+                    "calls": { title: WebInspector.UIString("Calls"), width: "54px", sortable: true },
+                    "function": { title: WebInspector.UIString("Function"), disclosure: true, sortable: true } };
+
+    if (Preferences.samplingCPUProfiler) {
+        delete columns.average;
+        delete columns.calls;
+    }
+
+    this.dataGrid = new WebInspector.DataGrid(columns);
+    this.dataGrid.addEventListener("sorting changed", this._sortData, this);
+    this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
+    this.element.appendChild(this.dataGrid.element);
+
+    this.viewSelectElement = document.createElement("select");
+    this.viewSelectElement.className = "status-bar-item";
+    this.viewSelectElement.addEventListener("change", this._changeView.bind(this), false);
+    this.view = "Heavy";
+
+    var heavyViewOption = document.createElement("option");
+    heavyViewOption.label = WebInspector.UIString("Heavy (Bottom Up)");
+    var treeViewOption = document.createElement("option");
+    treeViewOption.label = WebInspector.UIString("Tree (Top Down)");
+    this.viewSelectElement.appendChild(heavyViewOption);
+    this.viewSelectElement.appendChild(treeViewOption);
+
+    this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item");
+    this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
+
+    this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item");
+    this.focusButton.disabled = true;
+    this.focusButton.addEventListener("click", this._focusClicked.bind(this), false);
+
+    this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item");
+    this.excludeButton.disabled = true;
+    this.excludeButton.addEventListener("click", this._excludeClicked.bind(this), false);
+
+    this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item");
+    this.resetButton.visible = false;
+    this.resetButton.addEventListener("click", this._resetClicked.bind(this), false);
+
+    this.profile = profile;
+
+    this.profileDataGridTree = this.bottomUpProfileDataGridTree;
+    this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator("selfTime", false));
+
+    this.refresh();
+
+    this._updatePercentButton();
+}
+
+WebInspector.ProfileView.prototype = {
+    get statusBarItems()
+    {
+        return [this.viewSelectElement, this.percentButton.element, this.focusButton.element, this.excludeButton.element, this.resetButton.element];
+    },
+
+    get profile()
+    {
+        return this._profile;
+    },
+
+    set profile(profile)
+    {
+        this._profile = profile;
+    },
+
+    get bottomUpProfileDataGridTree()
+    {
+        if (!this._bottomUpProfileDataGridTree)
+            this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, this.profile.head);
+        return this._bottomUpProfileDataGridTree;
+    },
+
+    get topDownProfileDataGridTree()
+    {
+        if (!this._topDownProfileDataGridTree)
+            this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, this.profile.head);
+        return this._topDownProfileDataGridTree;
+    },
+
+    get currentTree()
+    {
+        return this._currentTree;
+    },
+
+    set currentTree(tree)
+    {
+        this._currentTree = tree;
+        this.refresh();
+    },
+
+    get topDownTree()
+    {
+        if (!this._topDownTree) {
+            this._topDownTree = WebInspector.TopDownTreeFactory.create(this.profile.head);
+            this._sortProfile(this._topDownTree);
+        }
+
+        return this._topDownTree;
+    },
+
+    get bottomUpTree()
+    {
+        if (!this._bottomUpTree) {
+            this._bottomUpTree = WebInspector.BottomUpTreeFactory.create(this.profile.head);
+            this._sortProfile(this._bottomUpTree);
+        }
+
+        return this._bottomUpTree;
+    },
+
+    show: function(parentElement)
+    {
+        WebInspector.View.prototype.show.call(this, parentElement);
+        this.dataGrid.updateWidths();
+    },
+
+    hide: function()
+    {
+        WebInspector.View.prototype.hide.call(this);
+        this._currentSearchResultIndex = -1;
+    },
+    
+    resize: function()
+    {
+        if (this.dataGrid)
+            this.dataGrid.updateWidths();
+    },
+
+    refresh: function()
+    {
+        var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;
+
+        this.dataGrid.removeChildren();
+
+        var children = this.profileDataGridTree.children;
+        var count = children.length;
+
+        for (var index = 0; index < count; ++index)
+            this.dataGrid.appendChild(children[index]);
+
+        if (selectedProfileNode)
+            selectedProfileNode.selected = true;
+    },
+
+    refreshVisibleData: function()
+    {
+        var child = this.dataGrid.children[0];
+        while (child) {
+            child.refresh();
+            child = child.traverseNextNode(false, null, true);
+        }
+    },
+
+    refreshShowAsPercents: function()
+    {
+        this._updatePercentButton();
+        this.refreshVisibleData();
+    },
+
+    searchCanceled: function()
+    {
+        if (this._searchResults) {
+            for (var i = 0; i < this._searchResults.length; ++i) {
+                var profileNode = this._searchResults[i].profileNode;
+
+                delete profileNode._searchMatchedSelfColumn;
+                delete profileNode._searchMatchedTotalColumn;
+                delete profileNode._searchMatchedCallsColumn;
+                delete profileNode._searchMatchedFunctionColumn;
+
+                profileNode.refresh();
+            }
+        }
+
+        delete this._searchFinishedCallback;
+        this._currentSearchResultIndex = -1;
+        this._searchResults = [];
+    },
+
+    performSearch: function(query, finishedCallback)
+    {
+        // Call searchCanceled since it will reset everything we need before doing a new search.
+        this.searchCanceled();
+
+        query = query.trimWhitespace();
+
+        if (!query.length)
+            return;
+
+        this._searchFinishedCallback = finishedCallback;
+
+        var greaterThan = (query.indexOf(">") === 0);
+        var lessThan = (query.indexOf("<") === 0);
+        var equalTo = (query.indexOf("=") === 0 || ((greaterThan || lessThan) && query.indexOf("=") === 1));
+        var percentUnits = (query.lastIndexOf("%") === (query.length - 1));
+        var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2));
+        var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1));
+
+        var queryNumber = parseFloat(query);
+        if (greaterThan || lessThan || equalTo) {
+            if (equalTo && (greaterThan || lessThan))
+                queryNumber = parseFloat(query.substring(2));
+            else
+                queryNumber = parseFloat(query.substring(1));
+        }
+
+        var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber);
+
+        // Make equalTo implicitly true if it wasn't specified there is no other operator.
+        if (!isNaN(queryNumber) && !(greaterThan || lessThan))
+            equalTo = true;
+
+        function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode)
+        {
+            delete profileDataGridNode._searchMatchedSelfColumn;
+            delete profileDataGridNode._searchMatchedTotalColumn;
+            delete profileDataGridNode._searchMatchedAverageColumn;
+            delete profileDataGridNode._searchMatchedCallsColumn;
+            delete profileDataGridNode._searchMatchedFunctionColumn;
+
+            if (percentUnits) {
+                if (lessThan) {
+                    if (profileDataGridNode.selfPercent < queryNumber)
+                        profileDataGridNode._searchMatchedSelfColumn = true;
+                    if (profileDataGridNode.totalPercent < queryNumber)
+                        profileDataGridNode._searchMatchedTotalColumn = true;
+                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedAverageColumn = true;
+                } else if (greaterThan) {
+                    if (profileDataGridNode.selfPercent > queryNumber)
+                        profileDataGridNode._searchMatchedSelfColumn = true;
+                    if (profileDataGridNode.totalPercent > queryNumber)
+                        profileDataGridNode._searchMatchedTotalColumn = true;
+                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedAverageColumn = true;
+                }
+
+                if (equalTo) {
+                    if (profileDataGridNode.selfPercent == queryNumber)
+                        profileDataGridNode._searchMatchedSelfColumn = true;
+                    if (profileDataGridNode.totalPercent == queryNumber)
+                        profileDataGridNode._searchMatchedTotalColumn = true;
+                    if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedAverageColumn = true;
+                }
+            } else if (millisecondsUnits || secondsUnits) {
+                if (lessThan) {
+                    if (profileDataGridNode.selfTime < queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedSelfColumn = true;
+                    if (profileDataGridNode.totalTime < queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedTotalColumn = true;
+                    if (profileDataGridNode.averageTime < queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedAverageColumn = true;
+                } else if (greaterThan) {
+                    if (profileDataGridNode.selfTime > queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedSelfColumn = true;
+                    if (profileDataGridNode.totalTime > queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedTotalColumn = true;
+                    if (profileDataGridNode.averageTime > queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedAverageColumn = true;
+                }
+
+                if (equalTo) {
+                    if (profileDataGridNode.selfTime == queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedSelfColumn = true;
+                    if (profileDataGridNode.totalTime == queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedTotalColumn = true;
+                    if (profileDataGridNode.averageTime == queryNumberMilliseconds)
+                        profileDataGridNode._searchMatchedAverageColumn = true;
+                }
+            } else {
+                if (equalTo && profileDataGridNode.numberOfCalls == queryNumber)
+                    profileDataGridNode._searchMatchedCallsColumn = true;
+                if (greaterThan && profileDataGridNode.numberOfCalls > queryNumber)
+                    profileDataGridNode._searchMatchedCallsColumn = true;
+                if (lessThan && profileDataGridNode.numberOfCalls < queryNumber)
+                    profileDataGridNode._searchMatchedCallsColumn = true;
+            }
+
+            if (profileDataGridNode.functionName.hasSubstring(query, true) || profileDataGridNode.url.hasSubstring(query, true))
+                profileDataGridNode._searchMatchedFunctionColumn = true;
+
+            if (profileDataGridNode._searchMatchedSelfColumn ||
+                profileDataGridNode._searchMatchedTotalColumn ||
+                profileDataGridNode._searchMatchedAverageColumn ||
+                profileDataGridNode._searchMatchedCallsColumn ||
+                profileDataGridNode._searchMatchedFunctionColumn)
+            {
+                profileDataGridNode.refresh();
+                return true;
+            }
+
+            return false;
+        }
+
+        var current = this.profileDataGridTree.children[0];
+
+        while (current) {
+            if (matchesQuery(current)) {
+                this._searchResults.push({ profileNode: current });
+            }
+
+            current = current.traverseNextNode(false, null, false);
+        }
+
+        finishedCallback(this, this._searchResults.length);
+    },
+
+    jumpToFirstSearchResult: function()
+    {
+        if (!this._searchResults || !this._searchResults.length)
+            return;
+        this._currentSearchResultIndex = 0;
+        this._jumpToSearchResult(this._currentSearchResultIndex);
+    },
+
+    jumpToLastSearchResult: function()
+    {
+        if (!this._searchResults || !this._searchResults.length)
+            return;
+        this._currentSearchResultIndex = (this._searchResults.length - 1);
+        this._jumpToSearchResult(this._currentSearchResultIndex);
+    },
+
+    jumpToNextSearchResult: function()
+    {
+        if (!this._searchResults || !this._searchResults.length)
+            return;
+        if (++this._currentSearchResultIndex >= this._searchResults.length)
+            this._currentSearchResultIndex = 0;
+        this._jumpToSearchResult(this._currentSearchResultIndex);
+    },
+
+    jumpToPreviousSearchResult: function()
+    {
+        if (!this._searchResults || !this._searchResults.length)
+            return;
+        if (--this._currentSearchResultIndex < 0)
+            this._currentSearchResultIndex = (this._searchResults.length - 1);
+        this._jumpToSearchResult(this._currentSearchResultIndex);
+    },
+
+    showingFirstSearchResult: function()
+    {
+        return (this._currentSearchResultIndex === 0);
+    },
+
+    showingLastSearchResult: function()
+    {
+        return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
+    },
+
+    _jumpToSearchResult: function(index)
+    {
+        var searchResult = this._searchResults[index];
+        if (!searchResult)
+            return;
+
+        var profileNode = searchResult.profileNode;
+        profileNode.reveal();
+        profileNode.select();
+    },
+
+    _changeView: function(event)
+    {
+        if (!event || !this.profile)
+            return;
+
+        if (event.target.selectedIndex == 1 && this.view == "Heavy") {
+            this.profileDataGridTree = this.topDownProfileDataGridTree;
+            this._sortProfile();
+            this.view = "Tree";
+        } else if (event.target.selectedIndex == 0 && this.view == "Tree") {
+            this.profileDataGridTree = this.bottomUpProfileDataGridTree;
+            this._sortProfile();
+            this.view = "Heavy";
+        }
+
+        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
+            return;
+
+        // The current search needs to be performed again. First negate out previous match
+        // count by calling the search finished callback with a negative number of matches.
+        // Then perform the search again the with same query and callback.
+        this._searchFinishedCallback(this, -this._searchResults.length);
+        this.performSearch(this.currentQuery, this._searchFinishedCallback);
+    },
+
+    _percentClicked: function(event)
+    {
+        var currentState = this.showSelfTimeAsPercent && this.showTotalTimeAsPercent && this.showAverageTimeAsPercent;
+        this.showSelfTimeAsPercent = !currentState;
+        this.showTotalTimeAsPercent = !currentState;
+        this.showAverageTimeAsPercent = !currentState;
+        this.refreshShowAsPercents();
+    },
+
+    _updatePercentButton: function()
+    {
+        if (this.showSelfTimeAsPercent && this.showTotalTimeAsPercent && this.showAverageTimeAsPercent) {
+            this.percentButton.title = WebInspector.UIString("Show absolute total and self times.");
+            this.percentButton.toggled = true;
+        } else {
+            this.percentButton.title = WebInspector.UIString("Show total and self times as percentages.");
+            this.percentButton.toggled = false;
+        }
+    },
+
+    _focusClicked: function(event)
+    {
+        if (!this.dataGrid.selectedNode)
+            return;
+
+        this.resetButton.visible = true;
+        this.profileDataGridTree.focus(this.dataGrid.selectedNode);
+        this.refresh();
+        this.refreshVisibleData();
+    },
+
+    _excludeClicked: function(event)
+    {
+        var selectedNode = this.dataGrid.selectedNode
+
+        if (!selectedNode)
+            return;
+
+        selectedNode.deselect();
+
+        this.resetButton.visible = true;
+        this.profileDataGridTree.exclude(selectedNode);
+        this.refresh();
+        this.refreshVisibleData();
+    },
+
+    _resetClicked: function(event)
+    {
+        this.resetButton.visible = false;
+        this.profileDataGridTree.restore();
+        this.refresh();
+        this.refreshVisibleData();
+    },
+
+    _dataGridNodeSelected: function(node)
+    {
+        this.focusButton.disabled = false;
+        this.excludeButton.disabled = false;
+    },
+
+    _dataGridNodeDeselected: function(node)
+    {
+        this.focusButton.disabled = true;
+        this.excludeButton.disabled = true;
+    },
+
+    _sortData: function(event)
+    {
+        this._sortProfile(this.profile);
+    },
+
+    _sortProfile: function()
+    {
+        var sortAscending = this.dataGrid.sortOrder === "ascending";
+        var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
+        var sortProperty = {
+                "average": "averageTime",
+                "self": "selfTime",
+                "total": "totalTime",
+                "calls": "numberOfCalls",
+                "function": "functionName"
+            }[sortColumnIdentifier];
+
+        this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending));
+
+        this.refresh();
+    },
+
+    _mouseDownInDataGrid: function(event)
+    {
+        if (event.detail < 2)
+            return;
+
+        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
+        if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column") && !cell.hasStyleClass("average-column")))
+            return;
+
+        if (cell.hasStyleClass("total-column"))
+            this.showTotalTimeAsPercent = !this.showTotalTimeAsPercent;
+        else if (cell.hasStyleClass("self-column"))
+            this.showSelfTimeAsPercent = !this.showSelfTimeAsPercent;
+        else if (cell.hasStyleClass("average-column"))
+            this.showAverageTimeAsPercent = !this.showAverageTimeAsPercent;
+
+        this.refreshShowAsPercents();
+
+        event.preventDefault();
+        event.stopPropagation();
+    }
+}
+
+WebInspector.ProfileView.prototype.__proto__ = WebInspector.View.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfilesPanel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfilesPanel.js
new file mode 100644
index 0000000..c010033
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfilesPanel.js
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+const UserInitiatedProfileName = "org.webkit.profiles.user-initiated";
+
+WebInspector.ProfilesPanel = function()
+{
+    WebInspector.Panel.call(this);
+
+    this.element.addStyleClass("profiles");
+
+    var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel.");
+    var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower.");
+    var panelEnablerButton = WebInspector.UIString("Enable Profiling");
+    this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
+    this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this);
+
+    this.element.appendChild(this.panelEnablerView.element);
+
+    this.sidebarElement = document.createElement("div");
+    this.sidebarElement.id = "profiles-sidebar";
+    this.sidebarElement.className = "sidebar";
+    this.element.appendChild(this.sidebarElement);
+
+    this.sidebarResizeElement = document.createElement("div");
+    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
+    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
+    this.element.appendChild(this.sidebarResizeElement);
+
+    this.sidebarTreeElement = document.createElement("ol");
+    this.sidebarTreeElement.className = "sidebar-tree";
+    this.sidebarElement.appendChild(this.sidebarTreeElement);
+
+    this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
+
+    this.profilesListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("CPU PROFILES"), null, true);
+    this.sidebarTree.appendChild(this.profilesListTreeElement);
+    this.profilesListTreeElement.expand();
+
+    this.snapshotsListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("HEAP SNAPSHOTS"), null, true);
+    if (Preferences.heapProfilerPresent) {
+        this.sidebarTree.appendChild(this.snapshotsListTreeElement);
+        this.snapshotsListTreeElement.expand();
+    }
+
+    this.profileViews = document.createElement("div");
+    this.profileViews.id = "profile-views";
+    this.element.appendChild(this.profileViews);
+
+    this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
+    this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false);
+
+    this.recordButton = new WebInspector.StatusBarButton(WebInspector.UIString("Start profiling."), "record-profile-status-bar-item");
+    this.recordButton.addEventListener("click", this._recordClicked.bind(this), false);
+
+    this.recording = false;
+
+    this.snapshotButton = new WebInspector.StatusBarButton(WebInspector.UIString("Take heap snapshot."), "heap-snapshot-status-bar-item");
+    this.snapshotButton.visible = Preferences.heapProfilerPresent;
+    this.snapshotButton.addEventListener("click", this._snapshotClicked.bind(this), false);
+
+    this.profileViewStatusBarItemsContainer = document.createElement("div");
+    this.profileViewStatusBarItemsContainer.id = "profile-view-status-bar-items";
+
+    this.reset();
+}
+
+WebInspector.ProfilesPanel.prototype = {
+    toolbarItemClass: "profiles",
+
+    get toolbarItemLabel()
+    {
+        return WebInspector.UIString("Profiles");
+    },
+
+    get statusBarItems()
+    {
+        return [this.enableToggleButton.element, this.recordButton.element, this.snapshotButton.element, this.profileViewStatusBarItemsContainer];
+    },
+
+    show: function()
+    {
+        WebInspector.Panel.prototype.show.call(this);
+        this._updateSidebarWidth();
+        if (this._shouldPopulateProfiles)
+            this._populateProfiles();
+    },
+
+    populateInterface: function()
+    {
+        if (this.visible)
+            this._populateProfiles();
+        else
+            this._shouldPopulateProfiles = true;
+    },
+
+    profilerWasEnabled: function()
+    {
+        this.reset();
+        this.populateInterface();
+    },
+
+    profilerWasDisabled: function()
+    {
+        this.reset();
+    },
+
+    reset: function()
+    {
+        if (this._profiles) {
+            var profiledLength = this._profiles.length;
+            for (var i = 0; i < profiledLength; ++i) {
+                var profile = this._profiles[i];
+                delete profile._profileView;
+            }
+        }
+
+        delete this.currentQuery;
+        this.searchCanceled();
+
+        this._profiles = [];
+        this._profilesIdMap = {};
+        this._profileGroups = {};
+        this._profileGroupsForLinks = {}
+
+        this.sidebarTreeElement.removeStyleClass("some-expandable");
+
+        this.profilesListTreeElement.removeChildren();
+        this.snapshotsListTreeElement.removeChildren();
+        this.profileViews.removeChildren();
+
+        this.profileViewStatusBarItemsContainer.removeChildren();
+
+        this._updateInterface();
+    },
+
+    handleKeyEvent: function(event)
+    {
+        this.sidebarTree.handleKeyEvent(event);
+    },
+
+    addProfile: function(profile)
+    {
+        this._profiles.push(profile);
+        this._profilesIdMap[profile.uid] = profile;
+
+        var sidebarParent = this.profilesListTreeElement;
+        var small = false;
+        var alternateTitle;
+
+        if (profile.title.indexOf(UserInitiatedProfileName) !== 0) {
+            if (!(profile.title in this._profileGroups))
+                this._profileGroups[profile.title] = [];
+
+            var group = this._profileGroups[profile.title];
+            group.push(profile);
+
+            if (group.length === 2) {
+                // Make a group TreeElement now that there are 2 profiles.
+                group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title);
+
+                // Insert at the same index for the first profile of the group.
+                var index = this.sidebarTree.children.indexOf(group[0]._profilesTreeElement);
+                this.sidebarTree.insertChild(group._profilesTreeElement, index);
+
+                // Move the first profile to the group.
+                var selected = group[0]._profilesTreeElement.selected;
+                this.sidebarTree.removeChild(group[0]._profilesTreeElement);
+                group._profilesTreeElement.appendChild(group[0]._profilesTreeElement);
+                if (selected) {
+                    group[0]._profilesTreeElement.select();
+                    group[0]._profilesTreeElement.reveal();
+                }
+
+                group[0]._profilesTreeElement.small = true;
+                group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);
+
+                this.sidebarTreeElement.addStyleClass("some-expandable");
+            }
+
+            if (group.length >= 2) {
+                sidebarParent = group._profilesTreeElement;
+                alternateTitle = WebInspector.UIString("Run %d", group.length);
+                small = true;
+            }
+        }
+
+        var profileTreeElement = new WebInspector.ProfileSidebarTreeElement(profile);
+        profileTreeElement.small = small;
+        if (alternateTitle)
+            profileTreeElement.mainTitle = alternateTitle;
+        profile._profilesTreeElement = profileTreeElement;
+
+        sidebarParent.appendChild(profileTreeElement);
+    },
+
+    showProfile: function(profile)
+    {
+        if (!profile)
+            return;
+
+        if (this.visibleView)
+            this.visibleView.hide();
+
+        var view = this.profileViewForProfile(profile);
+
+        view.show(this.profileViews);
+
+        profile._profilesTreeElement.select(true);
+        profile._profilesTreeElement.reveal();
+
+        this.visibleView = view;
+
+        this.profileViewStatusBarItemsContainer.removeChildren();
+
+        var statusBarItems = view.statusBarItems;
+        for (var i = 0; i < statusBarItems.length; ++i)
+            this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
+    },
+
+    showView: function(view)
+    {
+        this.showProfile(view.profile);
+    },
+
+    profileViewForProfile: function(profile)
+    {
+        if (!profile)
+            return null;
+        if (!profile._profileView)
+            profile._profileView = new WebInspector.ProfileView(profile);
+        return profile._profileView;
+    },
+
+    showProfileById: function(uid)
+    {
+        this.showProfile(this._profilesIdMap[uid]);
+    },
+
+    closeVisibleView: function()
+    {
+        if (this.visibleView)
+            this.visibleView.hide();
+        delete this.visibleView;
+    },
+
+    displayTitleForProfileLink: function(title)
+    {
+        title = unescape(title);
+        if (title.indexOf(UserInitiatedProfileName) === 0) {
+            title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1));
+        } else {
+            if (!(title in this._profileGroupsForLinks))
+                this._profileGroupsForLinks[title] = 0;
+
+            groupNumber = ++this._profileGroupsForLinks[title];
+
+            if (groupNumber > 2)
+                // The title is used in the console message announcing that a profile has started so it gets
+                // incremented twice as often as it's displayed
+                title += " " + WebInspector.UIString("Run %d", groupNumber / 2);
+        }
+        
+        return title;
+    },
+
+    get searchableViews()
+    {
+        var views = [];
+
+        const visibleView = this.visibleView;
+        if (visibleView && visibleView.performSearch)
+            views.push(visibleView);
+
+        var profilesLength = this._profiles.length;
+        for (var i = 0; i < profilesLength; ++i) {
+            var view = this.profileViewForProfile(this._profiles[i]);
+            if (!view.performSearch || view === visibleView)
+                continue;
+            views.push(view);
+        }
+
+        return views;
+    },
+
+    searchMatchFound: function(view, matches)
+    {
+        view.profile._profilesTreeElement.searchMatches = matches;
+    },
+
+    searchCanceled: function(startingNewSearch)
+    {
+        WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
+
+        if (!this._profiles)
+            return;
+
+        for (var i = 0; i < this._profiles.length; ++i) {
+            var profile = this._profiles[i];
+            profile._profilesTreeElement.searchMatches = 0;
+        }
+    },
+
+    setRecordingProfile: function(isProfiling)
+    {
+        this.recording = isProfiling;
+
+        if (isProfiling) {
+            this.recordButton.toggled = true;
+            this.recordButton.title = WebInspector.UIString("Stop profiling.");
+        } else {
+            this.recordButton.toggled = false;
+            this.recordButton.title = WebInspector.UIString("Start profiling.");
+        }
+    },
+    
+    resize: function()
+    {
+        var visibleView = this.visibleView;
+        if (visibleView && "resize" in visibleView)
+            visibleView.resize();
+    },
+
+    _updateInterface: function()
+    {
+        if (InspectorController.profilerEnabled()) {
+            this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable.");
+            this.enableToggleButton.toggled = true;
+            this.recordButton.visible = true;
+            if (Preferences.heapProfilerPresent)
+                this.snapshotButton.visible = true;
+            this.profileViewStatusBarItemsContainer.removeStyleClass("hidden");
+            this.panelEnablerView.visible = false;
+        } else {
+            this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable.");
+            this.enableToggleButton.toggled = false;
+            this.recordButton.visible = false;
+            this.snapshotButton.visible = false;
+            this.profileViewStatusBarItemsContainer.addStyleClass("hidden");
+            this.panelEnablerView.visible = true;
+        }
+    },
+
+    _recordClicked: function()
+    {
+        this.recording = !this.recording;
+
+        if (this.recording)
+            InspectorController.startProfiling();
+        else
+            InspectorController.stopProfiling();
+    },
+
+    _snapshotClicked: function()
+    {
+        InspectorController.takeHeapSnapshot();
+    },
+
+    _enableProfiling: function()
+    {
+        if (InspectorController.profilerEnabled())
+            return;
+        this._toggleProfiling(this.panelEnablerView.alwaysEnabled);
+    },
+
+    _toggleProfiling: function(optionalAlways)
+    {
+        if (InspectorController.profilerEnabled())
+            InspectorController.disableProfiler(true);
+        else
+            InspectorController.enableProfiler(!!optionalAlways);
+    },
+
+    _populateProfiles: function()
+    {
+        if (this.sidebarTree.children.length)
+            return;
+
+        var profiles = InspectorController.profiles();
+        var profilesLength = profiles.length;
+        for (var i = 0; i < profilesLength; ++i) {
+            var profile = profiles[i];
+            this.addProfile(profile);
+        }
+
+        if (this.sidebarTree.children[0])
+            this.sidebarTree.children[0].select();
+
+        delete this._shouldPopulateProfiles;
+    },
+
+    _startSidebarDragging: function(event)
+    {
+        WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
+    },
+
+    _sidebarDragging: function(event)
+    {
+        this._updateSidebarWidth(event.pageX);
+
+        event.preventDefault();
+    },
+
+    _endSidebarDragging: function(event)
+    {
+        WebInspector.elementDragEnd(event);
+    },
+
+    _updateSidebarWidth: function(width)
+    {
+        if (this.sidebarElement.offsetWidth <= 0) {
+            // The stylesheet hasn't loaded yet or the window is closed,
+            // so we can't calculate what is need. Return early.
+            return;
+        }
+
+        if (!("_currentSidebarWidth" in this))
+            this._currentSidebarWidth = this.sidebarElement.offsetWidth;
+
+        if (typeof width === "undefined")
+            width = this._currentSidebarWidth;
+
+        width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
+
+        this._currentSidebarWidth = width;
+
+        this.sidebarElement.style.width = width + "px";
+        this.profileViews.style.left = width + "px";
+        this.profileViewStatusBarItemsContainer.style.left = width + "px";
+        this.sidebarResizeElement.style.left = (width - 3) + "px";
+        
+        var visibleView = this.visibleView;
+        if (visibleView && "resize" in visibleView)
+            visibleView.resize();
+    }
+}
+
+WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
+
+WebInspector.ProfileSidebarTreeElement = function(profile)
+{
+    this.profile = profile;
+
+    if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
+        this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1);
+
+    WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "", "", profile, false);
+
+    this.refreshTitles();
+}
+
+WebInspector.ProfileSidebarTreeElement.prototype = {
+    onselect: function()
+    {
+        WebInspector.panels.profiles.showProfile(this.profile);
+    },
+
+    get mainTitle()
+    {
+        if (this._mainTitle)
+            return this._mainTitle;
+        if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
+            return WebInspector.UIString("Profile %d", this._profileNumber);
+        return this.profile.title;
+    },
+
+    set mainTitle(x)
+    {
+        this._mainTitle = x;
+        this.refreshTitles();
+    },
+
+    get subtitle()
+    {
+        // There is no subtitle.
+    },
+
+    set subtitle(x)
+    {
+        // Can't change subtitle.
+    },
+
+    set searchMatches(matches)
+    {
+        if (!matches) {
+            if (!this.bubbleElement)
+                return;
+            this.bubbleElement.removeStyleClass("search-matches");
+            this.bubbleText = "";
+            return;
+        }
+
+        this.bubbleText = matches;
+        this.bubbleElement.addStyleClass("search-matches");
+    }
+}
+
+WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
+
+WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle)
+{
+    WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
+}
+
+WebInspector.ProfileGroupSidebarTreeElement.prototype = {
+    onselect: function()
+    {
+        WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile);
+    }
+}
+
+WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSection.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSection.js
new file mode 100644
index 0000000..a4b2fba
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSection.js
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.PropertiesSection = function(title, subtitle)
+{
+    this.element = document.createElement("div");
+    this.element.className = "section";
+
+    this.headerElement = document.createElement("div");
+    this.headerElement.className = "header";
+
+    this.titleElement = document.createElement("div");
+    this.titleElement.className = "title";
+
+    this.subtitleElement = document.createElement("div");
+    this.subtitleElement.className = "subtitle";
+
+    this.headerElement.appendChild(this.subtitleElement);
+    this.headerElement.appendChild(this.titleElement);
+
+    this.headerElement.addEventListener("click", this.toggleExpanded.bind(this), false);
+
+    this.propertiesElement = document.createElement("ol");
+    this.propertiesElement.className = "properties";
+    this.propertiesTreeOutline = new TreeOutline(this.propertiesElement);
+    this.propertiesTreeOutline.section = this;
+
+    this.element.appendChild(this.headerElement);
+    this.element.appendChild(this.propertiesElement);
+
+    this.title = title;
+    this.subtitle = subtitle;
+    this._expanded = false;
+}
+
+WebInspector.PropertiesSection.prototype = {
+    get title()
+    {
+        return this._title;
+    },
+
+    set title(x)
+    {
+        if (this._title === x)
+            return;
+        this._title = x;
+
+        if (x instanceof Node) {
+            this.titleElement.removeChildren();
+            this.titleElement.appendChild(x);
+        } else
+          this.titleElement.textContent = x;
+    },
+
+    get subtitle()
+    {
+        return this._subtitle;
+    },
+
+    set subtitle(x)
+    {
+        if (this._subtitle === x)
+            return;
+        this._subtitle = x;
+        this.subtitleElement.innerHTML = x;
+    },
+
+    get expanded()
+    {
+        return this._expanded;
+    },
+
+    set expanded(x)
+    {
+        if (x)
+            this.expand();
+        else
+            this.collapse();
+    },
+
+    get populated()
+    {
+        return this._populated;
+    },
+
+    set populated(x)
+    {
+        this._populated = x;
+        if (!x && this.onpopulate && this._expanded) {
+            this.onpopulate(this);
+            this._populated = true;
+        }
+    },
+
+    expand: function()
+    {
+        if (this._expanded)
+            return;
+        this._expanded = true;
+        this.element.addStyleClass("expanded");
+
+        if (!this._populated && this.onpopulate) {
+            this.onpopulate(this);
+            this._populated = true;
+        }
+    },
+
+    collapse: function()
+    {
+        if (!this._expanded)
+            return;
+        this._expanded = false;
+        this.element.removeStyleClass("expanded");
+    },
+
+    toggleExpanded: function()
+    {
+        this.expanded = !this.expanded;
+    }
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSidebarPane.js
new file mode 100644
index 0000000..ec08210b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSidebarPane.js
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.PropertiesSidebarPane = function()
+{
+    WebInspector.SidebarPane.call(this, WebInspector.UIString("Properties"));
+}
+
+WebInspector.PropertiesSidebarPane.prototype = {
+    update: function(node)
+    {
+        var body = this.bodyElement;
+
+        body.removeChildren();
+
+        this.sections = [];
+
+        if (!node)
+            return;
+
+        var self = this;
+        var callback = function(prototypes) {
+            var body = self.bodyElement;
+            body.removeChildren();
+            self.sections = [];
+
+            // Get array of prototype user-friendly names.
+            for (var i = 0; i < prototypes.length; ++i) {
+                var prototype = new WebInspector.ObjectProxy(node.id, [], i);
+                var section = new WebInspector.ObjectPropertiesSection(prototype, prototypes[i], WebInspector.UIString("Prototype"));
+                self.sections.push(section);
+                body.appendChild(section.element);
+            }
+        };
+        InjectedScriptAccess.getPrototypes(node.id, callback);
+    }
+}
+
+WebInspector.PropertiesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Resource.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Resource.js
new file mode 100644
index 0000000..4dac093
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Resource.js
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.Resource = function(requestHeaders, url, domain, path, lastPathComponent, identifier, mainResource, cached, requestMethod, requestFormData)
+{
+    this.identifier = identifier;
+
+    this.startTime = -1;
+    this.endTime = -1;
+    this.mainResource = mainResource;
+    this.requestHeaders = requestHeaders;
+    this.url = url;
+    this.domain = domain;
+    this.path = path;
+    this.lastPathComponent = lastPathComponent;
+    this.cached = cached;
+    this.requestMethod = requestMethod || "";
+    this.requestFormData = requestFormData || "";
+
+    this.category = WebInspector.resourceCategories.other;
+}
+
+// Keep these in sync with WebCore::InspectorResource::Type
+WebInspector.Resource.Type = {
+    Document:   0,
+    Stylesheet: 1,
+    Image:      2,
+    Font:       3,
+    Script:     4,
+    XHR:        5,
+    Other:      6,
+
+    isTextType: function(type)
+    {
+        return (type === this.Document) || (type === this.Stylesheet) || (type === this.Script) || (type === this.XHR);
+    },
+
+    toString: function(type)
+    {
+        switch (type) {
+            case this.Document:
+                return WebInspector.UIString("document");
+            case this.Stylesheet:
+                return WebInspector.UIString("stylesheet");
+            case this.Image:
+                return WebInspector.UIString("image");
+            case this.Font:
+                return WebInspector.UIString("font");
+            case this.Script:
+                return WebInspector.UIString("script");
+            case this.XHR:
+                return WebInspector.UIString("XHR");
+            case this.Other:
+            default:
+                return WebInspector.UIString("other");
+        }
+    }
+}
+
+WebInspector.Resource.prototype = {
+    get url()
+    {
+        return this._url;
+    },
+
+    set url(x)
+    {
+        if (this._url === x)
+            return;
+
+        var oldURL = this._url;
+        this._url = x;
+
+        // FIXME: We should make the WebInspector object listen for the "url changed" event.
+        // Then resourceURLChanged can be removed.
+        WebInspector.resourceURLChanged(this, oldURL);
+
+        this.dispatchEventToListeners("url changed");
+    },
+
+    get domain()
+    {
+        return this._domain;
+    },
+
+    set domain(x)
+    {
+        if (this._domain === x)
+            return;
+        this._domain = x;
+    },
+
+    get lastPathComponent()
+    {
+        return this._lastPathComponent;
+    },
+
+    set lastPathComponent(x)
+    {
+        if (this._lastPathComponent === x)
+            return;
+        this._lastPathComponent = x;
+        this._lastPathComponentLowerCase = x ? x.toLowerCase() : null;
+    },
+
+    get displayName()
+    {
+        var title = this.lastPathComponent;
+        if (!title)
+            title = this.displayDomain;
+        if (!title && this.url)
+            title = this.url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : "");
+        if (title === "/")
+            title = this.url;
+        return title;
+    },
+
+    get displayDomain()
+    {
+        // WebInspector.Database calls this, so don't access more than this.domain.
+        if (this.domain && (!WebInspector.mainResource || (WebInspector.mainResource && this.domain !== WebInspector.mainResource.domain)))
+            return this.domain;
+        return "";
+    },
+
+    get startTime()
+    {
+        return this._startTime || -1;
+    },
+
+    set startTime(x)
+    {
+        if (this._startTime === x)
+            return;
+
+        this._startTime = x;
+
+        if (WebInspector.panels.resources)
+            WebInspector.panels.resources.refreshResource(this);
+    },
+
+    get responseReceivedTime()
+    {
+        return this._responseReceivedTime || -1;
+    },
+
+    set responseReceivedTime(x)
+    {
+        if (this._responseReceivedTime === x)
+            return;
+
+        this._responseReceivedTime = x;
+
+        if (WebInspector.panels.resources)
+            WebInspector.panels.resources.refreshResource(this);
+    },
+
+    get endTime()
+    {
+        return this._endTime || -1;
+    },
+
+    set endTime(x)
+    {
+        if (this._endTime === x)
+            return;
+
+        this._endTime = x;
+
+        if (WebInspector.panels.resources)
+            WebInspector.panels.resources.refreshResource(this);
+    },
+
+    get duration()
+    {
+        if (this._endTime === -1 || this._startTime === -1)
+            return -1;
+        return this._endTime - this._startTime;
+    },
+
+    get latency()
+    {
+        if (this._responseReceivedTime === -1 || this._startTime === -1)
+            return -1;
+        return this._responseReceivedTime - this._startTime;
+    },
+
+    get contentLength()
+    {
+        return this._contentLength || 0;
+    },
+
+    set contentLength(x)
+    {
+        if (this._contentLength === x)
+            return;
+
+        this._contentLength = x;
+
+        if (WebInspector.panels.resources)
+            WebInspector.panels.resources.refreshResource(this);
+    },
+
+    get expectedContentLength()
+    {
+        return this._expectedContentLength || 0;
+    },
+
+    set expectedContentLength(x)
+    {
+        if (this._expectedContentLength === x)
+            return;
+        this._expectedContentLength = x;
+    },
+
+    get finished()
+    {
+        return this._finished;
+    },
+
+    set finished(x)
+    {
+        if (this._finished === x)
+            return;
+
+        this._finished = x;
+
+        if (x) {
+            this._checkTips();
+            this._checkWarnings();
+            this.dispatchEventToListeners("finished");
+        }
+    },
+
+    get failed()
+    {
+        return this._failed;
+    },
+
+    set failed(x)
+    {
+        this._failed = x;
+    },
+
+    get category()
+    {
+        return this._category;
+    },
+
+    set category(x)
+    {
+        if (this._category === x)
+            return;
+
+        var oldCategory = this._category;
+        if (oldCategory)
+            oldCategory.removeResource(this);
+
+        this._category = x;
+
+        if (this._category)
+            this._category.addResource(this);
+
+        if (WebInspector.panels.resources) {
+            WebInspector.panels.resources.refreshResource(this);
+            WebInspector.panels.resources.recreateViewForResourceIfNeeded(this);
+        }
+    },
+
+    get mimeType()
+    {
+        return this._mimeType;
+    },
+
+    set mimeType(x)
+    {
+        if (this._mimeType === x)
+            return;
+
+        this._mimeType = x;
+    },
+
+    get type()
+    {
+        return this._type;
+    },
+
+    set type(x)
+    {
+        if (this._type === x)
+            return;
+
+        this._type = x;
+
+        switch (x) {
+            case WebInspector.Resource.Type.Document:
+                this.category = WebInspector.resourceCategories.documents;
+                break;
+            case WebInspector.Resource.Type.Stylesheet:
+                this.category = WebInspector.resourceCategories.stylesheets;
+                break;
+            case WebInspector.Resource.Type.Script:
+                this.category = WebInspector.resourceCategories.scripts;
+                break;
+            case WebInspector.Resource.Type.Image:
+                this.category = WebInspector.resourceCategories.images;
+                break;
+            case WebInspector.Resource.Type.Font:
+                this.category = WebInspector.resourceCategories.fonts;
+                break;
+            case WebInspector.Resource.Type.XHR:
+                this.category = WebInspector.resourceCategories.xhr;
+                break;
+            case WebInspector.Resource.Type.Other:
+            default:
+                this.category = WebInspector.resourceCategories.other;
+                break;
+        }
+    },
+
+    get requestHeaders()
+    {
+        if (this._requestHeaders === undefined)
+            this._requestHeaders = {};
+        return this._requestHeaders;
+    },
+
+    set requestHeaders(x)
+    {
+        if (this._requestHeaders === x)
+            return;
+
+        this._requestHeaders = x;
+        delete this._sortedRequestHeaders;
+
+        this.dispatchEventToListeners("requestHeaders changed");
+    },
+
+    get sortedRequestHeaders()
+    {
+        if (this._sortedRequestHeaders !== undefined)
+            return this._sortedRequestHeaders;
+
+        this._sortedRequestHeaders = [];
+        for (var key in this.requestHeaders)
+            this._sortedRequestHeaders.push({header: key, value: this.requestHeaders[key]});
+        this._sortedRequestHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) });
+
+        return this._sortedRequestHeaders;
+    },
+
+    get responseHeaders()
+    {
+        if (this._responseHeaders === undefined)
+            this._responseHeaders = {};
+        return this._responseHeaders;
+    },
+
+    set responseHeaders(x)
+    {
+        if (this._responseHeaders === x)
+            return;
+
+        this._responseHeaders = x;
+        delete this._sortedResponseHeaders;
+
+        this.dispatchEventToListeners("responseHeaders changed");
+    },
+
+    get sortedResponseHeaders()
+    {
+        if (this._sortedResponseHeaders !== undefined)
+            return this._sortedResponseHeaders;
+
+        this._sortedResponseHeaders = [];
+        for (var key in this.responseHeaders)
+            this._sortedResponseHeaders.push({header: key, value: this.responseHeaders[key]});
+        this._sortedResponseHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) });
+
+        return this._sortedResponseHeaders;
+    },
+
+    get scripts()
+    {
+        if (!("_scripts" in this))
+            this._scripts = [];
+        return this._scripts;
+    },
+
+    addScript: function(script)
+    {
+        if (!script)
+            return;
+        this.scripts.unshift(script);
+        script.resource = this;
+    },
+
+    removeAllScripts: function()
+    {
+        if (!this._scripts)
+            return;
+
+        for (var i = 0; i < this._scripts.length; ++i) {
+            if (this._scripts[i].resource === this)
+                delete this._scripts[i].resource;
+        }
+
+        delete this._scripts;
+    },
+
+    removeScript: function(script)
+    {
+        if (!script)
+            return;
+
+        if (script.resource === this)
+            delete script.resource;
+
+        if (!this._scripts)
+            return;
+
+        this._scripts.remove(script);
+    },
+
+    get errors()
+    {
+        return this._errors || 0;
+    },
+
+    set errors(x)
+    {
+        this._errors = x;
+    },
+
+    get warnings()
+    {
+        return this._warnings || 0;
+    },
+
+    set warnings(x)
+    {
+        this._warnings = x;
+    },
+
+    get tips()
+    {
+        if (!("_tips" in this))
+            this._tips = {};
+        return this._tips;
+    },
+
+    _addTip: function(tip)
+    {
+        if (tip.id in this.tips)
+            return;
+
+        this.tips[tip.id] = tip;
+
+        // FIXME: Re-enable this code once we have a scope bar in the Console.
+        // Otherwise, we flood the Console with too many tips.
+        /*
+        var msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other,
+            WebInspector.ConsoleMessage.MessageType.Log, WebInspector.ConsoleMessage.MessageLevel.Tip, 
+            -1, this.url, null, 1, tip.message);
+        WebInspector.console.addMessage(msg);
+        */
+    },
+
+    _checkTips: function()
+    {
+        for (var tip in WebInspector.Tips)
+            this._checkTip(WebInspector.Tips[tip]);
+    },
+
+    _checkTip: function(tip)
+    {
+        var addTip = false;
+        switch (tip.id) {
+            case WebInspector.Tips.ResourceNotCompressed.id:
+                addTip = this._shouldCompress();
+                break;
+        }
+
+        if (addTip)
+            this._addTip(tip);
+    },
+
+    _shouldCompress: function()
+    {
+        return WebInspector.Resource.Type.isTextType(this.type)
+            && this.domain
+            && !("Content-Encoding" in this.responseHeaders)
+            && this.contentLength !== undefined
+            && this.contentLength >= 512;
+    },
+
+    _mimeTypeIsConsistentWithType: function()
+    {
+        if (typeof this.type === "undefined"
+         || this.type === WebInspector.Resource.Type.Other
+         || this.type === WebInspector.Resource.Type.XHR)
+            return true;
+
+        if (this.mimeType in WebInspector.MIMETypes)
+            return this.type in WebInspector.MIMETypes[this.mimeType];
+
+        return true;
+    },
+
+    _checkWarnings: function()
+    {
+        for (var warning in WebInspector.Warnings)
+            this._checkWarning(WebInspector.Warnings[warning]);
+    },
+
+    _checkWarning: function(warning)
+    {
+        var msg;
+        switch (warning.id) {
+            case WebInspector.Warnings.IncorrectMIMEType.id:
+                if (!this._mimeTypeIsConsistentWithType())
+                    msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other,
+                        WebInspector.ConsoleMessage.MessageType.Log, 
+                        WebInspector.ConsoleMessage.MessageLevel.Warning, -1, this.url, null, 1,
+                        String.sprintf(WebInspector.Warnings.IncorrectMIMEType.message,
+                        WebInspector.Resource.Type.toString(this.type), this.mimeType));
+                break;
+        }
+
+        if (msg)
+            WebInspector.console.addMessage(msg);
+    }
+}
+
+WebInspector.Resource.prototype.__proto__ = WebInspector.Object.prototype;
+
+WebInspector.Resource.CompareByStartTime = function(a, b)
+{
+    if (a.startTime < b.startTime)
+        return -1;
+    if (a.startTime > b.startTime)
+        return 1;
+    return 0;
+}
+
+WebInspector.Resource.CompareByResponseReceivedTime = function(a, b)
+{
+    if (a.responseReceivedTime === -1 && b.responseReceivedTime !== -1)
+        return 1;
+    if (a.responseReceivedTime !== -1 && b.responseReceivedTime === -1)
+        return -1;
+    if (a.responseReceivedTime < b.responseReceivedTime)
+        return -1;
+    if (a.responseReceivedTime > b.responseReceivedTime)
+        return 1;
+    return 0;
+}
+
+WebInspector.Resource.CompareByEndTime = function(a, b)
+{
+    if (a.endTime === -1 && b.endTime !== -1)
+        return 1;
+    if (a.endTime !== -1 && b.endTime === -1)
+        return -1;
+    if (a.endTime < b.endTime)
+        return -1;
+    if (a.endTime > b.endTime)
+        return 1;
+    return 0;
+}
+
+WebInspector.Resource.CompareByDuration = function(a, b)
+{
+    if (a.duration < b.duration)
+        return -1;
+    if (a.duration > b.duration)
+        return 1;
+    return 0;
+}
+
+WebInspector.Resource.CompareByLatency = function(a, b)
+{
+    if (a.latency < b.latency)
+        return -1;
+    if (a.latency > b.latency)
+        return 1;
+    return 0;
+}
+
+WebInspector.Resource.CompareBySize = function(a, b)
+{
+    if (a.contentLength < b.contentLength)
+        return -1;
+    if (a.contentLength > b.contentLength)
+        return 1;
+    return 0;
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceCategory.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceCategory.js
new file mode 100644
index 0000000..fc508d0f59
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceCategory.js
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.ResourceCategory = function(title, name)
+{
+    this.name = name;
+    this.title = title;
+    this.resources = [];
+}
+
+WebInspector.ResourceCategory.prototype = {
+    toString: function()
+    {
+        return this.title;
+    },
+
+    addResource: function(resource)
+    {
+        var a = resource;
+        var resourcesLength = this.resources.length;
+        for (var i = 0; i < resourcesLength; ++i) {
+            var b = this.resources[i];
+            if (a._lastPathComponentLowerCase && b._lastPathComponentLowerCase)
+                if (a._lastPathComponentLowerCase < b._lastPathComponentLowerCase)
+                    break;
+            else if (a.name && b.name)
+                if (a.name < b.name)
+                    break;
+        }
+
+        this.resources.splice(i, 0, resource);
+    },
+
+    removeResource: function(resource)
+    {
+        this.resources.remove(resource, true);
+    },
+
+    removeAllResources: function(resource)
+    {
+        this.resources = [];
+    }
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceView.js
new file mode 100644
index 0000000..d745920
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceView.js
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) IBM Corp. 2009  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.ResourceView = function(resource)
+{
+    WebInspector.View.call(this);
+
+    this.element.addStyleClass("resource-view");
+
+    this.resource = resource;
+
+    this.headersElement = document.createElement("div");
+    this.headersElement.className = "resource-view-headers";
+    this.element.appendChild(this.headersElement);
+
+    this.contentElement = document.createElement("div");
+    this.contentElement.className = "resource-view-content";
+    this.element.appendChild(this.contentElement);
+
+    this.headersListElement = document.createElement("ol");
+    this.headersListElement.className = "outline-disclosure";
+    this.headersElement.appendChild(this.headersListElement);
+
+    this.headersTreeOutline = new TreeOutline(this.headersListElement);
+    this.headersTreeOutline.expandTreeElementsWhenArrowing = true;
+
+    this.urlTreeElement = new TreeElement("", null, false);
+    this.urlTreeElement.selectable = false;
+    this.headersTreeOutline.appendChild(this.urlTreeElement);
+
+    this.requestHeadersTreeElement = new TreeElement("", null, true);
+    this.requestHeadersTreeElement.expanded = false;
+    this.requestHeadersTreeElement.selectable = false;
+    this.headersTreeOutline.appendChild(this.requestHeadersTreeElement);
+
+    this._decodeHover = WebInspector.UIString("Double-Click to toggle between URL encoded and decoded formats");
+    this._decodeRequestParameters = true;
+
+    this.queryStringTreeElement = new TreeElement("", null, true);
+    this.queryStringTreeElement.expanded = false;
+    this.queryStringTreeElement.selectable = false;
+    this.queryStringTreeElement.hidden = true;
+    this.headersTreeOutline.appendChild(this.queryStringTreeElement);
+
+    this.formDataTreeElement = new TreeElement("", null, true);
+    this.formDataTreeElement.expanded = false;
+    this.formDataTreeElement.selectable = false;
+    this.formDataTreeElement.hidden = true;
+    this.headersTreeOutline.appendChild(this.formDataTreeElement);
+
+    this.requestPayloadTreeElement = new TreeElement(WebInspector.UIString("Request Payload"), null, true);
+    this.requestPayloadTreeElement.expanded = false;
+    this.requestPayloadTreeElement.selectable = false;
+    this.requestPayloadTreeElement.hidden = true;
+    this.headersTreeOutline.appendChild(this.requestPayloadTreeElement);
+
+    this.responseHeadersTreeElement = new TreeElement("", null, true);
+    this.responseHeadersTreeElement.expanded = false;
+    this.responseHeadersTreeElement.selectable = false;
+    this.headersTreeOutline.appendChild(this.responseHeadersTreeElement);
+
+    this.headersVisible = true;
+
+    resource.addEventListener("url changed", this._refreshURL, this);
+    resource.addEventListener("requestHeaders changed", this._refreshRequestHeaders, this);
+    resource.addEventListener("responseHeaders changed", this._refreshResponseHeaders, this);
+
+    this._refreshURL();
+    this._refreshRequestHeaders();
+    this._refreshResponseHeaders();
+}
+
+WebInspector.ResourceView.prototype = {
+    get headersVisible()
+    {
+        return this._headersVisible;
+    },
+
+    set headersVisible(x)
+    {
+        if (x === this._headersVisible)
+            return;
+
+        this._headersVisible = x;
+
+        if (x)
+            this.element.addStyleClass("headers-visible");
+        else
+            this.element.removeStyleClass("headers-visible");
+    },
+
+    attach: function()
+    {
+        if (!this.element.parentNode) {
+            var parentElement = (document.getElementById("resource-views") || document.getElementById("script-resource-views"));
+            if (parentElement)
+                parentElement.appendChild(this.element);
+        }
+    },
+
+    _refreshURL: function()
+    {
+        var url = this.resource.url;
+        this.urlTreeElement.title = this.resource.requestMethod + " " + url.escapeHTML();
+        this._refreshQueryString();
+    },
+
+    _refreshQueryString: function()
+    {
+        var url = this.resource.url;
+        var hasQueryString = url.indexOf("?") >= 0;
+
+        if (!hasQueryString) {
+            this.queryStringTreeElement.hidden = true;
+            return;
+        }
+
+        this.queryStringTreeElement.hidden = false;
+        var parmString = url.split("?", 2)[1];
+        this._refreshParms(WebInspector.UIString("Query String Parameters"), parmString, this.queryStringTreeElement);
+    },
+
+    _refreshFormData: function()
+    {
+        this.formDataTreeElement.hidden = true;
+        this.requestPayloadTreeElement.hidden = true;
+
+        var isFormData = this.resource.requestFormData;
+        if (!isFormData)
+            return;
+
+        var isFormEncoded = false;
+        var requestContentType = this._getHeaderValue(this.resource.requestHeaders, "Content-Type");
+        if (requestContentType == "application/x-www-form-urlencoded")
+            isFormEncoded = true;
+
+        if (isFormEncoded) {
+            this.formDataTreeElement.hidden = false;
+            this._refreshParms(WebInspector.UIString("Form Data"), this.resource.requestFormData, this.formDataTreeElement);
+        } else {
+            this.requestPayloadTreeElement.hidden = false;
+            this._refreshRequestPayload(this.resource.requestFormData);
+        }
+    },
+
+    _refreshRequestPayload: function(formData)
+    {
+        this.requestPayloadTreeElement.removeChildren();
+
+        var title = "<div class=\"header-name\">&nbsp;</div>";
+        title += "<div class=\"raw-form-data header-value\">" + formData.escapeHTML() + "</div>";
+        var parmTreeElement = new TreeElement(title, null, false);
+        this.requestPayloadTreeElement.appendChild(parmTreeElement);
+    },
+
+    _refreshParms: function(title, parmString, parmsTreeElement)
+    {
+        var parms = parmString.split("&");
+        for (var i = 0; i < parms.length; ++i) {
+            var parm = parms[i];
+            parm = parm.split("=", 2);
+            if (parm.length == 1)
+                parm.push("");
+            parms[i] = parm;
+        }
+
+        parmsTreeElement.removeChildren();
+
+        parmsTreeElement.title = title + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", parms.length) + "</span>";
+
+        for (var i = 0; i < parms.length; ++i) {
+            var key = parms[i][0];
+            var val = parms[i][1];
+
+            if (val.indexOf("%") >= 0)
+                if (this._decodeRequestParameters)
+                    val = decodeURIComponent(val).replace(/\+/g, " ");
+
+            var title = "<div class=\"header-name\">" + key.escapeHTML() + ":</div>";
+            title += "<div class=\"header-value\">" + val.escapeHTML() + "</div>";
+
+            var parmTreeElement = new TreeElement(title, null, false);
+            parmTreeElement.selectable = false;
+            parmTreeElement.tooltip = this._decodeHover;
+            parmTreeElement.ondblclick = this._toggleURLdecoding.bind(this);
+            parmsTreeElement.appendChild(parmTreeElement);
+        }
+    },
+
+    _toggleURLdecoding: function(treeElement, event)
+    {
+        this._decodeRequestParameters = !this._decodeRequestParameters;
+        this._refreshQueryString();
+        this._refreshFormData();
+    },
+
+    _getHeaderValue: function(headers, key)
+    {
+        var lowerKey = key.toLowerCase();
+        for (var testKey in headers) {
+            if (testKey.toLowerCase() === lowerKey)
+                return headers[testKey];
+        }
+    },
+
+    _refreshRequestHeaders: function()
+    {
+        this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, this.requestHeadersTreeElement);
+        this._refreshFormData();
+    },
+
+    _refreshResponseHeaders: function()
+    {
+        this._refreshHeaders(WebInspector.UIString("Response Headers"), this.resource.sortedResponseHeaders, this.responseHeadersTreeElement);
+    },
+
+    _refreshHeaders: function(title, headers, headersTreeElement)
+    {
+        headersTreeElement.removeChildren();
+
+        var length = headers.length;
+        headersTreeElement.title = title.escapeHTML() + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", length) + "</span>";
+        headersTreeElement.hidden = !length;
+
+        var length = headers.length;
+        for (var i = 0; i < length; ++i) {
+            var title = "<div class=\"header-name\">" + headers[i].header.escapeHTML() + ":</div>";
+            title += "<div class=\"header-value\">" + headers[i].value.escapeHTML() + "</div>"
+
+            var headerTreeElement = new TreeElement(title, null, false);
+            headerTreeElement.selectable = false;
+            headersTreeElement.appendChild(headerTreeElement);
+        }
+    }
+}
+
+WebInspector.ResourceView.prototype.__proto__ = WebInspector.View.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourcesPanel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourcesPanel.js
new file mode 100644
index 0000000..680f66c
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourcesPanel.js
@@ -0,0 +1,1462 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2008, 2009 Anthony Ricaud <[email protected]>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.ResourcesPanel = function()
+{
+    WebInspector.Panel.call(this);
+
+    this.element.addStyleClass("resources");
+
+    this.filterBarElement = document.createElement("div");
+    this.filterBarElement.id = "resources-filter";
+    this.element.appendChild(this.filterBarElement);
+
+    this.viewsContainerElement = document.createElement("div");
+    this.viewsContainerElement.id = "resource-views";
+    this.element.appendChild(this.viewsContainerElement);
+
+    this.containerElement = document.createElement("div");
+    this.containerElement.id = "resources-container";
+    this.containerElement.addEventListener("scroll", this._updateDividersLabelBarPosition.bind(this), false);
+    this.element.appendChild(this.containerElement);
+
+    this.sidebarElement = document.createElement("div");
+    this.sidebarElement.id = "resources-sidebar";
+    this.sidebarElement.className = "sidebar";
+    this.containerElement.appendChild(this.sidebarElement);
+
+    this.sidebarResizeElement = document.createElement("div");
+    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
+    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
+    this.element.appendChild(this.sidebarResizeElement);
+
+    this.containerContentElement = document.createElement("div");
+    this.containerContentElement.id = "resources-container-content";
+    this.containerElement.appendChild(this.containerContentElement);
+
+    this.summaryBar = new WebInspector.SummaryBar(this.categories);
+    this.summaryBar.element.id = "resources-summary";
+    this.containerContentElement.appendChild(this.summaryBar.element);
+
+    this.resourcesGraphsElement = document.createElement("div");
+    this.resourcesGraphsElement.id = "resources-graphs";
+    this.containerContentElement.appendChild(this.resourcesGraphsElement);
+
+    this.dividersElement = document.createElement("div");
+    this.dividersElement.id = "resources-dividers";
+    this.containerContentElement.appendChild(this.dividersElement);
+
+    this.dividersLabelBarElement = document.createElement("div");
+    this.dividersLabelBarElement.id = "resources-dividers-label-bar";
+    this.containerContentElement.appendChild(this.dividersLabelBarElement);
+
+    this.sidebarTreeElement = document.createElement("ol");
+    this.sidebarTreeElement.className = "sidebar-tree";
+    this.sidebarElement.appendChild(this.sidebarTreeElement);
+
+    this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
+
+    var timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time"));
+    timeGraphItem.onselect = this._graphSelected.bind(this);
+
+    var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator();
+    var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator();
+
+    timeGraphItem.sortingOptions = [
+        { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator },
+        { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator },
+        { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator },
+        { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator },
+        { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator },
+    ];
+
+    timeGraphItem.selectedSortingOptionIndex = 1;
+
+    var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size"));
+    sizeGraphItem.onselect = this._graphSelected.bind(this);
+
+    var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator();
+    sizeGraphItem.sortingOptions = [
+        { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator },
+    ];
+
+    sizeGraphItem.selectedSortingOptionIndex = 0;
+
+    this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true);
+    this.sidebarTree.appendChild(this.graphsTreeElement);
+
+    this.graphsTreeElement.appendChild(timeGraphItem);
+    this.graphsTreeElement.appendChild(sizeGraphItem);
+    this.graphsTreeElement.expand();
+
+    this.resourcesTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true);
+    this.sidebarTree.appendChild(this.resourcesTreeElement);
+
+    this.resourcesTreeElement.expand();
+
+    var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel.");
+    var panelEnablerDisclaimer = WebInspector.UIString("Enabling resource tracking will reload the page and make page loading slower.");
+    var panelEnablerButton = WebInspector.UIString("Enable resource tracking");
+
+    this.panelEnablerView = new WebInspector.PanelEnablerView("resources", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
+    this.panelEnablerView.addEventListener("enable clicked", this._enableResourceTracking, this);
+
+    this.element.appendChild(this.panelEnablerView.element);
+
+    this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
+    this.enableToggleButton.addEventListener("click", this._toggleResourceTracking.bind(this), false);
+
+    this.largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "resources-larger-resources-status-bar-item");
+    this.largerResourcesButton.toggled = true;
+    this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
+
+    this.sortingSelectElement = document.createElement("select");
+    this.sortingSelectElement.className = "status-bar-item";
+    this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false);
+
+    var createFilterElement = function (category) {
+        var categoryElement = document.createElement("li");
+        categoryElement.category = category;
+        categoryElement.addStyleClass(category);
+        var label = WebInspector.UIString("All");
+        if (WebInspector.resourceCategories[category])
+            label = WebInspector.resourceCategories[category].title;
+        categoryElement.appendChild(document.createTextNode(label));
+        categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
+        this.filterBarElement.appendChild(categoryElement);
+        return categoryElement;
+    };
+
+    var allElement = createFilterElement.call(this, "all");
+    this.filter(allElement.category);
+    for (var category in this.categories)
+        createFilterElement.call(this, category);
+
+    this.reset();
+
+    timeGraphItem.select();
+}
+
+WebInspector.ResourcesPanel.prototype = {
+    toolbarItemClass: "resources",
+
+    get categories()
+    {
+        if (!this._categories) {
+            this._categories = {documents: {color: {r: 47, g: 102, b: 236}}, stylesheets: {color: {r: 157, g: 231, b: 119}}, images: {color: {r: 164, g: 60, b: 255}}, scripts: {color: {r: 255, g: 121, b: 0}}, xhr: {color: {r: 231, g: 231, b: 10}}, fonts: {color: {r: 255, g: 82, b: 62}}, other: {color: {r: 186, g: 186, b: 186}}};
+            for (var category in this._categories) {
+                this._categories[category].title = WebInspector.resourceCategories[category].title;
+            }
+        }
+        return this._categories; 
+    },
+
+    filter: function (category) {
+        if (this._filterCategory && this._filterCategory === category)
+            return;
+
+        if (this._filterCategory) {
+            var filterElement = this.filterBarElement.getElementsByClassName(this._filterCategory)[0];
+            filterElement.removeStyleClass("selected");
+            var oldClass = "filter-" + this._filterCategory;
+            this.resourcesTreeElement.childrenListElement.removeStyleClass(oldClass);
+            this.resourcesGraphsElement.removeStyleClass(oldClass);
+        }
+        this._filterCategory = category;
+        var filterElement = this.filterBarElement.getElementsByClassName(this._filterCategory)[0];
+        filterElement.addStyleClass("selected");
+        var newClass = "filter-" + this._filterCategory;
+        this.resourcesTreeElement.childrenListElement.addStyleClass(newClass);
+        this.resourcesGraphsElement.addStyleClass(newClass);
+    },
+
+    _updateFilter: function (e) {
+        this.filter(e.target.category);
+    },
+
+    get toolbarItemLabel()
+    {
+        return WebInspector.UIString("Resources");
+    },
+
+    get statusBarItems()
+    {
+        return [this.enableToggleButton.element, this.largerResourcesButton.element, this.sortingSelectElement];
+    },
+
+    show: function()
+    {
+        WebInspector.Panel.prototype.show.call(this);
+
+        this._updateDividersLabelBarPosition();
+        this._updateSidebarWidth();
+        this.refreshIfNeeded();
+
+        var visibleView = this.visibleView;
+        if (visibleView) {
+            visibleView.headersVisible = true;
+            visibleView.show(this.viewsContainerElement);
+        }
+
+        // Hide any views that are visible that are not this panel's current visible view.
+        // This can happen when a ResourceView is visible in the Scripts panel then switched
+        // to the this panel.
+        var resourcesLength = this._resources.length;
+        for (var i = 0; i < resourcesLength; ++i) {
+            var resource = this._resources[i];
+            var view = resource._resourcesView;
+            if (!view || view === visibleView)
+                continue;
+            view.visible = false;
+        }
+    },
+
+    resize: function()
+    {
+        this._updateGraphDividersIfNeeded();
+
+        var visibleView = this.visibleView;
+        if (visibleView && "resize" in visibleView)
+            visibleView.resize();
+    },
+
+    get searchableViews()
+    {
+        var views = [];
+
+        const visibleView = this.visibleView;
+        if (visibleView && visibleView.performSearch)
+            views.push(visibleView);
+
+        var resourcesLength = this._resources.length;
+        for (var i = 0; i < resourcesLength; ++i) {
+            var resource = this._resources[i];
+            if (!resource._resourcesTreeElement)
+                continue;
+            var resourceView = this.resourceViewForResource(resource);
+            if (!resourceView.performSearch || resourceView === visibleView)
+                continue;
+            views.push(resourceView);
+        }
+
+        return views;
+    },
+
+    get searchResultsSortFunction()
+    {
+        const resourceTreeElementSortFunction = this.sortingFunction;
+
+        function sortFuction(a, b)
+        {
+            return resourceTreeElementSortFunction(a.resource._resourcesTreeElement, b.resource._resourcesTreeElement);
+        }
+
+        return sortFuction;
+    },
+
+    searchMatchFound: function(view, matches)
+    {
+        view.resource._resourcesTreeElement.searchMatches = matches;
+    },
+
+    searchCanceled: function(startingNewSearch)
+    {
+        WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
+
+        if (startingNewSearch || !this._resources)
+            return;
+
+        for (var i = 0; i < this._resources.length; ++i) {
+            var resource = this._resources[i];
+            if (resource._resourcesTreeElement)
+                resource._resourcesTreeElement.updateErrorsAndWarnings();
+        }
+    },
+
+    performSearch: function(query)
+    {
+        for (var i = 0; i < this._resources.length; ++i) {
+            var resource = this._resources[i];
+            if (resource._resourcesTreeElement)
+                resource._resourcesTreeElement.resetBubble();
+        }
+
+        WebInspector.Panel.prototype.performSearch.call(this, query);
+    },
+
+    get visibleView()
+    {
+        if (this.visibleResource)
+            return this.visibleResource._resourcesView;
+        return null;
+    },
+
+    get calculator()
+    {
+        return this._calculator;
+    },
+
+    set calculator(x)
+    {
+        if (!x || this._calculator === x)
+            return;
+
+        this._calculator = x;
+        this._calculator.reset();
+
+        this._staleResources = this._resources;
+        this.refresh();
+    },
+
+    get sortingFunction()
+    {
+        return this._sortingFunction;
+    },
+
+    set sortingFunction(x)
+    {
+        this._sortingFunction = x;
+        this._sortResourcesIfNeeded();
+    },
+
+    get needsRefresh()
+    {
+        return this._needsRefresh;
+    },
+
+    set needsRefresh(x)
+    {
+        if (this._needsRefresh === x)
+            return;
+
+        this._needsRefresh = x;
+
+        if (x) {
+            if (this.visible && !("_refreshTimeout" in this))
+                this._refreshTimeout = setTimeout(this.refresh.bind(this), 500);
+        } else {
+            if ("_refreshTimeout" in this) {
+                clearTimeout(this._refreshTimeout);
+                delete this._refreshTimeout;
+            }
+        }
+    },
+
+    refreshIfNeeded: function()
+    {
+        if (this.needsRefresh)
+            this.refresh();
+    },
+
+    refresh: function()
+    {
+        this.needsRefresh = false;
+
+        var staleResourcesLength = this._staleResources.length;
+        var boundariesChanged = false;
+
+        for (var i = 0; i < staleResourcesLength; ++i) {
+            var resource = this._staleResources[i];
+            if (!resource._resourcesTreeElement) {
+                // Create the resource tree element and graph.
+                resource._resourcesTreeElement = new WebInspector.ResourceSidebarTreeElement(resource);
+                resource._resourcesTreeElement._resourceGraph = new WebInspector.ResourceGraph(resource);
+
+                this.resourcesTreeElement.appendChild(resource._resourcesTreeElement);
+                this.resourcesGraphsElement.appendChild(resource._resourcesTreeElement._resourceGraph.graphElement);
+            }
+
+            resource._resourcesTreeElement.refresh();
+
+            if (this.calculator.updateBoundaries(resource))
+                boundariesChanged = true;
+        }
+
+        if (boundariesChanged) {
+            // The boundaries changed, so all resource graphs are stale.
+            this._staleResources = this._resources;
+            staleResourcesLength = this._staleResources.length;
+        }
+
+        for (var i = 0; i < staleResourcesLength; ++i)
+            this._staleResources[i]._resourcesTreeElement._resourceGraph.refresh(this.calculator);
+
+        this._staleResources = [];
+
+        this._updateGraphDividersIfNeeded();
+        this._sortResourcesIfNeeded();
+        this._updateSummaryGraph();
+    },
+
+    resourceTrackingWasEnabled: function()
+    {
+        this.reset();
+    },
+
+    resourceTrackingWasDisabled: function()
+    {
+        this.reset();
+    },
+
+    reset: function()
+    {
+        this.closeVisibleResource();
+
+        this.containerElement.scrollTop = 0;
+
+        delete this.currentQuery;
+        this.searchCanceled();
+
+        if (this._calculator)
+            this._calculator.reset();
+
+        if (this._resources) {
+            var resourcesLength = this._resources.length;
+            for (var i = 0; i < resourcesLength; ++i) {
+                var resource = this._resources[i];
+
+                resource.warnings = 0;
+                resource.errors = 0;
+
+                delete resource._resourcesTreeElement;
+                delete resource._resourcesView;
+            }
+        }
+
+        this._resources = [];
+        this._staleResources = [];
+
+        this.resourcesTreeElement.removeChildren();
+        this.viewsContainerElement.removeChildren();
+        this.resourcesGraphsElement.removeChildren();
+        this.summaryBar.reset();
+
+        this._updateGraphDividersIfNeeded(true);
+
+        if (InspectorController.resourceTrackingEnabled()) {
+            this.enableToggleButton.title = WebInspector.UIString("Resource tracking enabled. Click to disable.");
+            this.enableToggleButton.toggled = true;
+            this.largerResourcesButton.visible = true;
+            this.sortingSelectElement.removeStyleClass("hidden");
+            this.panelEnablerView.visible = false;
+        } else {
+            this.enableToggleButton.title = WebInspector.UIString("Resource tracking disabled. Click to enable.");
+            this.enableToggleButton.toggled = false;
+            this.largerResourcesButton.visible = false;
+            this.sortingSelectElement.addStyleClass("hidden");
+            this.panelEnablerView.visible = true;
+        }
+    },
+
+    addResource: function(resource)
+    {
+        this._resources.push(resource);
+        this.refreshResource(resource);
+    },
+
+    removeResource: function(resource)
+    {
+        if (this.visibleView === resource._resourcesView)
+            this.closeVisibleResource();
+
+        this._resources.remove(resource, true);
+
+        if (resource._resourcesTreeElement) {
+            this.resourcesTreeElement.removeChild(resource._resourcesTreeElement);
+            this.resourcesGraphsElement.removeChild(resource._resourcesTreeElement._resourceGraph.graphElement);
+        }
+
+        resource.warnings = 0;
+        resource.errors = 0;
+
+        delete resource._resourcesTreeElement;
+        delete resource._resourcesView;
+
+        this._adjustScrollPosition();
+    },
+
+    addMessageToResource: function(resource, msg)
+    {
+        if (!resource)
+            return;
+
+        switch (msg.level) {
+        case WebInspector.ConsoleMessage.MessageLevel.Warning:
+            resource.warnings += msg.repeatDelta;
+            break;
+        case WebInspector.ConsoleMessage.MessageLevel.Error:
+            resource.errors += msg.repeatDelta;
+            break;
+        }
+
+        if (!this.currentQuery && resource._resourcesTreeElement)
+            resource._resourcesTreeElement.updateErrorsAndWarnings();
+
+        var view = this.resourceViewForResource(resource);
+        if (view.addMessage)
+            view.addMessage(msg);
+    },
+
+    clearMessages: function()
+    {
+        var resourcesLength = this._resources.length;
+        for (var i = 0; i < resourcesLength; ++i) {
+            var resource = this._resources[i];
+            resource.warnings = 0;
+            resource.errors = 0;
+
+            if (!this.currentQuery && resource._resourcesTreeElement)
+                resource._resourcesTreeElement.updateErrorsAndWarnings();
+
+            var view = resource._resourcesView;
+            if (!view || !view.clearMessages)
+                continue;
+            view.clearMessages();
+        }
+    },
+
+    refreshResource: function(resource)
+    {
+        this._staleResources.push(resource);
+        this.needsRefresh = true;
+    },
+
+    recreateViewForResourceIfNeeded: function(resource)
+    {
+        if (!resource || !resource._resourcesView)
+            return;
+
+        var newView = this._createResourceView(resource);
+        if (newView.prototype === resource._resourcesView.prototype)
+            return;
+
+        resource.warnings = 0;
+        resource.errors = 0;
+
+        if (!this.currentQuery && resource._resourcesTreeElement)
+            resource._resourcesTreeElement.updateErrorsAndWarnings();
+
+        var oldView = resource._resourcesView;
+
+        resource._resourcesView.detach();
+        delete resource._resourcesView;
+
+        resource._resourcesView = newView;
+
+        newView.headersVisible = oldView.headersVisible;
+
+        if (oldView.visible && oldView.element.parentNode)
+            newView.show(oldView.element.parentNode);
+    },
+
+    showResource: function(resource, line)
+    {
+        if (!resource)
+            return;
+
+        this.containerElement.addStyleClass("viewing-resource");
+
+        if (this.visibleResource && this.visibleResource._resourcesView)
+            this.visibleResource._resourcesView.hide();
+
+        var view = this.resourceViewForResource(resource);
+        view.headersVisible = true;
+        view.show(this.viewsContainerElement);
+
+        if (line) {
+            if (view.revealLine)
+                view.revealLine(line);
+            if (view.highlightLine)
+                view.highlightLine(line);
+        }
+
+        if (resource._resourcesTreeElement) {
+            resource._resourcesTreeElement.reveal();
+            resource._resourcesTreeElement.select(true);
+        }
+
+        this.visibleResource = resource;
+
+        this._updateSidebarWidth();
+    },
+
+    showView: function(view)
+    {
+        if (!view)
+            return;
+        this.showResource(view.resource);
+    },
+
+    closeVisibleResource: function()
+    {
+        this.containerElement.removeStyleClass("viewing-resource");
+        this._updateDividersLabelBarPosition();
+
+        if (this.visibleResource && this.visibleResource._resourcesView)
+            this.visibleResource._resourcesView.hide();
+        delete this.visibleResource;
+
+        if (this._lastSelectedGraphTreeElement)
+            this._lastSelectedGraphTreeElement.select(true);
+
+        this._updateSidebarWidth();
+    },
+
+    resourceViewForResource: function(resource)
+    {
+        if (!resource)
+            return null;
+        if (!resource._resourcesView)
+            resource._resourcesView = this._createResourceView(resource);
+        return resource._resourcesView;
+    },
+
+    sourceFrameForResource: function(resource)
+    {
+        var view = this.resourceViewForResource(resource);
+        if (!view)
+            return null;
+
+        if (!view.setupSourceFrameIfNeeded)
+            return null;
+
+        // Setting up the source frame requires that we be attached.
+        if (!this.element.parentNode)
+            this.attach();
+
+        view.setupSourceFrameIfNeeded();
+        return view.sourceFrame;
+    },
+
+    handleKeyEvent: function(event)
+    {
+        this.sidebarTree.handleKeyEvent(event);
+    },
+
+    _sortResourcesIfNeeded: function()
+    {
+        var sortedElements = [].concat(this.resourcesTreeElement.children);
+        sortedElements.sort(this.sortingFunction);
+
+        var sortedElementsLength = sortedElements.length;
+        for (var i = 0; i < sortedElementsLength; ++i) {
+            var treeElement = sortedElements[i];
+            if (treeElement === this.resourcesTreeElement.children[i])
+                continue;
+
+            var wasSelected = treeElement.selected;
+            this.resourcesTreeElement.removeChild(treeElement);
+            this.resourcesTreeElement.insertChild(treeElement, i);
+            if (wasSelected)
+                treeElement.select(true);
+
+            var graphElement = treeElement._resourceGraph.graphElement;
+            this.resourcesGraphsElement.insertBefore(graphElement, this.resourcesGraphsElement.children[i]);
+        }
+    },
+
+    _updateGraphDividersIfNeeded: function(force)
+    {
+        if (!this.visible) {
+            this.needsRefresh = true;
+            return;
+        }
+
+        if (document.body.offsetWidth <= 0) {
+            // The stylesheet hasn't loaded yet or the window is closed,
+            // so we can't calculate what is need. Return early.
+            return;
+        }
+
+        var dividerCount = Math.round(this.dividersElement.offsetWidth / 64);
+        var slice = this.calculator.boundarySpan / dividerCount;
+        if (!force && this._currentDividerSlice === slice)
+            return;
+
+        this._currentDividerSlice = slice;
+
+        this.dividersElement.removeChildren();
+        this.dividersLabelBarElement.removeChildren();
+
+        for (var i = 1; i <= dividerCount; ++i) {
+            var divider = document.createElement("div");
+            divider.className = "resources-divider";
+            if (i === dividerCount)
+                divider.addStyleClass("last");
+            divider.style.left = ((i / dividerCount) * 100) + "%";
+
+            this.dividersElement.appendChild(divider.cloneNode());
+
+            var label = document.createElement("div");
+            label.className = "resources-divider-label";
+            if (!isNaN(slice))
+                label.textContent = this.calculator.formatValue(slice * i);
+            divider.appendChild(label);
+
+            this.dividersLabelBarElement.appendChild(divider);
+        }
+    },
+
+    _updateSummaryGraph: function()
+    {
+        this.summaryBar.update(this._resources);
+    },
+
+    _updateDividersLabelBarPosition: function()
+    {
+        var scrollTop = this.containerElement.scrollTop;
+        var dividersTop = (scrollTop < this.summaryBar.element.offsetHeight ? this.summaryBar.element.offsetHeight : scrollTop);
+        this.dividersElement.style.top = scrollTop + "px";
+        this.dividersLabelBarElement.style.top = dividersTop + "px";
+    },
+
+    _graphSelected: function(treeElement)
+    {
+        if (this._lastSelectedGraphTreeElement)
+            this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = this.sortingSelectElement.selectedIndex;
+
+        this._lastSelectedGraphTreeElement = treeElement;
+
+        this.sortingSelectElement.removeChildren();
+        for (var i = 0; i < treeElement.sortingOptions.length; ++i) {
+            var sortingOption = treeElement.sortingOptions[i];
+            var option = document.createElement("option");
+            option.label = sortingOption.name;
+            option.sortingFunction = sortingOption.sortingFunction;
+            option.calculator = sortingOption.calculator;
+            this.sortingSelectElement.appendChild(option);
+        }
+
+        this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex;
+        this._changeSortingFunction();
+
+        this.closeVisibleResource();
+        this.containerElement.scrollTop = 0;
+    },
+
+    _toggleLargerResources: function()
+    {
+        if (!this.resourcesTreeElement._childrenListNode)
+            return;
+
+        this.resourcesTreeElement.smallChildren = !this.resourcesTreeElement.smallChildren;
+
+        if (this.resourcesTreeElement.smallChildren) {
+            this.resourcesGraphsElement.addStyleClass("small");
+            this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
+            this.largerResourcesButton.toggled = false;
+            this._adjustScrollPosition();
+        } else {
+            this.resourcesGraphsElement.removeStyleClass("small");
+            this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
+            this.largerResourcesButton.toggled = true;
+        }
+    },
+
+    _adjustScrollPosition: function()
+    {
+        // Prevent the container from being scrolled off the end.
+        if ((this.containerElement.scrollTop + this.containerElement.offsetHeight) > this.sidebarElement.offsetHeight)
+            this.containerElement.scrollTop = (this.sidebarElement.offsetHeight - this.containerElement.offsetHeight);
+    },
+
+    _changeSortingFunction: function()
+    {
+        var selectedOption = this.sortingSelectElement[this.sortingSelectElement.selectedIndex];
+        this.sortingFunction = selectedOption.sortingFunction;
+        this.calculator = this.summaryBar.calculator = selectedOption.calculator;
+    },
+
+    _createResourceView: function(resource)
+    {
+        switch (resource.category) {
+            case WebInspector.resourceCategories.documents:
+            case WebInspector.resourceCategories.stylesheets:
+            case WebInspector.resourceCategories.scripts:
+            case WebInspector.resourceCategories.xhr:
+                return new WebInspector.SourceView(resource);
+            case WebInspector.resourceCategories.images:
+                return new WebInspector.ImageView(resource);
+            case WebInspector.resourceCategories.fonts:
+                return new WebInspector.FontView(resource);
+            default:
+                return new WebInspector.ResourceView(resource);
+        }
+    },
+
+    _startSidebarDragging: function(event)
+    {
+        WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
+    },
+
+    _sidebarDragging: function(event)
+    {
+        this._updateSidebarWidth(event.pageX);
+
+        event.preventDefault();
+    },
+
+    _endSidebarDragging: function(event)
+    {
+        WebInspector.elementDragEnd(event);
+    },
+
+    _updateSidebarWidth: function(width)
+    {
+        if (this.sidebarElement.offsetWidth <= 0) {
+            // The stylesheet hasn't loaded yet or the window is closed,
+            // so we can't calculate what is need. Return early.
+            return;
+        }
+
+        if (!("_currentSidebarWidth" in this))
+            this._currentSidebarWidth = this.sidebarElement.offsetWidth;
+
+        if (typeof width === "undefined")
+            width = this._currentSidebarWidth;
+
+        width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
+
+        this._currentSidebarWidth = width;
+
+        if (this.visibleResource) {
+            this.containerElement.style.width = width + "px";
+            this.sidebarElement.style.removeProperty("width");
+        } else {
+            this.sidebarElement.style.width = width + "px";
+            this.containerElement.style.removeProperty("width");
+        }
+
+        this.containerContentElement.style.left = width + "px";
+        this.viewsContainerElement.style.left = width + "px";
+        this.sidebarResizeElement.style.left = (width - 3) + "px";
+
+        this._updateGraphDividersIfNeeded();
+
+        var visibleView = this.visibleView;
+        if (visibleView && "resize" in visibleView)
+            visibleView.resize();
+    },
+
+    _enableResourceTracking: function()
+    {
+        if (InspectorController.resourceTrackingEnabled())
+            return;
+        this._toggleResourceTracking(this.panelEnablerView.alwaysEnabled);
+    },
+
+    _toggleResourceTracking: function(optionalAlways)
+    {
+        if (InspectorController.resourceTrackingEnabled()) {
+            this.largerResourcesButton.visible = false;
+            this.sortingSelectElement.visible = false;
+            InspectorController.disableResourceTracking(true);
+        } else {
+            this.largerResourcesButton.visible = true;
+            this.sortingSelectElement.visible = true;
+            InspectorController.enableResourceTracking(!!optionalAlways);
+        }
+    }
+}
+
+WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
+
+WebInspector.ResourceCalculator = function()
+{
+}
+
+WebInspector.ResourceCalculator.prototype = {
+    computeSummaryValues: function(resources)
+    {
+        var total = 0;
+        var categoryValues = {};
+
+        var resourcesLength = resources.length;
+        for (var i = 0; i < resourcesLength; ++i) {
+            var resource = resources[i];
+            var value = this._value(resource);
+            if (typeof value === "undefined")
+                continue;
+            if (!(resource.category.name in categoryValues))
+                categoryValues[resource.category.name] = 0;
+            categoryValues[resource.category.name] += value;
+            total += value;
+        }
+
+        return {categoryValues: categoryValues, total: total};
+    },
+
+    computeBarGraphPercentages: function(resource)
+    {
+        return {start: 0, middle: 0, end: (this._value(resource) / this.boundarySpan) * 100};
+    },
+
+    computeBarGraphLabels: function(resource)
+    {
+        const label = this.formatValue(this._value(resource));
+        var tooltip = label;
+        if (resource.cached)
+            tooltip = WebInspector.UIString("%s (from cache)", tooltip);
+        return {left: label, right: label, tooltip: tooltip};
+    },
+
+    get boundarySpan()
+    {
+        return this.maximumBoundary - this.minimumBoundary;
+    },
+
+    updateBoundaries: function(resource)
+    {
+        this.minimumBoundary = 0;
+
+        var value = this._value(resource);
+        if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
+            this.maximumBoundary = value;
+            return true;
+        }
+
+        return false;
+    },
+
+    reset: function()
+    {
+        delete this.minimumBoundary;
+        delete this.maximumBoundary;
+    },
+
+    _value: function(resource)
+    {
+        return 0;
+    },
+
+    formatValue: function(value)
+    {
+        return value.toString();
+    }
+}
+
+WebInspector.ResourceTimeCalculator = function(startAtZero)
+{
+    WebInspector.ResourceCalculator.call(this);
+    this.startAtZero = startAtZero;
+}
+
+WebInspector.ResourceTimeCalculator.prototype = {
+    computeSummaryValues: function(resources)
+    {
+        var resourcesByCategory = {};
+        var resourcesLength = resources.length;
+        for (var i = 0; i < resourcesLength; ++i) {
+            var resource = resources[i];
+            if (!(resource.category.name in resourcesByCategory))
+                resourcesByCategory[resource.category.name] = [];
+            resourcesByCategory[resource.category.name].push(resource);
+        }
+
+        var earliestStart;
+        var latestEnd;
+        var categoryValues = {};
+        for (var category in resourcesByCategory) {
+            resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
+            categoryValues[category] = 0;
+
+            var segment = {start: -1, end: -1};
+
+            var categoryResources = resourcesByCategory[category];
+            var resourcesLength = categoryResources.length;
+            for (var i = 0; i < resourcesLength; ++i) {
+                var resource = categoryResources[i];
+                if (resource.startTime === -1 || resource.endTime === -1)
+                    continue;
+
+                if (typeof earliestStart === "undefined")
+                    earliestStart = resource.startTime;
+                else
+                    earliestStart = Math.min(earliestStart, resource.startTime);
+
+                if (typeof latestEnd === "undefined")
+                    latestEnd = resource.endTime;
+                else
+                    latestEnd = Math.max(latestEnd, resource.endTime);
+
+                if (resource.startTime <= segment.end) {
+                    segment.end = Math.max(segment.end, resource.endTime);
+                    continue;
+                }
+
+                categoryValues[category] += segment.end - segment.start;
+
+                segment.start = resource.startTime;
+                segment.end = resource.endTime;
+            }
+
+            // Add the last segment
+            categoryValues[category] += segment.end - segment.start;
+        }
+
+        return {categoryValues: categoryValues, total: latestEnd - earliestStart};
+    },
+
+    computeBarGraphPercentages: function(resource)
+    {
+        if (resource.startTime !== -1)
+            var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
+        else
+            var start = 0;
+
+        if (resource.responseReceivedTime !== -1)
+            var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
+        else
+            var middle = (this.startAtZero ? start : 100);
+
+        if (resource.endTime !== -1)
+            var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
+        else
+            var end = (this.startAtZero ? middle : 100);
+
+        if (this.startAtZero) {
+            end -= start;
+            middle -= start;
+            start = 0;
+        }
+
+        return {start: start, middle: middle, end: end};
+    },
+
+    computeBarGraphLabels: function(resource)
+    {
+        var leftLabel = "";
+        if (resource.latency > 0)
+            leftLabel = this.formatValue(resource.latency);
+
+        var rightLabel = "";
+        if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
+            rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
+
+        if (leftLabel && rightLabel) {
+            var total = this.formatValue(resource.duration);
+            var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
+        } else if (leftLabel)
+            var tooltip = WebInspector.UIString("%s latency", leftLabel);
+        else if (rightLabel)
+            var tooltip = WebInspector.UIString("%s download", rightLabel);
+
+        if (resource.cached)
+            tooltip = WebInspector.UIString("%s (from cache)", tooltip);
+
+        return {left: leftLabel, right: rightLabel, tooltip: tooltip};
+    },
+
+    updateBoundaries: function(resource)
+    {
+        var didChange = false;
+
+        var lowerBound;
+        if (this.startAtZero)
+            lowerBound = 0;
+        else
+            lowerBound = this._lowerBound(resource);
+
+        if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
+            this.minimumBoundary = lowerBound;
+            didChange = true;
+        }
+
+        var upperBound = this._upperBound(resource);
+        if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
+            this.maximumBoundary = upperBound;
+            didChange = true;
+        }
+
+        return didChange;
+    },
+
+    formatValue: function(value)
+    {
+        return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
+    },
+
+    _lowerBound: function(resource)
+    {
+        return 0;
+    },
+
+    _upperBound: function(resource)
+    {
+        return 0;
+    },
+}
+
+WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype;
+
+WebInspector.ResourceTransferTimeCalculator = function()
+{
+    WebInspector.ResourceTimeCalculator.call(this, false);
+}
+
+WebInspector.ResourceTransferTimeCalculator.prototype = {
+    formatValue: function(value)
+    {
+        return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
+    },
+
+    _lowerBound: function(resource)
+    {
+        return resource.startTime;
+    },
+
+    _upperBound: function(resource)
+    {
+        return resource.endTime;
+    }
+}
+
+WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
+
+WebInspector.ResourceTransferDurationCalculator = function()
+{
+    WebInspector.ResourceTimeCalculator.call(this, true);
+}
+
+WebInspector.ResourceTransferDurationCalculator.prototype = {
+    formatValue: function(value)
+    {
+        return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
+    },
+
+    _upperBound: function(resource)
+    {
+        return resource.duration;
+    }
+}
+
+WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
+
+WebInspector.ResourceTransferSizeCalculator = function()
+{
+    WebInspector.ResourceCalculator.call(this);
+}
+
+WebInspector.ResourceTransferSizeCalculator.prototype = {
+    _value: function(resource)
+    {
+        return resource.contentLength;
+    },
+
+    formatValue: function(value)
+    {
+        return Number.bytesToString(value, WebInspector.UIString.bind(WebInspector));
+    }
+}
+
+WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype;
+
+WebInspector.ResourceSidebarTreeElement = function(resource)
+{
+    this.resource = resource;
+
+    this.createIconElement();
+
+    WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource);
+
+    this.refreshTitles();
+}
+
+WebInspector.ResourceSidebarTreeElement.prototype = {
+    onattach: function()
+    {
+        WebInspector.SidebarTreeElement.prototype.onattach.call(this);
+
+        var link = document.createElement("a");
+        link.href = this.resource.url;
+        link.className = "invisible";
+        while (this._listItemNode.firstChild)
+            link.appendChild(this._listItemNode.firstChild);
+        this._listItemNode.appendChild(link);
+        this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
+    },
+
+    onselect: function()
+    {
+        WebInspector.panels.resources.showResource(this.resource);
+    },
+    
+    ondblclick: function(treeElement, event)
+    {
+        InjectedScriptAccess.openInInspectedWindow(this.resource.url);
+    },
+
+    get mainTitle()
+    {
+        return this.resource.displayName;
+    },
+
+    set mainTitle(x)
+    {
+        // Do nothing.
+    },
+
+    get subtitle()
+    {
+        var subtitle = this.resource.displayDomain;
+
+        if (this.resource.path && this.resource.lastPathComponent) {
+            var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent);
+            if (lastPathComponentIndex != -1)
+                subtitle += this.resource.path.substring(0, lastPathComponentIndex);
+        }
+
+        return subtitle;
+    },
+
+    set subtitle(x)
+    {
+        // Do nothing.
+    },
+
+    get selectable()
+    {
+        return WebInspector.panels.resources._filterCategory == "all" || WebInspector.panels.resources._filterCategory == this.resource.category.name;
+    },
+
+    createIconElement: function()
+    {
+        var previousIconElement = this.iconElement;
+
+        if (this.resource.category === WebInspector.resourceCategories.images) {
+            var previewImage = document.createElement("img");
+            previewImage.className = "image-resource-icon-preview";
+            previewImage.src = this.resource.url;
+
+            this.iconElement = document.createElement("div");
+            this.iconElement.className = "icon";
+            this.iconElement.appendChild(previewImage);
+        } else {
+            this.iconElement = document.createElement("img");
+            this.iconElement.className = "icon";
+        }
+
+        if (previousIconElement)
+            previousIconElement.parentNode.replaceChild(this.iconElement, previousIconElement);
+    },
+
+    refresh: function()
+    {
+        this.refreshTitles();
+
+        if (!this._listItemNode.hasStyleClass("resources-category-" + this.resource.category.name)) {
+            this._listItemNode.removeMatchingStyleClasses("resources-category-\\w+");
+            this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
+
+            this.createIconElement();
+        }
+    },
+
+    resetBubble: function()
+    {
+        this.bubbleText = "";
+        this.bubbleElement.removeStyleClass("search-matches");
+        this.bubbleElement.removeStyleClass("warning");
+        this.bubbleElement.removeStyleClass("error");
+    },
+
+    set searchMatches(matches)
+    {
+        this.resetBubble();
+
+        if (!matches)
+            return;
+
+        this.bubbleText = matches;
+        this.bubbleElement.addStyleClass("search-matches");
+    },
+
+    updateErrorsAndWarnings: function()
+    {
+        this.resetBubble();
+
+        if (this.resource.warnings || this.resource.errors)
+            this.bubbleText = (this.resource.warnings + this.resource.errors);
+
+        if (this.resource.warnings)
+            this.bubbleElement.addStyleClass("warning");
+
+        if (this.resource.errors)
+            this.bubbleElement.addStyleClass("error");
+    }
+}
+
+WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b)
+{
+    return WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
+        || WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
+        || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
+}
+
+WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b)
+{
+    return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource)
+        || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
+        || WebInspector.Resource.CompareByEndTime(a.resource, b.resource);
+}
+
+WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b)
+{
+    return WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
+        || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
+        || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
+}
+
+WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b)
+{
+    return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource);
+}
+
+WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b)
+{
+    return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource);
+}
+
+WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b)
+{
+    return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource);
+}
+
+WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
+
+WebInspector.ResourceGraph = function(resource)
+{
+    this.resource = resource;
+
+    this._graphElement = document.createElement("div");
+    this._graphElement.className = "resources-graph-side";
+    this._graphElement.addEventListener("mouseover", this.refreshLabelPositions.bind(this), false);
+
+    if (resource.cached)
+        this._graphElement.addStyleClass("resource-cached");
+
+    this._barAreaElement = document.createElement("div");
+    this._barAreaElement.className = "resources-graph-bar-area hidden";
+    this._graphElement.appendChild(this._barAreaElement);
+
+    this._barLeftElement = document.createElement("div");
+    this._barLeftElement.className = "resources-graph-bar waiting";
+    this._barAreaElement.appendChild(this._barLeftElement);
+
+    this._barRightElement = document.createElement("div");
+    this._barRightElement.className = "resources-graph-bar";
+    this._barAreaElement.appendChild(this._barRightElement);
+
+    this._labelLeftElement = document.createElement("div");
+    this._labelLeftElement.className = "resources-graph-label waiting";
+    this._barAreaElement.appendChild(this._labelLeftElement);
+
+    this._labelRightElement = document.createElement("div");
+    this._labelRightElement.className = "resources-graph-label";
+    this._barAreaElement.appendChild(this._labelRightElement);
+
+    this._graphElement.addStyleClass("resources-category-" + resource.category.name);
+}
+
+WebInspector.ResourceGraph.prototype = {
+    get graphElement()
+    {
+        return this._graphElement;
+    },
+
+    refreshLabelPositions: function()
+    {
+        this._labelLeftElement.style.removeProperty("left");
+        this._labelLeftElement.style.removeProperty("right");
+        this._labelLeftElement.removeStyleClass("before");
+        this._labelLeftElement.removeStyleClass("hidden");
+
+        this._labelRightElement.style.removeProperty("left");
+        this._labelRightElement.style.removeProperty("right");
+        this._labelRightElement.removeStyleClass("after");
+        this._labelRightElement.removeStyleClass("hidden");
+
+        const labelPadding = 10;
+        const rightBarWidth = (this._barRightElement.offsetWidth - labelPadding);
+        const leftBarWidth = ((this._barLeftElement.offsetWidth - this._barRightElement.offsetWidth) - labelPadding);
+
+        var labelBefore = (this._labelLeftElement.offsetWidth > leftBarWidth);
+        var labelAfter = (this._labelRightElement.offsetWidth > rightBarWidth);
+
+        if (labelBefore) {
+            if ((this._graphElement.offsetWidth * (this._percentages.start / 100)) < (this._labelLeftElement.offsetWidth + 10))
+                this._labelLeftElement.addStyleClass("hidden");
+            this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
+            this._labelLeftElement.addStyleClass("before");
+        } else {
+            this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
+            this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
+        }
+
+        if (labelAfter) {
+            if ((this._graphElement.offsetWidth * ((100 - this._percentages.end) / 100)) < (this._labelRightElement.offsetWidth + 10))
+                this._labelRightElement.addStyleClass("hidden");
+            this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
+            this._labelRightElement.addStyleClass("after");
+        } else {
+            this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
+            this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
+        }
+    },
+
+    refresh: function(calculator)
+    {
+        var percentages = calculator.computeBarGraphPercentages(this.resource);
+        var labels = calculator.computeBarGraphLabels(this.resource);
+
+        this._percentages = percentages;
+
+        this._barAreaElement.removeStyleClass("hidden");
+
+        if (!this._graphElement.hasStyleClass("resources-category-" + this.resource.category.name)) {
+            this._graphElement.removeMatchingStyleClasses("resources-category-\\w+");
+            this._graphElement.addStyleClass("resources-category-" + this.resource.category.name);
+        }
+
+        this._barLeftElement.style.setProperty("left", percentages.start + "%");
+        this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
+
+        this._barRightElement.style.setProperty("left", percentages.middle + "%");
+        this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
+
+        this._labelLeftElement.textContent = labels.left;
+        this._labelRightElement.textContent = labels.right;
+
+        var tooltip = (labels.tooltip || "");
+        this._barLeftElement.title = tooltip;
+        this._labelLeftElement.title = tooltip;
+        this._labelRightElement.title = tooltip;
+        this._barRightElement.title = tooltip;
+    }
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScopeChainSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScopeChainSidebarPane.js
new file mode 100644
index 0000000..3875324f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScopeChainSidebarPane.js
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.ScopeChainSidebarPane = function()
+{
+    WebInspector.SidebarPane.call(this, WebInspector.UIString("Scope Variables"));
+    this._expandedProperties = [];
+}
+
+WebInspector.ScopeChainSidebarPane.prototype = {
+    update: function(callFrame)
+    {
+        this.bodyElement.removeChildren();
+
+        this.sections = [];
+        this.callFrame = callFrame;
+
+        if (!callFrame) {
+            var infoElement = document.createElement("div");
+            infoElement.className = "info";
+            infoElement.textContent = WebInspector.UIString("Not Paused");
+            this.bodyElement.appendChild(infoElement);
+            return;
+        }
+
+        var foundLocalScope = false;
+        var scopeChain = callFrame.scopeChain;
+        for (var i = 0; i < scopeChain.length; ++i) {
+            var scopeObjectProxy = scopeChain[i];
+            var title = null;
+            var subtitle = scopeObjectProxy.description;
+            var emptyPlaceholder = null;
+            var extraProperties = null;
+
+            if (scopeObjectProxy.isLocal) {
+                if (scopeObjectProxy.thisObject) {
+                    extraProperties = [ new WebInspector.ObjectPropertyProxy("this", scopeObjectProxy.thisObject) ];
+                    title = WebInspector.UIString("Local");
+                } else
+                    title = WebInspector.UIString("Closure");
+                emptyPlaceholder = WebInspector.UIString("No Variables");
+                subtitle = null;
+                foundLocalScope = true;
+            } else if (i === (scopeChain.length - 1))
+                title = WebInspector.UIString("Global");
+            else if (scopeObjectProxy.isElement)
+                title = WebInspector.UIString("Event Target");
+            else if (scopeObjectProxy.isDocument)
+                title = WebInspector.UIString("Event Document");
+            else if (scopeObjectProxy.isWithBlock)
+                title = WebInspector.UIString("With Block");
+
+            if (!title || title === subtitle)
+                subtitle = null;
+
+            var section = new WebInspector.ObjectPropertiesSection(scopeObjectProxy, title, subtitle, emptyPlaceholder, true, extraProperties, WebInspector.ScopeVariableTreeElement);
+            section.editInSelectedCallFrameWhenPaused = true;
+            section.pane = this;
+
+            if (!foundLocalScope || scopeObjectProxy.isLocal)
+                section.expanded = true;
+
+            this.sections.push(section);
+            this.bodyElement.appendChild(section.element);
+        }
+    }
+}
+
+WebInspector.ScopeChainSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
+
+WebInspector.ScopeVariableTreeElement = function(property)
+{
+    WebInspector.ObjectPropertyTreeElement.call(this, property);
+}
+
+WebInspector.ScopeVariableTreeElement.prototype = {
+    onattach: function()
+    {
+        WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
+        if (this.hasChildren && this.propertyIdentifier in this.treeOutline.section.pane._expandedProperties)
+            this.expand();
+    },
+
+    onexpand: function()
+    {
+        this.treeOutline.section.pane._expandedProperties[this.propertyIdentifier] = true;
+    },
+
+    oncollapse: function()
+    {
+        delete this.treeOutline.section.pane._expandedProperties[this.propertyIdentifier];
+    },
+
+    get propertyIdentifier()
+    {
+        if ("_propertyIdentifier" in this)
+            return this._propertyIdentifier;
+        var section = this.treeOutline.section;
+        this._propertyIdentifier = section.title + ":" + (section.subtitle ? section.subtitle + ":" : "") + this.propertyPath;
+        return this._propertyIdentifier;
+    },
+
+    get propertyPath()
+    {
+        if ("_propertyPath" in this)
+            return this._propertyPath;
+
+        var current = this;
+        var result;
+
+        do {
+            if (result)
+                result = current.property.name + "." + result;
+            else
+                result = current.property.name;
+            current = current.parent;
+        } while (current && !current.root);
+
+        this._propertyPath = result;
+        return result;
+    }
+}
+
+WebInspector.ScopeVariableTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Script.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Script.js
new file mode 100644
index 0000000..e6413a9
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Script.js
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.Script = function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage)
+{
+    this.sourceID = sourceID;
+    this.sourceURL = sourceURL;
+    this.source = source;
+    this.startingLine = startingLine;
+    this.errorLine = errorLine;
+    this.errorMessage = errorMessage;
+
+    // if no URL, look for "//@ sourceURL=" decorator
+    // note that this sourceURL comment decorator is behavior that FireBug added
+    // in it's 1.1 release as noted in the release notes:
+    // http://fbug.googlecode.com/svn/branches/firebug1.1/docs/ReleaseNotes_1.1.txt
+    if (!sourceURL) {
+        // use of [ \t] rather than \s is to prevent \n from matching
+        var pattern = /^\s*\/\/[ \t]*@[ \t]*sourceURL[ \t]*=[ \t]*(\S+).*$/m;
+        var match = pattern.exec(source);
+
+        if (match)
+            this.sourceURL = WebInspector.UIString("(program): %s", match[1]);
+    }
+}
+
+WebInspector.Script.prototype = {
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptView.js
new file mode 100644
index 0000000..124190c
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptView.js
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.ScriptView = function(script)
+{
+    WebInspector.View.call(this);
+
+    this.element.addStyleClass("script-view");
+
+    this.script = script;
+
+    this._frameNeedsSetup = true;
+    this._sourceFrameSetup = false;
+
+    this.sourceFrame = new WebInspector.SourceFrame(null, this._addBreakpoint.bind(this));
+
+    this.element.appendChild(this.sourceFrame.element);
+}
+
+WebInspector.ScriptView.prototype = {
+    show: function(parentElement)
+    {
+        WebInspector.View.prototype.show.call(this, parentElement);
+        this.setupSourceFrameIfNeeded();
+    },
+
+    hide: function()
+    {
+        WebInspector.View.prototype.hide.call(this);
+        this._currentSearchResultIndex = -1;
+    },
+
+    setupSourceFrameIfNeeded: function()
+    {
+        if (!this._frameNeedsSetup)
+            return;
+
+        this.attach();
+
+        if (!InspectorController.addSourceToFrame("text/javascript", this.script.source, this.sourceFrame.element))
+            return;
+
+        delete this._frameNeedsSetup;
+
+        this.sourceFrame.addEventListener("syntax highlighting complete", this._syntaxHighlightingComplete, this);
+        this.sourceFrame.syntaxHighlightJavascript();
+    },
+
+    attach: function()
+    {
+        if (!this.element.parentNode)
+            document.getElementById("script-resource-views").appendChild(this.element);
+    },
+
+    _addBreakpoint: function(line)
+    {
+        var breakpoint = new WebInspector.Breakpoint(this.script.sourceURL, line, this.script.sourceID);
+        WebInspector.panels.scripts.addBreakpoint(breakpoint);
+    },
+
+    // The follow methods are pulled from SourceView, since they are
+    // generic and work with ScriptView just fine.
+
+    revealLine: WebInspector.SourceView.prototype.revealLine,
+    highlightLine: WebInspector.SourceView.prototype.highlightLine,
+    addMessage: WebInspector.SourceView.prototype.addMessage,
+    clearMessages: WebInspector.SourceView.prototype.clearMessages,
+    searchCanceled: WebInspector.SourceView.prototype.searchCanceled,
+    performSearch: WebInspector.SourceView.prototype.performSearch,
+    jumpToFirstSearchResult: WebInspector.SourceView.prototype.jumpToFirstSearchResult,
+    jumpToLastSearchResult: WebInspector.SourceView.prototype.jumpToLastSearchResult,
+    jumpToNextSearchResult: WebInspector.SourceView.prototype.jumpToNextSearchResult,
+    jumpToPreviousSearchResult: WebInspector.SourceView.prototype.jumpToPreviousSearchResult,
+    showingFirstSearchResult: WebInspector.SourceView.prototype.showingFirstSearchResult,
+    showingLastSearchResult: WebInspector.SourceView.prototype.showingLastSearchResult,
+    _jumpToSearchResult: WebInspector.SourceView.prototype._jumpToSearchResult,
+    _sourceFrameSetupFinished: WebInspector.SourceView.prototype._sourceFrameSetupFinished,
+    _syntaxHighlightingComplete: WebInspector.SourceView.prototype._syntaxHighlightingComplete
+}
+
+WebInspector.ScriptView.prototype.__proto__ = WebInspector.View.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptsPanel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptsPanel.js
new file mode 100644
index 0000000..04f27bb
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptsPanel.js
@@ -0,0 +1,918 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.ScriptsPanel = function()
+{
+    WebInspector.Panel.call(this);
+
+    this.element.addStyleClass("scripts");
+
+    this.topStatusBar = document.createElement("div");
+    this.topStatusBar.className = "status-bar";
+    this.topStatusBar.id = "scripts-status-bar";
+    this.element.appendChild(this.topStatusBar);
+
+    this.backButton = document.createElement("button");
+    this.backButton.className = "status-bar-item";
+    this.backButton.id = "scripts-back";
+    this.backButton.title = WebInspector.UIString("Show the previous script resource.");
+    this.backButton.disabled = true;
+    this.backButton.appendChild(document.createElement("img"));
+    this.backButton.addEventListener("click", this._goBack.bind(this), false);
+    this.topStatusBar.appendChild(this.backButton);
+
+    this.forwardButton = document.createElement("button");
+    this.forwardButton.className = "status-bar-item";
+    this.forwardButton.id = "scripts-forward";
+    this.forwardButton.title = WebInspector.UIString("Show the next script resource.");
+    this.forwardButton.disabled = true;
+    this.forwardButton.appendChild(document.createElement("img"));
+    this.forwardButton.addEventListener("click", this._goForward.bind(this), false);
+    this.topStatusBar.appendChild(this.forwardButton);
+
+    this.filesSelectElement = document.createElement("select");
+    this.filesSelectElement.className = "status-bar-item";
+    this.filesSelectElement.id = "scripts-files";
+    this.filesSelectElement.addEventListener("change", this._changeVisibleFile.bind(this), false);
+    this.filesSelectElement.handleKeyEvent = this.handleKeyEvent.bind(this);
+    this.topStatusBar.appendChild(this.filesSelectElement);
+
+    this.functionsSelectElement = document.createElement("select");
+    this.functionsSelectElement.className = "status-bar-item";
+    this.functionsSelectElement.id = "scripts-functions";
+
+    // FIXME: append the functions select element to the top status bar when it is implemented.
+    // this.topStatusBar.appendChild(this.functionsSelectElement);
+
+    this.sidebarButtonsElement = document.createElement("div");
+    this.sidebarButtonsElement.id = "scripts-sidebar-buttons";
+    this.topStatusBar.appendChild(this.sidebarButtonsElement);
+
+    this.pauseButton = document.createElement("button");
+    this.pauseButton.className = "status-bar-item";
+    this.pauseButton.id = "scripts-pause";
+    this.pauseButton.title = WebInspector.UIString("Pause script execution.");
+    this.pauseButton.disabled = true;
+    this.pauseButton.appendChild(document.createElement("img"));
+    this.pauseButton.addEventListener("click", this._togglePause.bind(this), false);
+    this.sidebarButtonsElement.appendChild(this.pauseButton);
+
+    this.stepOverButton = document.createElement("button");
+    this.stepOverButton.className = "status-bar-item";
+    this.stepOverButton.id = "scripts-step-over";
+    this.stepOverButton.title = WebInspector.UIString("Step over next function call.");
+    this.stepOverButton.disabled = true;
+    this.stepOverButton.addEventListener("click", this._stepOverClicked.bind(this), false);
+    this.stepOverButton.appendChild(document.createElement("img"));
+    this.sidebarButtonsElement.appendChild(this.stepOverButton);
+
+    this.stepIntoButton = document.createElement("button");
+    this.stepIntoButton.className = "status-bar-item";
+    this.stepIntoButton.id = "scripts-step-into";
+    this.stepIntoButton.title = WebInspector.UIString("Step into next function call.");
+    this.stepIntoButton.disabled = true;
+    this.stepIntoButton.addEventListener("click", this._stepIntoClicked.bind(this), false);
+    this.stepIntoButton.appendChild(document.createElement("img"));
+    this.sidebarButtonsElement.appendChild(this.stepIntoButton);
+
+    this.stepOutButton = document.createElement("button");
+    this.stepOutButton.className = "status-bar-item";
+    this.stepOutButton.id = "scripts-step-out";
+    this.stepOutButton.title = WebInspector.UIString("Step out of current function.");
+    this.stepOutButton.disabled = true;
+    this.stepOutButton.addEventListener("click", this._stepOutClicked.bind(this), false);
+    this.stepOutButton.appendChild(document.createElement("img"));
+    this.sidebarButtonsElement.appendChild(this.stepOutButton);
+
+    this.debuggerStatusElement = document.createElement("div");
+    this.debuggerStatusElement.id = "scripts-debugger-status";
+    this.sidebarButtonsElement.appendChild(this.debuggerStatusElement);
+
+    this.viewsContainerElement = document.createElement("div");
+    this.viewsContainerElement.id = "script-resource-views";
+
+    this.sidebarElement = document.createElement("div");
+    this.sidebarElement.id = "scripts-sidebar";
+
+    this.sidebarResizeElement = document.createElement("div");
+    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
+    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
+
+    this.sidebarResizeWidgetElement = document.createElement("div");
+    this.sidebarResizeWidgetElement.id = "scripts-sidebar-resizer-widget";
+    this.sidebarResizeWidgetElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
+    this.topStatusBar.appendChild(this.sidebarResizeWidgetElement);
+
+    this.sidebarPanes = {};
+    this.sidebarPanes.watchExpressions = new WebInspector.WatchExpressionsSidebarPane();
+    this.sidebarPanes.callstack = new WebInspector.CallStackSidebarPane();
+    this.sidebarPanes.scopechain = new WebInspector.ScopeChainSidebarPane();
+    this.sidebarPanes.breakpoints = new WebInspector.BreakpointsSidebarPane();
+
+    for (var pane in this.sidebarPanes)
+        this.sidebarElement.appendChild(this.sidebarPanes[pane].element);
+
+    this.sidebarPanes.callstack.expanded = true;
+    this.sidebarPanes.callstack.addEventListener("call frame selected", this._callFrameSelected, this);
+
+    this.sidebarPanes.scopechain.expanded = true;
+    this.sidebarPanes.breakpoints.expanded = true;
+
+    var panelEnablerHeading = WebInspector.UIString("You need to enable debugging before you can use the Scripts panel.");
+    var panelEnablerDisclaimer = WebInspector.UIString("Enabling debugging will make scripts run slower.");
+    var panelEnablerButton = WebInspector.UIString("Enable Debugging");
+
+    this.panelEnablerView = new WebInspector.PanelEnablerView("scripts", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
+    this.panelEnablerView.addEventListener("enable clicked", this._enableDebugging, this);
+
+    this.element.appendChild(this.panelEnablerView.element);
+    this.element.appendChild(this.viewsContainerElement);
+    this.element.appendChild(this.sidebarElement);
+    this.element.appendChild(this.sidebarResizeElement);
+
+    this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
+    this.enableToggleButton.addEventListener("click", this._toggleDebugging.bind(this), false);
+
+    this.pauseOnExceptionButton = new WebInspector.StatusBarButton("", "scripts-pause-on-exceptions-status-bar-item");
+    this.pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false);
+
+    this._breakpointsURLMap = {};
+
+    this._shortcuts = {};
+
+    var isMac = InspectorController.platform().indexOf("mac-") === 0;
+    var platformSpecificModifier = isMac ? WebInspector.KeyboardShortcut.Modifiers.Meta : WebInspector.KeyboardShortcut.Modifiers.Ctrl;
+
+    // Continue.
+    var handler = this.pauseButton.click.bind(this.pauseButton);
+    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F8);
+    this._shortcuts[shortcut] = handler;
+    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Slash, platformSpecificModifier);
+    this._shortcuts[shortcut] = handler;
+
+    // Step over.
+    var handler = this.stepOverButton.click.bind(this.stepOverButton);
+    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F10);
+    this._shortcuts[shortcut] = handler;
+    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.SingleQuote, platformSpecificModifier);
+    this._shortcuts[shortcut] = handler;
+
+    // Step into.
+    var handler = this.stepIntoButton.click.bind(this.stepIntoButton);
+    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F11);
+    this._shortcuts[shortcut] = handler;
+    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Semicolon, platformSpecificModifier);
+    this._shortcuts[shortcut] = handler;
+
+    // Step out.
+    var handler = this.stepOutButton.click.bind(this.stepOutButton);
+    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F11, WebInspector.KeyboardShortcut.Modifiers.Shift);
+    this._shortcuts[shortcut] = handler;
+    var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Semicolon, WebInspector.KeyboardShortcut.Modifiers.Shift, platformSpecificModifier);
+    this._shortcuts[shortcut] = handler;
+
+    this.reset();
+}
+
+WebInspector.ScriptsPanel.prototype = {
+    toolbarItemClass: "scripts",
+
+    get toolbarItemLabel()
+    {
+        return WebInspector.UIString("Scripts");
+    },
+
+    get statusBarItems()
+    {
+        return [this.enableToggleButton.element, this.pauseOnExceptionButton.element];
+    },
+
+    get paused()
+    {
+        return this._paused;
+    },
+
+    show: function()
+    {
+        WebInspector.Panel.prototype.show.call(this);
+        this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";
+
+        if (this.visibleView) {
+            if (this.visibleView instanceof WebInspector.ResourceView)
+                this.visibleView.headersVisible = false;
+            this.visibleView.show(this.viewsContainerElement);
+        }
+
+        // Hide any views that are visible that are not this panel's current visible view.
+        // This can happen when a ResourceView is visible in the Resources panel then switched
+        // to the this panel.
+        for (var sourceID in this._sourceIDMap) {
+            var scriptOrResource = this._sourceIDMap[sourceID];
+            var view = this._sourceViewForScriptOrResource(scriptOrResource);
+            if (!view || view === this.visibleView)
+                continue;
+            view.visible = false;
+        }
+        if (this._attachDebuggerWhenShown) {
+            InspectorController.enableDebugger(false);
+            delete this._attachDebuggerWhenShown;
+        }
+    },
+
+    get searchableViews()
+    {
+        var views = [];
+
+        const visibleView = this.visibleView;
+        if (visibleView && visibleView.performSearch) {
+            visibleView.alreadySearching = true;
+            views.push(visibleView);
+        }
+
+        for (var sourceID in this._sourceIDMap) {
+            var scriptOrResource = this._sourceIDMap[sourceID];
+            var view = this._sourceViewForScriptOrResource(scriptOrResource);
+            if (!view || !view.performSearch || view.alreadySearching)
+                continue;
+
+            view.alreadySearching = true;
+            views.push(view);
+        }
+
+        for (var i = 0; i < views.length; ++i)
+            delete views[i].alreadySearching;
+
+        return views;
+    },
+
+    addScript: function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage)
+    {
+        var script = new WebInspector.Script(sourceID, sourceURL, source, startingLine, errorLine, errorMessage);
+
+        if (sourceURL in WebInspector.resourceURLMap) {
+            var resource = WebInspector.resourceURLMap[sourceURL];
+            resource.addScript(script);
+        }
+
+        if (sourceURL in this._breakpointsURLMap && sourceID) {
+            var breakpoints = this._breakpointsURLMap[sourceURL];
+            var breakpointsLength = breakpoints.length;
+            for (var i = 0; i < breakpointsLength; ++i) {
+                var breakpoint = breakpoints[i];
+                if (startingLine <= breakpoint.line) {
+                    breakpoint.sourceID = sourceID;
+                    if (breakpoint.enabled)
+                        InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition);
+                }
+            }
+        }
+
+        if (sourceID)
+            this._sourceIDMap[sourceID] = (resource || script);
+
+        this._addScriptToFilesMenu(script);
+    },
+
+    scriptOrResourceForID: function(id)
+    {
+        return this._sourceIDMap[id];
+    },
+
+    addBreakpoint: function(breakpoint)
+    {
+        this.sidebarPanes.breakpoints.addBreakpoint(breakpoint);
+
+        var sourceFrame;
+        if (breakpoint.url) {
+            if (!(breakpoint.url in this._breakpointsURLMap))
+                this._breakpointsURLMap[breakpoint.url] = [];
+            this._breakpointsURLMap[breakpoint.url].unshift(breakpoint);
+
+            if (breakpoint.url in WebInspector.resourceURLMap) {
+                var resource = WebInspector.resourceURLMap[breakpoint.url];
+                sourceFrame = this._sourceFrameForScriptOrResource(resource);
+            }
+        }
+
+        if (breakpoint.sourceID && !sourceFrame) {
+            var object = this._sourceIDMap[breakpoint.sourceID]
+            sourceFrame = this._sourceFrameForScriptOrResource(object);
+        }
+
+        if (sourceFrame)
+            sourceFrame.addBreakpoint(breakpoint);
+    },
+
+    removeBreakpoint: function(breakpoint)
+    {
+        this.sidebarPanes.breakpoints.removeBreakpoint(breakpoint);
+
+        var sourceFrame;
+        if (breakpoint.url && breakpoint.url in this._breakpointsURLMap) {
+            var breakpoints = this._breakpointsURLMap[breakpoint.url];
+            breakpoints.remove(breakpoint);
+            if (!breakpoints.length)
+                delete this._breakpointsURLMap[breakpoint.url];
+
+            if (breakpoint.url in WebInspector.resourceURLMap) {
+                var resource = WebInspector.resourceURLMap[breakpoint.url];
+                sourceFrame = this._sourceFrameForScriptOrResource(resource);
+            }
+        }
+
+        if (breakpoint.sourceID && !sourceFrame) {
+            var object = this._sourceIDMap[breakpoint.sourceID]
+            sourceFrame = this._sourceFrameForScriptOrResource(object);
+        }
+
+        if (sourceFrame)
+            sourceFrame.removeBreakpoint(breakpoint);
+    },
+
+    evaluateInSelectedCallFrame: function(code, updateInterface, callback)
+    {
+        var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
+        if (!this._paused || !selectedCallFrame)
+            return;
+
+        if (typeof updateInterface === "undefined")
+            updateInterface = true;
+
+        var self = this;
+        function updatingCallbackWrapper(result, exception)
+        {
+            callback(result, exception);
+            if (updateInterface)
+                self.sidebarPanes.scopechain.update(selectedCallFrame);
+        }
+        this.doEvalInCallFrame(selectedCallFrame, code, updatingCallbackWrapper);
+    },
+
+    doEvalInCallFrame: function(callFrame, code, callback)
+    {
+        function evalCallback(result)
+        {
+            if (result)
+                callback(result.value, result.isException);
+        }
+        InjectedScriptAccess.evaluateInCallFrame(callFrame.id, code, evalCallback);
+    },
+
+    variablesInSelectedCallFrame: function()
+    {
+        var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
+        if (!this._paused || !selectedCallFrame)
+            return {};
+
+        var result = {};
+        var scopeChain = selectedCallFrame.scopeChain;
+        for (var i = 0; i < scopeChain.length; ++i) {
+            var scopeObjectProperties = scopeChain[i].properties;
+            for (var j = 0; j < scopeObjectProperties.length; ++j)
+                result[scopeObjectProperties[j]] = true;
+        }
+        return result;
+    },
+
+    debuggerPaused: function(callFrames)
+    {
+        this._paused = true;
+        this._waitingToPause = false;
+        this._stepping = false;
+
+        this._updateDebuggerButtons();
+
+        this.sidebarPanes.callstack.update(callFrames, this._sourceIDMap);
+        this.sidebarPanes.callstack.selectedCallFrame = callFrames[0];
+
+        WebInspector.currentPanel = this;
+        window.focus();
+    },
+
+    debuggerResumed: function()
+    {
+        this._paused = false;
+        this._waitingToPause = false;
+        this._stepping = false;
+
+        this._clearInterface();
+    },
+
+    attachDebuggerWhenShown: function()
+    {
+        if (this.element.parentElement) {
+            InspectorController.enableDebugger(false);
+        } else {
+            this._attachDebuggerWhenShown = true;
+        }
+    },
+
+    debuggerWasEnabled: function()
+    {
+        this.reset();
+    },
+
+    debuggerWasDisabled: function()
+    {
+        this.reset();
+    },
+
+    reset: function()
+    {
+        this.visibleView = null;
+
+        delete this.currentQuery;
+        this.searchCanceled();
+
+        if (!InspectorController.debuggerEnabled()) {
+            this._paused = false;
+            this._waitingToPause = false;
+            this._stepping = false;
+        }
+
+        this._clearInterface();
+
+        this._backForwardList = [];
+        this._currentBackForwardIndex = -1;
+        this._updateBackAndForwardButtons();
+
+        this._scriptsForURLsInFilesSelect = {};
+        this.filesSelectElement.removeChildren();
+        this.functionsSelectElement.removeChildren();
+        this.viewsContainerElement.removeChildren();
+
+        if (this._sourceIDMap) {
+            for (var sourceID in this._sourceIDMap) {
+                var object = this._sourceIDMap[sourceID];
+                if (object instanceof WebInspector.Resource)
+                    object.removeAllScripts();
+            }
+        }
+
+        this._sourceIDMap = {};
+        
+        this.sidebarPanes.watchExpressions.refreshExpressions();
+    },
+
+    get visibleView()
+    {
+        return this._visibleView;
+    },
+
+    set visibleView(x)
+    {
+        if (this._visibleView === x)
+            return;
+
+        if (this._visibleView)
+            this._visibleView.hide();
+
+        this._visibleView = x;
+
+        if (x)
+            x.show(this.viewsContainerElement);
+    },
+
+    canShowResource: function(resource)
+    {
+        return resource && resource.scripts.length && InspectorController.debuggerEnabled();
+    },
+
+    showScript: function(script, line)
+    {
+        this._showScriptOrResource(script, line, true);
+    },
+
+    showResource: function(resource, line)
+    {
+        this._showScriptOrResource(resource, line, true);
+    },
+
+    showView: function(view)
+    {
+        if (!view)
+            return;
+        this._showScriptOrResource((view.resource || view.script));
+    },
+
+    handleKeyEvent: function(event)
+    {
+        var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
+        var handler = this._shortcuts[shortcut];
+        if (handler) {
+            handler(event);
+            event.preventDefault();
+            event.handled = true;
+        } else {
+            this.sidebarPanes.callstack.handleKeyEvent(event);
+        }
+    },
+
+    scriptViewForScript: function(script)
+    {
+        if (!script)
+            return null;
+        if (!script._scriptView)
+            script._scriptView = new WebInspector.ScriptView(script);
+        return script._scriptView;
+    },
+
+    sourceFrameForScript: function(script)
+    {
+        var view = this.scriptViewForScript(script);
+        if (!view)
+            return null;
+
+        // Setting up the source frame requires that we be attached.
+        if (!this.element.parentNode)
+            this.attach();
+
+        view.setupSourceFrameIfNeeded();
+        return view.sourceFrame;
+    },
+
+    _sourceViewForScriptOrResource: function(scriptOrResource)
+    {
+        if (scriptOrResource instanceof WebInspector.Resource) {
+            if (!WebInspector.panels.resources)
+                return null;
+            return WebInspector.panels.resources.resourceViewForResource(scriptOrResource);
+        }
+        if (scriptOrResource instanceof WebInspector.Script)
+            return this.scriptViewForScript(scriptOrResource);
+    },
+
+    _sourceFrameForScriptOrResource: function(scriptOrResource)
+    {
+        if (scriptOrResource instanceof WebInspector.Resource) {
+            if (!WebInspector.panels.resources)
+                return null;
+            return WebInspector.panels.resources.sourceFrameForResource(scriptOrResource);
+        }
+        if (scriptOrResource instanceof WebInspector.Script)
+            return this.sourceFrameForScript(scriptOrResource);
+    },
+
+    _showScriptOrResource: function(scriptOrResource, line, shouldHighlightLine, fromBackForwardAction)
+    {
+        if (!scriptOrResource)
+            return;
+
+        var view;
+        if (scriptOrResource instanceof WebInspector.Resource) {
+            if (!WebInspector.panels.resources)
+                return null;
+            view = WebInspector.panels.resources.resourceViewForResource(scriptOrResource);
+            view.headersVisible = false;
+
+            if (scriptOrResource.url in this._breakpointsURLMap) {
+                var sourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource);
+                if (sourceFrame && !sourceFrame.breakpoints.length) {
+                    var breakpoints = this._breakpointsURLMap[scriptOrResource.url];
+                    var breakpointsLength = breakpoints.length;
+                    for (var i = 0; i < breakpointsLength; ++i)
+                        sourceFrame.addBreakpoint(breakpoints[i]);
+                }
+            }
+        } else if (scriptOrResource instanceof WebInspector.Script)
+            view = this.scriptViewForScript(scriptOrResource);
+
+        if (!view)
+            return;
+
+        if (!fromBackForwardAction) {
+            var oldIndex = this._currentBackForwardIndex;
+            if (oldIndex >= 0)
+                this._backForwardList.splice(oldIndex + 1, this._backForwardList.length - oldIndex);
+
+            // Check for a previous entry of the same object in _backForwardList.
+            // If one is found, remove it and update _currentBackForwardIndex to match.
+            var previousEntryIndex = this._backForwardList.indexOf(scriptOrResource);
+            if (previousEntryIndex !== -1) {
+                this._backForwardList.splice(previousEntryIndex, 1);
+                --this._currentBackForwardIndex;
+            }
+
+            this._backForwardList.push(scriptOrResource);
+            ++this._currentBackForwardIndex;
+
+            this._updateBackAndForwardButtons();
+        }
+
+        this.visibleView = view;
+
+        if (line) {
+            if (view.revealLine)
+                view.revealLine(line);
+            if (view.highlightLine && shouldHighlightLine)
+                view.highlightLine(line);
+        }
+
+        var option;
+        if (scriptOrResource instanceof WebInspector.Script) {
+            option = scriptOrResource.filesSelectOption;
+            console.assert(option);
+        } else {
+            var url = scriptOrResource.url;
+            var script = this._scriptsForURLsInFilesSelect[url];
+            if (script)
+               option = script.filesSelectOption;
+        }
+
+        if (option)
+            this.filesSelectElement.selectedIndex = option.index;
+    },
+
+    _addScriptToFilesMenu: function(script)
+    {
+        if (script.resource && this._scriptsForURLsInFilesSelect[script.sourceURL])
+            return;
+
+        this._scriptsForURLsInFilesSelect[script.sourceURL] = script;
+
+        var select = this.filesSelectElement;
+
+        var option = document.createElement("option");
+        option.representedObject = (script.resource || script);
+        option.text = (script.sourceURL ? WebInspector.displayNameForURL(script.sourceURL) : WebInspector.UIString("(program)"));
+
+        function optionCompare(a, b)
+        {
+            var aTitle = a.text.toLowerCase();
+            var bTitle = b.text.toLowerCase();
+            if (aTitle < bTitle)
+                return -1;
+            else if (aTitle > bTitle)
+                return 1;
+
+            var aSourceID = a.representedObject.sourceID;
+            var bSourceID = b.representedObject.sourceID;
+            if (aSourceID < bSourceID)
+                return -1;
+            else if (aSourceID > bSourceID)
+                return 1;
+            return 0;
+        }
+
+        var insertionIndex = insertionIndexForObjectInListSortedByFunction(option, select.childNodes, optionCompare);
+        if (insertionIndex < 0)
+            select.appendChild(option);
+        else
+            select.insertBefore(option, select.childNodes.item(insertionIndex));
+
+        script.filesSelectOption = option;
+
+        // Call _showScriptOrResource if the option we just appended ended up being selected.
+        // This will happen for the first item added to the menu.
+        if (select.options[select.selectedIndex] === option)
+            this._showScriptOrResource(option.representedObject);
+    },
+
+    _clearCurrentExecutionLine: function()
+    {
+        if (this._executionSourceFrame)
+            this._executionSourceFrame.executionLine = 0;
+        delete this._executionSourceFrame;
+    },
+
+    _callFrameSelected: function()
+    {
+        this._clearCurrentExecutionLine();
+
+        var callStackPane = this.sidebarPanes.callstack;
+        var currentFrame = callStackPane.selectedCallFrame;
+        if (!currentFrame)
+            return;
+
+        this.sidebarPanes.scopechain.update(currentFrame);
+        this.sidebarPanes.watchExpressions.refreshExpressions();
+
+        var scriptOrResource = this._sourceIDMap[currentFrame.sourceID];
+        this._showScriptOrResource(scriptOrResource, currentFrame.line);
+
+        this._executionSourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource);
+        if (this._executionSourceFrame)
+            this._executionSourceFrame.executionLine = currentFrame.line;
+    },
+
+    _changeVisibleFile: function(event)
+    {
+        var select = this.filesSelectElement;
+        this._showScriptOrResource(select.options[select.selectedIndex].representedObject);
+    },
+
+    _startSidebarResizeDrag: function(event)
+    {
+        WebInspector.elementDragStart(this.sidebarElement, this._sidebarResizeDrag.bind(this), this._endSidebarResizeDrag.bind(this), event, "col-resize");
+
+        if (event.target === this.sidebarResizeWidgetElement)
+            this._dragOffset = (event.target.offsetWidth - (event.pageX - event.target.totalOffsetLeft));
+        else
+            this._dragOffset = 0;
+    },
+
+    _endSidebarResizeDrag: function(event)
+    {
+        WebInspector.elementDragEnd(event);
+
+        delete this._dragOffset;
+    },
+
+    _sidebarResizeDrag: function(event)
+    {
+        var x = event.pageX + this._dragOffset;
+        var newWidth = Number.constrain(window.innerWidth - x, Preferences.minScriptsSidebarWidth, window.innerWidth * 0.66);
+
+        this.sidebarElement.style.width = newWidth + "px";
+        this.sidebarButtonsElement.style.width = newWidth + "px";
+        this.viewsContainerElement.style.right = newWidth + "px";
+        this.sidebarResizeWidgetElement.style.right = newWidth + "px";
+        this.sidebarResizeElement.style.right = (newWidth - 3) + "px";
+
+        event.preventDefault();
+    },
+
+    _updatePauseOnExceptionsButton: function()
+    {
+        if (InspectorController.pauseOnExceptions()) {
+            this.pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.");
+            this.pauseOnExceptionButton.toggled = true;
+        } else {
+            this.pauseOnExceptionButton.title = WebInspector.UIString("Pause on exceptions.");
+            this.pauseOnExceptionButton.toggled = false;
+        }
+    },
+
+    _updateDebuggerButtons: function()
+    {
+        if (InspectorController.debuggerEnabled()) {
+            this.enableToggleButton.title = WebInspector.UIString("Debugging enabled. Click to disable.");
+            this.enableToggleButton.toggled = true;
+            this.pauseOnExceptionButton.visible = true;
+            this.panelEnablerView.visible = false;
+        } else {
+            this.enableToggleButton.title = WebInspector.UIString("Debugging disabled. Click to enable.");
+            this.enableToggleButton.toggled = false;
+            this.pauseOnExceptionButton.visible = false;
+            this.panelEnablerView.visible = true;
+        }
+
+        this._updatePauseOnExceptionsButton();
+
+        if (this._paused) {
+            this.pauseButton.addStyleClass("paused");
+
+            this.pauseButton.disabled = false;
+            this.stepOverButton.disabled = false;
+            this.stepIntoButton.disabled = false;
+            this.stepOutButton.disabled = false;
+
+            this.debuggerStatusElement.textContent = WebInspector.UIString("Paused");
+        } else {
+            this.pauseButton.removeStyleClass("paused");
+
+            this.pauseButton.disabled = this._waitingToPause;
+            this.stepOverButton.disabled = true;
+            this.stepIntoButton.disabled = true;
+            this.stepOutButton.disabled = true;
+
+            if (this._waitingToPause)
+                this.debuggerStatusElement.textContent = WebInspector.UIString("Pausing");
+            else if (this._stepping)
+                this.debuggerStatusElement.textContent = WebInspector.UIString("Stepping");
+            else
+                this.debuggerStatusElement.textContent = "";
+        }
+    },
+
+    _updateBackAndForwardButtons: function()
+    {
+        this.backButton.disabled = this._currentBackForwardIndex <= 0;
+        this.forwardButton.disabled = this._currentBackForwardIndex >= (this._backForwardList.length - 1);
+    },
+
+    _clearInterface: function()
+    {
+        this.sidebarPanes.callstack.update(null);
+        this.sidebarPanes.scopechain.update(null);
+
+        this._clearCurrentExecutionLine();
+        this._updateDebuggerButtons();
+    },
+
+    _goBack: function()
+    {
+        if (this._currentBackForwardIndex <= 0) {
+            console.error("Can't go back from index " + this._currentBackForwardIndex);
+            return;
+        }
+
+        this._showScriptOrResource(this._backForwardList[--this._currentBackForwardIndex], null, false, true);
+        this._updateBackAndForwardButtons();
+    },
+
+    _goForward: function()
+    {
+        if (this._currentBackForwardIndex >= this._backForwardList.length - 1) {
+            console.error("Can't go forward from index " + this._currentBackForwardIndex);
+            return;
+        }
+
+        this._showScriptOrResource(this._backForwardList[++this._currentBackForwardIndex], null, false, true);
+        this._updateBackAndForwardButtons();
+    },
+
+    _enableDebugging: function()
+    {
+        if (InspectorController.debuggerEnabled())
+            return;
+        this._toggleDebugging(this.panelEnablerView.alwaysEnabled);
+    },
+
+    _toggleDebugging: function(optionalAlways)
+    {
+        this._paused = false;
+        this._waitingToPause = false;
+        this._stepping = false;
+
+        if (InspectorController.debuggerEnabled())
+            InspectorController.disableDebugger(true);
+        else
+            InspectorController.enableDebugger(!!optionalAlways);
+    },
+
+    _togglePauseOnExceptions: function()
+    {
+        InspectorController.setPauseOnExceptions(!InspectorController.pauseOnExceptions());
+        this._updatePauseOnExceptionsButton();
+    },
+
+    _togglePause: function()
+    {
+        if (this._paused) {
+            this._paused = false;
+            this._waitingToPause = false;
+            InspectorController.resumeDebugger();
+        } else {
+            this._stepping = false;
+            this._waitingToPause = true;
+            InspectorController.pauseInDebugger();
+        }
+
+        this._clearInterface();
+    },
+
+    _stepOverClicked: function()
+    {
+        this._paused = false;
+        this._stepping = true;
+
+        this._clearInterface();
+
+        InspectorController.stepOverStatementInDebugger();
+    },
+
+    _stepIntoClicked: function()
+    {
+        this._paused = false;
+        this._stepping = true;
+
+        this._clearInterface();
+
+        InspectorController.stepIntoStatementInDebugger();
+    },
+
+    _stepOutClicked: function()
+    {
+        this._paused = false;
+        this._stepping = true;
+
+        this._clearInterface();
+
+        InspectorController.stepOutOfFunctionInDebugger();
+    }
+}
+
+WebInspector.ScriptsPanel.prototype.__proto__ = WebInspector.Panel.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarPane.js
new file mode 100644
index 0000000..af9e5f9
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarPane.js
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.SidebarPane = function(title)
+{
+    this.element = document.createElement("div");
+    this.element.className = "pane";
+
+    this.titleElement = document.createElement("div");
+    this.titleElement.className = "title";
+    this.titleElement.addEventListener("click", this.toggleExpanded.bind(this), false);
+
+    this.bodyElement = document.createElement("div");
+    this.bodyElement.className = "body";
+
+    this.element.appendChild(this.titleElement);
+    this.element.appendChild(this.bodyElement);
+
+    this.title = title;
+    this.growbarVisible = false;
+    this.expanded = false;
+}
+
+WebInspector.SidebarPane.prototype = {
+    get title()
+    {
+        return this._title;
+    },
+
+    set title(x)
+    {
+        if (this._title === x)
+            return;
+        this._title = x;
+        this.titleElement.textContent = x;
+    },
+
+    get growbarVisible()
+    {
+        return this._growbarVisible;
+    },
+
+    set growbarVisible(x)
+    {
+        if (this._growbarVisible === x)
+            return;
+
+        this._growbarVisible = x;
+
+        if (x && !this._growbarElement) {
+            this._growbarElement = document.createElement("div");
+            this._growbarElement.className = "growbar";
+            this.element.appendChild(this._growbarElement);
+        } else if (!x && this._growbarElement) {
+            if (this._growbarElement.parentNode)
+                this._growbarElement.parentNode(this._growbarElement);
+            delete this._growbarElement;
+        }
+    },
+
+    get expanded()
+    {
+        return this._expanded;
+    },
+
+    set expanded(x)
+    {
+        if (x)
+            this.expand();
+        else
+            this.collapse();
+    },
+
+    expand: function()
+    {
+        if (this._expanded)
+            return;
+        this._expanded = true;
+        this.element.addStyleClass("expanded");
+        if (this.onexpand)
+            this.onexpand(this);
+    },
+
+    collapse: function()
+    {
+        if (!this._expanded)
+            return;
+        this._expanded = false;
+        this.element.removeStyleClass("expanded");
+        if (this.oncollapse)
+            this.oncollapse(this);
+    },
+
+    toggleExpanded: function()
+    {
+        this.expanded = !this.expanded;
+    }
+}
+
+WebInspector.SidebarPane.prototype.__proto__ = WebInspector.Object.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarTreeElement.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarTreeElement.js
new file mode 100644
index 0000000..c08b0ef
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarTreeElement.js
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.SidebarSectionTreeElement = function(title, representedObject, hasChildren)
+{
+    TreeElement.call(this, title.escapeHTML(), representedObject || {}, hasChildren);
+}
+
+WebInspector.SidebarSectionTreeElement.prototype = {
+    selectable: false,
+
+    get smallChildren()
+    {
+        return this._smallChildren;
+    },
+
+    set smallChildren(x)
+    {
+        if (this._smallChildren === x)
+            return;
+
+        this._smallChildren = x;
+
+        if (this._smallChildren)
+            this._childrenListNode.addStyleClass("small");
+        else
+            this._childrenListNode.removeStyleClass("small");
+    },
+
+    onattach: function()
+    {
+        this._listItemNode.addStyleClass("sidebar-tree-section");
+    },
+
+    onreveal: function()
+    {
+        if (this.listItemElement)
+            this.listItemElement.scrollIntoViewIfNeeded(false);
+    }
+}
+
+WebInspector.SidebarSectionTreeElement.prototype.__proto__ = TreeElement.prototype;
+
+WebInspector.SidebarTreeElement = function(className, title, subtitle, representedObject, hasChildren)
+{
+    TreeElement.call(this, "", representedObject || {}, hasChildren);
+
+    if (hasChildren) {
+        this.disclosureButton = document.createElement("button");
+        this.disclosureButton.className = "disclosure-button";
+    }
+
+    if (!this.iconElement) {
+        this.iconElement = document.createElement("img");
+        this.iconElement.className = "icon";
+    }
+
+    this.statusElement = document.createElement("div");
+    this.statusElement.className = "status";
+
+    this.titlesElement = document.createElement("div");
+    this.titlesElement.className = "titles";
+
+    this.titleElement = document.createElement("span");
+    this.titleElement.className = "title";
+    this.titlesElement.appendChild(this.titleElement);
+
+    this.subtitleElement = document.createElement("span");
+    this.subtitleElement.className = "subtitle";
+    this.titlesElement.appendChild(this.subtitleElement);
+
+    this.className = className;
+    this.mainTitle = title;
+    this.subtitle = subtitle;
+}
+
+WebInspector.SidebarTreeElement.prototype = {
+    get small()
+    {
+        return this._small;
+    },
+
+    set small(x)
+    {
+        this._small = x;
+
+        if (this._listItemNode) {
+            if (this._small)
+                this._listItemNode.addStyleClass("small");
+            else
+                this._listItemNode.removeStyleClass("small");
+        }
+    },
+
+    get mainTitle()
+    {
+        return this._mainTitle;
+    },
+
+    set mainTitle(x)
+    {
+        this._mainTitle = x;
+        this.refreshTitles();
+    },
+
+    get subtitle()
+    {
+        return this._subtitle;
+    },
+
+    set subtitle(x)
+    {
+        this._subtitle = x;
+        this.refreshTitles();
+    },
+
+    get bubbleText()
+    {
+        return this._bubbleText;
+    },
+
+    set bubbleText(x)
+    {
+        if (!this.bubbleElement) {
+            this.bubbleElement = document.createElement("div");
+            this.bubbleElement.className = "bubble";
+            this.statusElement.appendChild(this.bubbleElement);
+        }
+
+        this._bubbleText = x;
+        this.bubbleElement.textContent = x;
+    },
+
+    refreshTitles: function()
+    {
+        var mainTitle = this.mainTitle;
+        if (this.titleElement.textContent !== mainTitle)
+            this.titleElement.textContent = mainTitle;
+
+        var subtitle = this.subtitle;
+        if (subtitle) {
+            if (this.subtitleElement.textContent !== subtitle)
+                this.subtitleElement.textContent = subtitle;
+            this.titlesElement.removeStyleClass("no-subtitle");
+        } else
+            this.titlesElement.addStyleClass("no-subtitle");
+    },
+
+    isEventWithinDisclosureTriangle: function(event)
+    {
+        return event.target === this.disclosureButton;
+    },
+
+    onattach: function()
+    {
+        this._listItemNode.addStyleClass("sidebar-tree-item");
+
+        if (this.className)
+            this._listItemNode.addStyleClass(this.className);
+
+        if (this.small)
+            this._listItemNode.addStyleClass("small");
+
+        if (this.hasChildren && this.disclosureButton)
+            this._listItemNode.appendChild(this.disclosureButton);
+
+        this._listItemNode.appendChild(this.iconElement);
+        this._listItemNode.appendChild(this.statusElement);
+        this._listItemNode.appendChild(this.titlesElement);
+    },
+
+    onreveal: function()
+    {
+        if (this._listItemNode)
+            this._listItemNode.scrollIntoViewIfNeeded(false);
+    }
+}
+
+WebInspector.SidebarTreeElement.prototype.__proto__ = TreeElement.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceFrame.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceFrame.js
new file mode 100644
index 0000000..e364cb2
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceFrame.js
@@ -0,0 +1,906 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.SourceFrame = function(element, addBreakpointDelegate)
+{
+    this.messages = [];
+    this.breakpoints = [];
+    this._shortcuts = {};
+
+    this.addBreakpointDelegate = addBreakpointDelegate;
+
+    this.element = element || document.createElement("iframe");
+    this.element.addStyleClass("source-view-frame");
+    this.element.setAttribute("viewsource", "true");
+
+    this.element.addEventListener("load", this._loaded.bind(this), false);
+}
+
+WebInspector.SourceFrame.prototype = {
+    get executionLine()
+    {
+        return this._executionLine;
+    },
+
+    set executionLine(x)
+    {
+        if (this._executionLine === x)
+            return;
+
+        var previousLine = this._executionLine;
+        this._executionLine = x;
+
+        this._updateExecutionLine(previousLine);
+    },
+
+    get autoSizesToFitContentHeight()
+    {
+        return this._autoSizesToFitContentHeight;
+    },
+
+    set autoSizesToFitContentHeight(x)
+    {
+        if (this._autoSizesToFitContentHeight === x)
+            return;
+
+        this._autoSizesToFitContentHeight = x;
+
+        if (this._autoSizesToFitContentHeight) {
+            this._windowResizeListener = this._windowResized.bind(this);
+            window.addEventListener("resize", this._windowResizeListener, false);
+            this.sizeToFitContentHeight();
+        } else {
+            this.element.style.removeProperty("height");
+            if (this.element.contentDocument)
+                this.element.contentDocument.body.removeStyleClass("webkit-height-sized-to-fit");
+            window.removeEventListener("resize", this._windowResizeListener, false);
+            delete this._windowResizeListener;
+        }
+    },
+
+    sourceRow: function(lineNumber)
+    {
+        if (!lineNumber || !this.element.contentDocument)
+            return;
+
+        var table = this.element.contentDocument.getElementsByTagName("table")[0];
+        if (!table)
+            return;
+
+        var rows = table.rows;
+
+        // Line numbers are a 1-based index, but the rows collection is 0-based.
+        --lineNumber;
+
+        return rows[lineNumber];
+    },
+
+    lineNumberForSourceRow: function(sourceRow)
+    {
+        // Line numbers are a 1-based index, but the rows collection is 0-based.
+        var lineNumber = 0;
+        while (sourceRow) {
+            ++lineNumber;
+            sourceRow = sourceRow.previousSibling;
+        }
+
+        return lineNumber;
+    },
+
+    revealLine: function(lineNumber)
+    {
+        if (!this._isContentLoaded()) {
+            this._lineNumberToReveal = lineNumber;
+            return;
+        }
+
+        var row = this.sourceRow(lineNumber);
+        if (row)
+            row.scrollIntoViewIfNeeded(true);
+    },
+
+    addBreakpoint: function(breakpoint)
+    {
+        this.breakpoints.push(breakpoint);
+        breakpoint.addEventListener("enabled", this._breakpointEnableChanged, this);
+        breakpoint.addEventListener("disabled", this._breakpointEnableChanged, this);
+        this._addBreakpointToSource(breakpoint);
+    },
+
+    removeBreakpoint: function(breakpoint)
+    {
+        this.breakpoints.remove(breakpoint);
+        breakpoint.removeEventListener("enabled", null, this);
+        breakpoint.removeEventListener("disabled", null, this);
+        this._removeBreakpointFromSource(breakpoint);
+    },
+
+    addMessage: function(msg)
+    {
+        // Don't add the message if there is no message or valid line or if the msg isn't an error or warning.
+        if (!msg.message || msg.line <= 0 || !msg.isErrorOrWarning())
+            return;
+        this.messages.push(msg);
+        this._addMessageToSource(msg);
+    },
+
+    clearMessages: function()
+    {
+        this.messages = [];
+
+        if (!this.element.contentDocument)
+            return;
+
+        var bubbles = this.element.contentDocument.querySelectorAll(".webkit-html-message-bubble");
+        if (!bubbles)
+            return;
+
+        for (var i = 0; i < bubbles.length; ++i) {
+            var bubble = bubbles[i];
+            bubble.parentNode.removeChild(bubble);
+        }
+    },
+
+    sizeToFitContentHeight: function()
+    {
+        if (this.element.contentDocument) {
+            this.element.style.setProperty("height", this.element.contentDocument.body.offsetHeight + "px");
+            this.element.contentDocument.body.addStyleClass("webkit-height-sized-to-fit");
+        }
+    },
+
+    _highlightLineEnds: function(event)
+    {
+        event.target.parentNode.removeStyleClass("webkit-highlighted-line");
+    },
+
+    highlightLine: function(lineNumber)
+    {
+        if (!this._isContentLoaded()) {
+            this._lineNumberToHighlight = lineNumber;
+            return;
+        }
+
+        var sourceRow = this.sourceRow(lineNumber);
+        if (!sourceRow)
+            return;
+        var line = sourceRow.getElementsByClassName('webkit-line-content')[0];
+        // Trick to reset the animation if the user clicks on the same link
+        // Using a timeout to avoid coalesced style updates
+        line.style.setProperty("-webkit-animation-name", "none");
+        setTimeout(function () {
+            line.style.removeProperty("-webkit-animation-name");
+            sourceRow.addStyleClass("webkit-highlighted-line");
+        }, 0);
+    },
+
+    _loaded: function()
+    {
+        WebInspector.addMainEventListeners(this.element.contentDocument);
+        this.element.contentDocument.addEventListener("contextmenu", this._documentContextMenu.bind(this), true);
+        this.element.contentDocument.addEventListener("mousedown", this._documentMouseDown.bind(this), true);
+        this.element.contentDocument.addEventListener("keydown", this._documentKeyDown.bind(this), true);
+        this.element.contentDocument.addEventListener("keyup", WebInspector.documentKeyUp.bind(WebInspector), true);
+        this.element.contentDocument.addEventListener("webkitAnimationEnd", this._highlightLineEnds.bind(this), false);
+
+        // Register 'eval' shortcut.
+        var isMac = InspectorController.platform().indexOf("mac-") === 0;
+        var platformSpecificModifier = isMac ? WebInspector.KeyboardShortcut.Modifiers.Meta : WebInspector.KeyboardShortcut.Modifiers.Ctrl;
+        var shortcut = WebInspector.KeyboardShortcut.makeKey(69 /* 'E' */, platformSpecificModifier | WebInspector.KeyboardShortcut.Modifiers.Shift);
+        this._shortcuts[shortcut] = this._evalSelectionInCallFrame.bind(this);
+
+        var headElement = this.element.contentDocument.getElementsByTagName("head")[0];
+        if (!headElement) {
+            headElement = this.element.contentDocument.createElement("head");
+            this.element.contentDocument.documentElement.insertBefore(headElement, this.element.contentDocument.documentElement.firstChild);
+        }
+
+        var styleElement = this.element.contentDocument.createElement("style");
+        headElement.appendChild(styleElement);
+
+        // Add these style rules here since they are specific to the Inspector. They also behave oddly and not
+        // all properties apply if added to view-source.css (becuase it is a user agent sheet.)
+        var styleText = ".webkit-line-number { background-repeat: no-repeat; background-position: right 1px; }\n";
+        styleText += ".webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(program-counter); }\n";
+
+        styleText += ".webkit-breakpoint .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint); }\n";
+        styleText += ".webkit-breakpoint-disabled .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-disabled); }\n";
+        styleText += ".webkit-breakpoint.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-program-counter); }\n";
+        styleText += ".webkit-breakpoint-disabled.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-disabled-program-counter); }\n";
+
+        styleText += ".webkit-breakpoint.webkit-breakpoint-conditional .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-conditional); }\n";
+        styleText += ".webkit-breakpoint-disabled.webkit-breakpoint-conditional .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-disabled-conditional); }\n";
+        styleText += ".webkit-breakpoint.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-conditional-program-counter); }\n";
+        styleText += ".webkit-breakpoint-disabled.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-disabled-conditional-program-counter); }\n";
+
+        styleText += ".webkit-execution-line .webkit-line-content { background-color: rgb(171, 191, 254); outline: 1px solid rgb(64, 115, 244); }\n";
+        styleText += ".webkit-height-sized-to-fit { overflow-y: hidden }\n";
+        styleText += ".webkit-line-content { background-color: white; }\n";
+        styleText += "@-webkit-keyframes fadeout {from {background-color: rgb(255, 255, 120);} to { background-color: white;}}\n";
+        styleText += ".webkit-highlighted-line .webkit-line-content { background-color: rgb(255, 255, 120); -webkit-animation: 'fadeout' 2s 500ms}\n";
+        styleText += ".webkit-javascript-comment { color: rgb(0, 116, 0); }\n";
+        styleText += ".webkit-javascript-keyword { color: rgb(170, 13, 145); }\n";
+        styleText += ".webkit-javascript-number { color: rgb(28, 0, 207); }\n";
+        styleText += ".webkit-javascript-string, .webkit-javascript-regexp { color: rgb(196, 26, 22); }\n";
+
+        // TODO: Move these styles into inspector.css once https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed and popup moved into the top frame.
+        styleText += ".popup-content { position: absolute; z-index: 10000; padding: 4px; background-color: rgb(203, 226, 255); -webkit-border-radius: 7px; border: 2px solid rgb(169, 172, 203); }";
+        styleText += ".popup-glasspane { position: absolute; top: 0; left: 0; height: 100%; width: 100%; opacity: 0; z-index: 9900; }";
+        styleText += ".popup-message { background-color: transparent; font-family: Lucida Grande, sans-serif; font-weight: normal; font-size: 11px; text-align: left; text-shadow: none; color: rgb(85, 85, 85); cursor: default; margin: 0 0 2px 0; }";
+        styleText += ".popup-content.breakpoint-condition { width: 90%; }";
+        styleText += ".popup-content input#bp-condition { font-family: monospace; margin: 0; border: 1px inset rgb(190, 190, 190) !important; width: 100%; box-shadow: none !important; outline: none !important; -webkit-user-modify: read-write; }";
+        // This class is already in inspector.css
+        styleText += ".hidden { display: none !important; }";
+        
+        styleElement.textContent = styleText;
+
+        this._needsProgramCounterImage = true;
+        this._needsBreakpointImages = true;
+
+        this.element.contentWindow.Element.prototype.addStyleClass = Element.prototype.addStyleClass;
+        this.element.contentWindow.Element.prototype.removeStyleClass = Element.prototype.removeStyleClass;
+        this.element.contentWindow.Element.prototype.positionAt = Element.prototype.positionAt;
+        this.element.contentWindow.Element.prototype.removeMatchingStyleClasses = Element.prototype.removeMatchingStyleClasses;
+        this.element.contentWindow.Element.prototype.hasStyleClass = Element.prototype.hasStyleClass;
+        this.element.contentWindow.Element.prototype.pageOffsetRelativeToWindow = Element.prototype.pageOffsetRelativeToWindow;
+        this.element.contentWindow.Element.prototype.__defineGetter__("totalOffsetLeft", Element.prototype.__lookupGetter__("totalOffsetLeft"));
+        this.element.contentWindow.Element.prototype.__defineGetter__("totalOffsetTop", Element.prototype.__lookupGetter__("totalOffsetTop"));
+        this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeName = Node.prototype.enclosingNodeOrSelfWithNodeName;
+        this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = Node.prototype.enclosingNodeOrSelfWithNodeNameInArray;
+
+        this._addExistingMessagesToSource();
+        this._addExistingBreakpointsToSource();
+        this._updateExecutionLine();
+        if (this._executionLine)
+            this.revealLine(this._executionLine);
+
+        if (this.autoSizesToFitContentHeight)
+            this.sizeToFitContentHeight();
+
+        if (this._lineNumberToReveal) {
+            this.revealLine(this._lineNumberToReveal);
+            delete this._lineNumberToReveal;
+        }
+
+        if (this._lineNumberToHighlight) {
+            this.highlightLine(this._lineNumberToHighlight);
+            delete this._lineNumberToHighlight;
+        }
+
+        this.dispatchEventToListeners("content loaded");
+    },
+
+    _isContentLoaded: function() {
+        var doc = this.element.contentDocument;
+        return doc && doc.getElementsByTagName("table")[0];
+    },
+
+    _windowResized: function(event)
+    {
+        if (!this._autoSizesToFitContentHeight)
+            return;
+        this.sizeToFitContentHeight();
+    },
+
+    _documentContextMenu: function(event)
+    {
+        if (!event.target.hasStyleClass("webkit-line-number"))
+            return;
+        var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr");
+        if (!sourceRow._breakpointObject && this.addBreakpointDelegate)
+            this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow));
+        
+        var breakpoint = sourceRow._breakpointObject;
+        if (!breakpoint)
+            return;
+        
+        this._editBreakpointCondition(event.target, sourceRow, breakpoint);
+        event.preventDefault();
+    },
+
+    _documentMouseDown: function(event)
+    {
+        if (!event.target.hasStyleClass("webkit-line-number"))
+            return;
+        if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
+            return;
+        var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr");
+        if (sourceRow._breakpointObject && sourceRow._breakpointObject.enabled)
+            sourceRow._breakpointObject.enabled = false;
+        else if (sourceRow._breakpointObject)
+            WebInspector.panels.scripts.removeBreakpoint(sourceRow._breakpointObject);
+        else if (this.addBreakpointDelegate)
+            this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow));
+    },
+
+    _editBreakpointCondition: function(eventTarget, sourceRow, breakpoint)
+    {
+        // TODO: Migrate the popup to the top-level document and remove the blur listener from conditionElement once https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed.
+        var popupDocument = this.element.contentDocument;
+        this._showBreakpointConditionPopup(eventTarget, breakpoint.line, popupDocument);
+        
+        function committed(element, newText)
+        {
+            breakpoint.condition = newText;
+            if (breakpoint.condition)
+                sourceRow.addStyleClass("webkit-breakpoint-conditional");
+            else
+                sourceRow.removeStyleClass("webkit-breakpoint-conditional");
+            dismissed.call(this);
+        }
+
+        function dismissed()
+        {
+            this._popup.hide();
+            delete this._conditionEditorElement;
+        }
+
+        var dismissedHandler = dismissed.bind(this);
+        this._conditionEditorElement.addEventListener("blur", dismissedHandler, false);
+        
+        WebInspector.startEditing(this._conditionEditorElement, committed.bind(this), dismissedHandler);
+        this._conditionEditorElement.value = breakpoint.condition;
+        this._conditionEditorElement.select();
+    },
+
+    _showBreakpointConditionPopup: function(clickedElement, lineNumber, popupDocument)
+    {
+        var popupContentElement = this._createPopupElement(lineNumber, popupDocument);
+        var lineElement = clickedElement.enclosingNodeOrSelfWithNodeName("td").nextSibling;
+        if (this._popup) {
+            this._popup.hide();
+            this._popup.element = popupContentElement;
+        } else {
+            this._popup = new WebInspector.Popup(popupContentElement);
+            this._popup.autoHide = true;
+        }
+        this._popup.anchor = lineElement;
+        this._popup.show();
+    },
+
+    _createPopupElement: function(lineNumber, popupDocument)
+    {
+        var popupContentElement = popupDocument.createElement("div");
+        popupContentElement.className = "popup-content breakpoint-condition";
+
+        var labelElement = document.createElement("label");
+        labelElement.className = "popup-message";
+        labelElement.htmlFor = "bp-condition";
+        labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber)));
+        popupContentElement.appendChild(labelElement);
+
+        var editorElement = document.createElement("input");
+        editorElement.id = "bp-condition";
+        editorElement.type = "text"
+        popupContentElement.appendChild(editorElement);
+        this._conditionEditorElement = editorElement;
+
+        return popupContentElement;
+    },
+
+    _documentKeyDown: function(event)
+    {
+        var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
+        var handler = this._shortcuts[shortcut];
+        if (handler) {
+            handler(event);
+            event.preventDefault();
+        } else {
+            WebInspector.documentKeyDown(event);
+        }
+    },
+
+    _evalSelectionInCallFrame: function(event)
+    {
+        if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
+            return;
+
+        var selection = this.element.contentWindow.getSelection();
+        if (!selection.rangeCount)
+            return;
+
+        var expression = selection.getRangeAt(0).toString().trimWhitespace();
+        WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, function(result, exception) {
+            WebInspector.showConsole();
+            var commandMessage = new WebInspector.ConsoleCommand(expression);
+            WebInspector.console.addMessage(commandMessage);
+            WebInspector.console.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage));
+        });
+    },
+
+    _breakpointEnableChanged: function(event)
+    {
+        var breakpoint = event.target;
+        var sourceRow = this.sourceRow(breakpoint.line);
+        if (!sourceRow)
+            return;
+
+        sourceRow.addStyleClass("webkit-breakpoint");
+
+        if (breakpoint.enabled)
+            sourceRow.removeStyleClass("webkit-breakpoint-disabled");
+        else
+            sourceRow.addStyleClass("webkit-breakpoint-disabled");
+    },
+
+    _updateExecutionLine: function(previousLine)
+    {
+        if (previousLine) {
+            var sourceRow = this.sourceRow(previousLine);
+            if (sourceRow)
+                sourceRow.removeStyleClass("webkit-execution-line");
+        }
+
+        if (!this._executionLine)
+            return;
+
+        this._drawProgramCounterImageIfNeeded();
+
+        var sourceRow = this.sourceRow(this._executionLine);
+        if (sourceRow)
+            sourceRow.addStyleClass("webkit-execution-line");
+    },
+
+    _addExistingBreakpointsToSource: function()
+    {
+        var length = this.breakpoints.length;
+        for (var i = 0; i < length; ++i)
+            this._addBreakpointToSource(this.breakpoints[i]);
+    },
+
+    _addBreakpointToSource: function(breakpoint)
+    {
+        var sourceRow = this.sourceRow(breakpoint.line);
+        if (!sourceRow)
+            return;
+
+        breakpoint.sourceText = sourceRow.getElementsByClassName('webkit-line-content')[0].textContent;
+
+        this._drawBreakpointImagesIfNeeded();
+
+        sourceRow._breakpointObject = breakpoint;
+
+        sourceRow.addStyleClass("webkit-breakpoint");
+        if (!breakpoint.enabled)
+            sourceRow.addStyleClass("webkit-breakpoint-disabled");
+        if (breakpoint.condition)
+            sourceRow.addStyleClass("webkit-breakpoint-conditional");
+    },
+
+    _removeBreakpointFromSource: function(breakpoint)
+    {
+        var sourceRow = this.sourceRow(breakpoint.line);
+        if (!sourceRow)
+            return;
+
+        delete sourceRow._breakpointObject;
+
+        sourceRow.removeStyleClass("webkit-breakpoint");
+        sourceRow.removeStyleClass("webkit-breakpoint-disabled");
+        sourceRow.removeStyleClass("webkit-breakpoint-conditional");
+    },
+
+    _incrementMessageRepeatCount: function(msg, repeatDelta)
+    {
+        if (!msg._resourceMessageLineElement)
+            return;
+
+        if (!msg._resourceMessageRepeatCountElement) {
+            var repeatedElement = document.createElement("span");
+            msg._resourceMessageLineElement.appendChild(repeatedElement);
+            msg._resourceMessageRepeatCountElement = repeatedElement;
+        }
+
+        msg.repeatCount += repeatDelta;
+        msg._resourceMessageRepeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", msg.repeatCount);
+    },
+
+    _addExistingMessagesToSource: function()
+    {
+        var length = this.messages.length;
+        for (var i = 0; i < length; ++i)
+            this._addMessageToSource(this.messages[i]);
+    },
+
+    _addMessageToSource: function(msg)
+    {
+        var row = this.sourceRow(msg.line);
+        if (!row)
+            return;
+
+        var cell = row.cells[1];
+        if (!cell)
+            return;
+
+        var messageBubbleElement = cell.lastChild;
+        if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) {
+            messageBubbleElement = this.element.contentDocument.createElement("div");
+            messageBubbleElement.className = "webkit-html-message-bubble";
+            cell.appendChild(messageBubbleElement);
+        }
+
+        if (!row.messages)
+            row.messages = [];
+
+        for (var i = 0; i < row.messages.length; ++i) {
+            if (row.messages[i].isEqual(msg, true)) {
+                this._incrementMessageRepeatCount(row.messages[i], msg.repeatDelta);
+                return;
+            }
+        }
+
+        row.messages.push(msg);
+
+        var imageURL;
+        switch (msg.level) {
+            case WebInspector.ConsoleMessage.MessageLevel.Error:
+                messageBubbleElement.addStyleClass("webkit-html-error-message");
+                imageURL = "Images/errorIcon.png";
+                break;
+            case WebInspector.ConsoleMessage.MessageLevel.Warning:
+                messageBubbleElement.addStyleClass("webkit-html-warning-message");
+                imageURL = "Images/warningIcon.png";
+                break;
+        }
+
+        var messageLineElement = this.element.contentDocument.createElement("div");
+        messageLineElement.className = "webkit-html-message-line";
+        messageBubbleElement.appendChild(messageLineElement);
+
+        // Create the image element in the Inspector's document so we can use relative image URLs.
+        var image = document.createElement("img");
+        image.src = imageURL;
+        image.className = "webkit-html-message-icon";
+
+        // Adopt the image element since it wasn't created in element's contentDocument.
+        image = this.element.contentDocument.adoptNode(image);
+        messageLineElement.appendChild(image);
+        messageLineElement.appendChild(this.element.contentDocument.createTextNode(msg.message));
+
+        msg._resourceMessageLineElement = messageLineElement;
+    },
+
+    _drawProgramCounterInContext: function(ctx, glow)
+    {
+        if (glow)
+            ctx.save();
+
+        ctx.beginPath();
+        ctx.moveTo(17, 2);
+        ctx.lineTo(19, 2);
+        ctx.lineTo(19, 0);
+        ctx.lineTo(21, 0);
+        ctx.lineTo(26, 5.5);
+        ctx.lineTo(21, 11);
+        ctx.lineTo(19, 11);
+        ctx.lineTo(19, 9);
+        ctx.lineTo(17, 9);
+        ctx.closePath();
+        ctx.fillStyle = "rgb(142, 5, 4)";
+
+        if (glow) {
+            ctx.shadowBlur = 4;
+            ctx.shadowColor = "rgb(255, 255, 255)";
+            ctx.shadowOffsetX = -1;
+            ctx.shadowOffsetY = 0;
+        }
+
+        ctx.fill();
+        ctx.fill(); // Fill twice to get a good shadow and darker anti-aliased pixels.
+
+        if (glow)
+            ctx.restore();
+    },
+
+    _drawProgramCounterImageIfNeeded: function()
+    {
+        if (!this._needsProgramCounterImage || !this.element.contentDocument)
+            return;
+
+        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "program-counter", 26, 11);
+        ctx.clearRect(0, 0, 26, 11);
+        this._drawProgramCounterInContext(ctx, true);
+
+        delete this._needsProgramCounterImage;
+    },
+
+    _drawBreakpointImagesIfNeeded: function(conditional)
+    {
+        if (!this._needsBreakpointImages || !this.element.contentDocument)
+            return;
+
+        function drawBreakpoint(ctx, disabled, conditional)
+        {
+            ctx.beginPath();
+            ctx.moveTo(0, 2);
+            ctx.lineTo(2, 0);
+            ctx.lineTo(21, 0);
+            ctx.lineTo(26, 5.5);
+            ctx.lineTo(21, 11);
+            ctx.lineTo(2, 11);
+            ctx.lineTo(0, 9);
+            ctx.closePath();
+            ctx.fillStyle = conditional ? "rgb(217, 142, 1)" : "rgb(1, 142, 217)";
+            ctx.strokeStyle = conditional ? "rgb(205, 103, 0)" : "rgb(0, 103, 205)";
+            ctx.lineWidth = 3;
+            ctx.fill();
+            ctx.save();
+            ctx.clip();
+            ctx.stroke();
+            ctx.restore();
+
+            if (!disabled)
+                return;
+
+            ctx.save();
+            ctx.globalCompositeOperation = "destination-out";
+            ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
+            ctx.fillRect(0, 0, 26, 11);
+            ctx.restore();
+        }
+
+
+        // Unconditional breakpoints.
+
+        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint", 26, 11);
+        ctx.clearRect(0, 0, 26, 11);
+        drawBreakpoint(ctx);
+
+        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-program-counter", 26, 11);
+        ctx.clearRect(0, 0, 26, 11);
+        drawBreakpoint(ctx);
+        ctx.clearRect(20, 0, 6, 11);
+        this._drawProgramCounterInContext(ctx, true);
+
+        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled", 26, 11);
+        ctx.clearRect(0, 0, 26, 11);
+        drawBreakpoint(ctx, true);
+
+        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-program-counter", 26, 11);
+        ctx.clearRect(0, 0, 26, 11);
+        drawBreakpoint(ctx, true);
+        ctx.clearRect(20, 0, 6, 11);
+        this._drawProgramCounterInContext(ctx, true);
+
+
+        // Conditional breakpoints.
+
+        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-conditional", 26, 11);
+        ctx.clearRect(0, 0, 26, 11);
+        drawBreakpoint(ctx, false, true);
+
+        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-conditional-program-counter", 26, 11);
+        ctx.clearRect(0, 0, 26, 11);
+        drawBreakpoint(ctx, false, true);
+        ctx.clearRect(20, 0, 6, 11);
+        this._drawProgramCounterInContext(ctx, true);
+
+        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-conditional", 26, 11);
+        ctx.clearRect(0, 0, 26, 11);
+        drawBreakpoint(ctx, true, true);
+
+        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-conditional-program-counter", 26, 11);
+        ctx.clearRect(0, 0, 26, 11);
+        drawBreakpoint(ctx, true, true);
+        ctx.clearRect(20, 0, 6, 11);
+        this._drawProgramCounterInContext(ctx, true);
+
+        delete this._needsBreakpointImages;
+    },
+
+    syntaxHighlightJavascript: function()
+    {
+        var table = this.element.contentDocument.getElementsByTagName("table")[0];
+        if (!table)
+            return;
+
+        function deleteContinueFlags(cell)
+        {
+            if (!cell)
+                return;
+            delete cell._commentContinues;
+            delete cell._singleQuoteStringContinues;
+            delete cell._doubleQuoteStringContinues;
+            delete cell._regexpContinues;
+        }
+
+        function createSpan(content, className)
+        {
+            var span = document.createElement("span");
+            span.className = className;
+            span.appendChild(document.createTextNode(content));
+            return span;
+        }
+
+        function generateFinder(regex, matchNumber, className)
+        {
+            return function(str) {
+                var match = regex.exec(str);
+                if (!match)
+                    return null;
+                previousMatchLength = match[matchNumber].length;
+                return createSpan(match[matchNumber], className);
+            };
+        }
+
+        var findNumber = generateFinder(/^(-?(\d+\.?\d*([eE][+-]\d+)?|0[xX]\h+|Infinity)|NaN)(?:\W|$)/, 1, "webkit-javascript-number");
+        var findKeyword = generateFinder(/^(null|true|false|break|case|catch|const|default|finally|for|instanceof|new|var|continue|function|return|void|delete|if|this|do|while|else|in|switch|throw|try|typeof|with|debugger|class|enum|export|extends|import|super|get|set)(?:\W|$)/, 1, "webkit-javascript-keyword");
+        var findSingleLineString = generateFinder(/^"(?:[^"\\]|\\.)*"|^'([^'\\]|\\.)*'/, 0, "webkit-javascript-string"); // " this quote keeps Xcode happy
+        var findMultilineCommentStart = generateFinder(/^\/\*.*$/, 0, "webkit-javascript-comment");
+        var findMultilineCommentEnd = generateFinder(/^.*?\*\//, 0, "webkit-javascript-comment");
+        var findMultilineSingleQuoteStringStart = generateFinder(/^'(?:[^'\\]|\\.)*\\$/, 0, "webkit-javascript-string");
+        var findMultilineSingleQuoteStringEnd = generateFinder(/^(?:[^'\\]|\\.)*?'/, 0, "webkit-javascript-string");
+        var findMultilineDoubleQuoteStringStart = generateFinder(/^"(?:[^"\\]|\\.)*\\$/, 0, "webkit-javascript-string");
+        var findMultilineDoubleQuoteStringEnd = generateFinder(/^(?:[^"\\]|\\.)*?"/, 0, "webkit-javascript-string");
+        var findMultilineRegExpEnd = generateFinder(/^(?:[^\/\\]|\\.)*?\/([gim]{0,3})/, 0, "webkit-javascript-regexp");
+        var findSingleLineComment = generateFinder(/^\/\/.*|^\/\*.*?\*\//, 0, "webkit-javascript-comment");
+
+        function findMultilineRegExpStart(str)
+        {
+            var match = /^\/(?:[^\/\\]|\\.)*\\$/.exec(str);
+            if (!match || !/\\|\$|\.[\?\*\+]|[^\|]\|[^\|]/.test(match[0]))
+                return null;
+            var node = createSpan(match[0], "webkit-javascript-regexp");
+            previousMatchLength = match[0].length;
+            return node;
+        }
+
+        function findSingleLineRegExp(str)
+        {
+            var match = /^(\/(?:[^\/\\]|\\.)*\/([gim]{0,3}))(.?)/.exec(str);
+            if (!match || !(match[2].length > 0 || /\\|\$|\.[\?\*\+]|[^\|]\|[^\|]/.test(match[1]) || /\.|;|,/.test(match[3])))
+                return null;
+            var node = createSpan(match[1], "webkit-javascript-regexp");
+            previousMatchLength = match[1].length;
+            return node;
+        }
+
+        function syntaxHighlightJavascriptLine(line, prevLine)
+        {
+            var messageBubble = line.lastChild;
+            if (messageBubble && messageBubble.nodeType === Node.ELEMENT_NODE && messageBubble.hasStyleClass("webkit-html-message-bubble"))
+                line.removeChild(messageBubble);
+            else
+                messageBubble = null;
+
+            var code = line.textContent;
+
+            while (line.firstChild)
+                line.removeChild(line.firstChild);
+
+            var token;
+            var tmp = 0;
+            var i = 0;
+            previousMatchLength = 0;
+
+            if (prevLine) {
+                if (prevLine._commentContinues) {
+                    if (!(token = findMultilineCommentEnd(code))) {
+                        token = createSpan(code, "webkit-javascript-comment");
+                        line._commentContinues = true;
+                    }
+                } else if (prevLine._singleQuoteStringContinues) {
+                    if (!(token = findMultilineSingleQuoteStringEnd(code))) {
+                        token = createSpan(code, "webkit-javascript-string");
+                        line._singleQuoteStringContinues = true;
+                    }
+                } else if (prevLine._doubleQuoteStringContinues) {
+                    if (!(token = findMultilineDoubleQuoteStringEnd(code))) {
+                        token = createSpan(code, "webkit-javascript-string");
+                        line._doubleQuoteStringContinues = true;
+                    }
+                } else if (prevLine._regexpContinues) {
+                    if (!(token = findMultilineRegExpEnd(code))) {
+                        token = createSpan(code, "webkit-javascript-regexp");
+                        line._regexpContinues = true;
+                    }
+                }
+                if (token) {
+                    i += previousMatchLength ? previousMatchLength : code.length;
+                    tmp = i;
+                    line.appendChild(token);
+                }
+            }
+
+            for ( ; i < code.length; ++i) {
+                var codeFragment = code.substr(i);
+                var prevChar = code[i - 1];
+                token = findSingleLineComment(codeFragment);
+                if (!token) {
+                    if ((token = findMultilineCommentStart(codeFragment)))
+                        line._commentContinues = true;
+                    else if (!prevChar || /^\W/.test(prevChar)) {
+                        token = findNumber(codeFragment, code[i - 1]) ||
+                                findKeyword(codeFragment, code[i - 1]) ||
+                                findSingleLineString(codeFragment) ||
+                                findSingleLineRegExp(codeFragment);
+                        if (!token) {
+                            if (token = findMultilineSingleQuoteStringStart(codeFragment))
+                                line._singleQuoteStringContinues = true;
+                            else if (token = findMultilineDoubleQuoteStringStart(codeFragment))
+                                line._doubleQuoteStringContinues = true;
+                            else if (token = findMultilineRegExpStart(codeFragment))
+                                line._regexpContinues = true;
+                        }
+                    }
+                }
+
+                if (token) {
+                    if (tmp !== i)
+                        line.appendChild(document.createTextNode(code.substring(tmp, i)));
+                    line.appendChild(token);
+                    i += previousMatchLength - 1;
+                    tmp = i + 1;
+                }
+            }
+
+            if (tmp < code.length)
+                line.appendChild(document.createTextNode(code.substring(tmp, i)));
+
+            if (messageBubble)
+                line.appendChild(messageBubble);
+        }
+
+        var i = 0;
+        var rows = table.rows;
+        var rowsLength = rows.length;
+        var previousCell = null;
+        var previousMatchLength = 0;
+        var sourceFrame = this;
+
+        // Split up the work into chunks so we don't block the
+        // UI thread while processing.
+
+        function processChunk()
+        {
+            for (var end = Math.min(i + 10, rowsLength); i < end; ++i) {
+                var row = rows[i];
+                if (!row)
+                    continue;
+                var cell = row.cells[1];
+                if (!cell)
+                    continue;
+                syntaxHighlightJavascriptLine(cell, previousCell);
+                if (i < (end - 1))
+                    deleteContinueFlags(previousCell);
+                previousCell = cell;
+            }
+
+            if (i >= rowsLength && processChunkInterval) {
+                deleteContinueFlags(previousCell);
+                clearInterval(processChunkInterval);
+
+                sourceFrame.dispatchEventToListeners("syntax highlighting complete");
+            }
+        }
+
+        processChunk();
+
+        var processChunkInterval = setInterval(processChunk, 25);
+    }
+}
+
+WebInspector.SourceFrame.prototype.__proto__ = WebInspector.Object.prototype;
+
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceView.js
new file mode 100644
index 0000000..97a5bd5d
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceView.js
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.SourceView = function(resource)
+{
+    // Set the sourceFrame first since WebInspector.ResourceView will set headersVisible
+    // and our override of headersVisible needs the sourceFrame.
+    this.sourceFrame = new WebInspector.SourceFrame(null, this._addBreakpoint.bind(this));
+
+    WebInspector.ResourceView.call(this, resource);
+
+    resource.addEventListener("finished", this._resourceLoadingFinished, this);
+
+    this.element.addStyleClass("source");
+
+    this._frameNeedsSetup = true;
+
+    this.contentElement.appendChild(this.sourceFrame.element);
+
+    var gutterElement = document.createElement("div");
+    gutterElement.className = "webkit-line-gutter-backdrop";
+    this.element.appendChild(gutterElement);
+}
+
+WebInspector.SourceView.prototype = {
+    set headersVisible(x)
+    {
+        if (x === this._headersVisible)
+            return;
+
+        var superSetter = WebInspector.ResourceView.prototype.__lookupSetter__("headersVisible");
+        if (superSetter)
+            superSetter.call(this, x);
+
+        this.sourceFrame.autoSizesToFitContentHeight = x;
+    },
+
+    show: function(parentElement)
+    {
+        WebInspector.ResourceView.prototype.show.call(this, parentElement);
+        this.setupSourceFrameIfNeeded();
+    },
+
+    hide: function()
+    {
+        WebInspector.View.prototype.hide.call(this);
+        this._currentSearchResultIndex = -1;
+    },
+
+    resize: function()
+    {
+        if (this.sourceFrame.autoSizesToFitContentHeight)
+            this.sourceFrame.sizeToFitContentHeight();
+    },
+
+    detach: function()
+    {
+        WebInspector.ResourceView.prototype.detach.call(this);
+
+        // FIXME: We need to mark the frame for setup on detach because the frame DOM is cleared
+        // when it is removed from the document. Is this a bug?
+        this._frameNeedsSetup = true;
+        this._sourceFrameSetup = false;
+    },
+
+    setupSourceFrameIfNeeded: function()
+    {
+        if (!this._frameNeedsSetup)
+            return;
+
+        this.attach();
+
+        delete this._frameNeedsSetup;
+        this.sourceFrame.addEventListener("content loaded", this._contentLoaded, this);
+        InspectorController.addResourceSourceToFrame(this.resource.identifier, this.sourceFrame.element);
+    },
+    
+    _contentLoaded: function()
+    {
+        delete this._frameNeedsSetup;
+        this.sourceFrame.removeEventListener("content loaded", this._contentLoaded, this);
+        
+        if (this.resource.type === WebInspector.Resource.Type.Script
+            || this.resource.mimeType === 'application/json'
+            || this.resource.mimeType === 'application/javascript'
+            || /\.js(on)?$/.test(this.resource.lastPathComponent) ) {
+            this.sourceFrame.addEventListener("syntax highlighting complete", this._syntaxHighlightingComplete, this);
+            this.sourceFrame.syntaxHighlightJavascript();
+        } else
+            this._sourceFrameSetupFinished();
+    },
+
+    _resourceLoadingFinished: function(event)
+    {
+        this._frameNeedsSetup = true;
+        this._sourceFrameSetup = false;
+        if (this.visible)
+            this.setupSourceFrameIfNeeded();
+        this.resource.removeEventListener("finished", this._resourceLoadingFinished, this);
+    },
+
+    _addBreakpoint: function(line)
+    {
+        var sourceID = null;
+        var closestStartingLine = 0;
+        var scripts = this.resource.scripts;
+        for (var i = 0; i < scripts.length; ++i) {
+            var script = scripts[i];
+            if (script.startingLine <= line && script.startingLine >= closestStartingLine) {
+                closestStartingLine = script.startingLine;
+                sourceID = script.sourceID;
+            }
+        }
+
+        if (WebInspector.panels.scripts) {
+            var breakpoint = new WebInspector.Breakpoint(this.resource.url, line, sourceID);
+            WebInspector.panels.scripts.addBreakpoint(breakpoint);
+        }
+    },
+
+    // The rest of the methods in this prototype need to be generic enough to work with a ScriptView.
+    // The ScriptView prototype pulls these methods into it's prototype to avoid duplicate code.
+
+    searchCanceled: function()
+    {
+        this._currentSearchResultIndex = -1;
+        this._searchResults = [];
+        delete this._delayedFindSearchMatches;
+    },
+
+    performSearch: function(query, finishedCallback)
+    {
+        // Call searchCanceled since it will reset everything we need before doing a new search.
+        this.searchCanceled();
+
+        var lineQueryRegex = /(^|\s)(?:#|line:\s*)(\d+)(\s|$)/i;
+        var lineQueryMatch = query.match(lineQueryRegex);
+        if (lineQueryMatch) {
+            var lineToSearch = parseInt(lineQueryMatch[2]);
+
+            // If there was a space before and after the line query part, replace with a space.
+            // Otherwise replace with an empty string to eat the prefix or postfix space.
+            var lineQueryReplacement = (lineQueryMatch[1] && lineQueryMatch[3] ? " " : "");
+            var filterlessQuery = query.replace(lineQueryRegex, lineQueryReplacement);
+        }
+
+        this._searchFinishedCallback = finishedCallback;
+
+        function findSearchMatches(query, finishedCallback)
+        {
+            if (isNaN(lineToSearch)) {
+                // Search the whole document since there was no line to search.
+                this._searchResults = (InspectorController.search(this.sourceFrame.element.contentDocument, query) || []);
+            } else {
+                var sourceRow = this.sourceFrame.sourceRow(lineToSearch);
+                if (sourceRow) {
+                    if (filterlessQuery) {
+                        // There is still a query string, so search for that string in the line.
+                        this._searchResults = (InspectorController.search(sourceRow, filterlessQuery) || []);
+                    } else {
+                        // Match the whole line, since there was no remaining query string to match.
+                        var rowRange = this.sourceFrame.element.contentDocument.createRange();
+                        rowRange.selectNodeContents(sourceRow);
+                        this._searchResults = [rowRange];
+                    }
+                }
+
+                // Attempt to search for the whole query, just incase it matches a color like "#333".
+                var wholeQueryMatches = InspectorController.search(this.sourceFrame.element.contentDocument, query);
+                if (wholeQueryMatches)
+                    this._searchResults = this._searchResults.concat(wholeQueryMatches);
+            }
+
+            if (this._searchResults)
+                finishedCallback(this, this._searchResults.length);
+        }
+
+        if (!this._sourceFrameSetup) {
+            // The search is performed in _sourceFrameSetupFinished by calling _delayedFindSearchMatches.
+            this._delayedFindSearchMatches = findSearchMatches.bind(this, query, finishedCallback);
+            this.setupSourceFrameIfNeeded();
+            return;
+        }
+
+        findSearchMatches.call(this, query, finishedCallback);
+    },
+
+    jumpToFirstSearchResult: function()
+    {
+        if (!this._searchResults || !this._searchResults.length)
+            return;
+        this._currentSearchResultIndex = 0;
+        this._jumpToSearchResult(this._currentSearchResultIndex);
+    },
+
+    jumpToLastSearchResult: function()
+    {
+        if (!this._searchResults || !this._searchResults.length)
+            return;
+        this._currentSearchResultIndex = (this._searchResults.length - 1);
+        this._jumpToSearchResult(this._currentSearchResultIndex);
+    },
+
+    jumpToNextSearchResult: function()
+    {
+        if (!this._searchResults || !this._searchResults.length)
+            return;
+        if (++this._currentSearchResultIndex >= this._searchResults.length)
+            this._currentSearchResultIndex = 0;
+        this._jumpToSearchResult(this._currentSearchResultIndex);
+    },
+
+    jumpToPreviousSearchResult: function()
+    {
+        if (!this._searchResults || !this._searchResults.length)
+            return;
+        if (--this._currentSearchResultIndex < 0)
+            this._currentSearchResultIndex = (this._searchResults.length - 1);
+        this._jumpToSearchResult(this._currentSearchResultIndex);
+    },
+
+    showingFirstSearchResult: function()
+    {
+        return (this._currentSearchResultIndex === 0);
+    },
+
+    showingLastSearchResult: function()
+    {
+        return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
+    },
+
+    revealLine: function(lineNumber)
+    {
+        this.setupSourceFrameIfNeeded();
+        this.sourceFrame.revealLine(lineNumber);
+    },
+
+    highlightLine: function(lineNumber)
+    {
+        this.setupSourceFrameIfNeeded();
+        this.sourceFrame.highlightLine(lineNumber);
+    },
+
+    addMessage: function(msg)
+    {
+        this.sourceFrame.addMessage(msg);
+    },
+
+    clearMessages: function()
+    {
+        this.sourceFrame.clearMessages();
+    },
+
+    _jumpToSearchResult: function(index)
+    {
+        var foundRange = this._searchResults[index];
+        if (!foundRange)
+            return;
+
+        var selection = this.sourceFrame.element.contentWindow.getSelection();
+        selection.removeAllRanges();
+        selection.addRange(foundRange);
+
+        if (foundRange.startContainer.scrollIntoViewIfNeeded)
+            foundRange.startContainer.scrollIntoViewIfNeeded(true);
+        else if (foundRange.startContainer.parentNode)
+            foundRange.startContainer.parentNode.scrollIntoViewIfNeeded(true);
+    },
+
+    _sourceFrameSetupFinished: function()
+    {
+        this._sourceFrameSetup = true;
+        if (this._delayedFindSearchMatches) {
+            this._delayedFindSearchMatches();
+            delete this._delayedFindSearchMatches;
+        }
+    },
+
+    _syntaxHighlightingComplete: function(event)
+    {
+        this._sourceFrameSetupFinished();
+        this.sourceFrame.removeEventListener("syntax highlighting complete", null, this);
+    }
+}
+
+WebInspector.SourceView.prototype.__proto__ = WebInspector.ResourceView.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StatusBarButton.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StatusBarButton.js
new file mode 100644
index 0000000..5c69ed5
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StatusBarButton.js
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2009 Google Inc. 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.
+ */
+
+WebInspector.StatusBarButton = function(title, className)
+{
+    this.element = document.createElement("button");
+    this.element.className = className + " status-bar-item";
+    this.element.addEventListener("click", this._clicked.bind(this), false);
+
+    this.glyph = document.createElement("div");
+    this.glyph.className = "glyph";
+    this.element.appendChild(this.glyph);
+
+    this.glyphShadow = document.createElement("div");
+    this.glyphShadow.className = "glyph shadow";
+    this.element.appendChild(this.glyphShadow);
+
+    this.title = title;
+    this.disabled = false;
+    this._toggled = false;
+    this._visible = true;
+}
+
+WebInspector.StatusBarButton.prototype = {
+    _clicked: function()
+    {
+        this.dispatchEventToListeners("click");
+    },
+
+    get disabled()
+    {
+        return this._disabled;
+    },
+
+    set disabled(x)
+    {
+        if (this._disabled === x)
+            return;
+        this._disabled = x;
+        this.element.disabled = x;
+    },
+
+    get title()
+    {
+        return this._title;
+    },
+
+    set title(x)
+    {
+        if (this._title === x)
+            return;
+        this._title = x;
+        this.element.title = x;
+    },
+
+    get toggled()
+    {
+        return this._toggled;
+    },
+
+    set toggled(x)
+    {
+        if (this._toggled === x)
+            return;
+
+        if (x)
+            this.element.addStyleClass("toggled-on");
+        else
+            this.element.removeStyleClass("toggled-on");
+        this._toggled = x;
+    },
+
+    get visible()
+    {
+        return this._visible;
+    },
+
+    set visible(x)
+    {
+        if (this._visible === x)
+            return;
+
+        if (x)
+            this.element.removeStyleClass("hidden");
+        else
+            this.element.addStyleClass("hidden");
+        this._visible = x;
+    }
+}
+
+WebInspector.StatusBarButton.prototype.__proto__ = WebInspector.Object.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StoragePanel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StoragePanel.js
new file mode 100644
index 0000000..aed0d06
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StoragePanel.js
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.StoragePanel = function(database)
+{
+    WebInspector.Panel.call(this);
+
+    this.sidebarElement = document.createElement("div");
+    this.sidebarElement.id = "storage-sidebar";
+    this.sidebarElement.className = "sidebar";
+    this.element.appendChild(this.sidebarElement);
+
+    this.sidebarResizeElement = document.createElement("div");
+    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
+    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
+    this.element.appendChild(this.sidebarResizeElement);
+
+    this.sidebarTreeElement = document.createElement("ol");
+    this.sidebarTreeElement.className = "sidebar-tree";
+    this.sidebarElement.appendChild(this.sidebarTreeElement);
+
+    this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
+
+    this.databasesListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("DATABASES"), {}, true);
+    this.sidebarTree.appendChild(this.databasesListTreeElement);
+    this.databasesListTreeElement.expand();
+
+    this.localStorageListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("LOCAL STORAGE"), {}, true);
+    this.sidebarTree.appendChild(this.localStorageListTreeElement);
+    this.localStorageListTreeElement.expand();
+
+    this.sessionStorageListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("SESSION STORAGE"), {}, true);
+    this.sidebarTree.appendChild(this.sessionStorageListTreeElement);
+    this.sessionStorageListTreeElement.expand();
+
+    this.cookieListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("COOKIES"), {}, true);
+    this.sidebarTree.appendChild(this.cookieListTreeElement);
+    this.cookieListTreeElement.expand();
+
+    this.cookieTreeElement = new WebInspector.CookieSidebarTreeElement();
+    this.cookieListTreeElement.appendChild(this.cookieTreeElement);
+
+    this.storageViews = document.createElement("div");
+    this.storageViews.id = "storage-views";
+    this.element.appendChild(this.storageViews);
+
+    this.storageViewStatusBarItemsContainer = document.createElement("div");
+    this.storageViewStatusBarItemsContainer.id = "storage-view-status-bar-items";
+
+    this.reset();
+}
+
+WebInspector.StoragePanel.prototype = {
+    toolbarItemClass: "storage",
+
+    get toolbarItemLabel()
+    {
+        return WebInspector.UIString("Storage");
+    },
+
+    get statusBarItems()
+    {
+        return [this.storageViewStatusBarItemsContainer];
+    },
+
+    show: function()
+    {
+        WebInspector.Panel.prototype.show.call(this);
+        this._updateSidebarWidth();
+        this._registerStorageEventListener();
+    },
+
+    reset: function()
+    {
+        if (this._databases) {
+            var databasesLength = this._databases.length;
+            for (var i = 0; i < databasesLength; ++i) {
+                var database = this._databases[i];
+
+                delete database._tableViews;
+                delete database._queryView;
+            }
+        }
+
+        this._databases = [];
+
+        this._unregisterStorageEventListener();
+
+        if (this._domStorage) {
+            var domStorageLength = this._domStorage.length;
+            for (var i = 0; i < domStorageLength; ++i) {
+                var domStorage = this._domStorage[i];
+
+                delete domStorage._domStorageView;
+            }
+        }
+
+        this._domStorage = [];
+
+        delete this._cookieView;
+
+        this.databasesListTreeElement.removeChildren();
+        this.localStorageListTreeElement.removeChildren();
+        this.sessionStorageListTreeElement.removeChildren();
+        this.storageViews.removeChildren();        
+
+        this.storageViewStatusBarItemsContainer.removeChildren();
+        
+        if (this.sidebarTree.selectedTreeElement)
+            this.sidebarTree.selectedTreeElement.deselect();
+    },
+
+    handleKeyEvent: function(event)
+    {
+        this.sidebarTree.handleKeyEvent(event);
+    },
+
+    addDatabase: function(database)
+    {
+        this._databases.push(database);
+
+        var databaseTreeElement = new WebInspector.DatabaseSidebarTreeElement(database);
+        database._databasesTreeElement = databaseTreeElement;
+        this.databasesListTreeElement.appendChild(databaseTreeElement);
+    },
+
+    addDOMStorage: function(domStorage)
+    {
+        this._domStorage.push(domStorage);
+        var domStorageTreeElement = new WebInspector.DOMStorageSidebarTreeElement(domStorage, (domStorage.isLocalStorage ? "local-storage" : "session-storage"));
+        domStorage._domStorageTreeElement = domStorageTreeElement;
+        if (domStorage.isLocalStorage)
+            this.localStorageListTreeElement.appendChild(domStorageTreeElement);
+        else
+            this.sessionStorageListTreeElement.appendChild(domStorageTreeElement);
+    },
+
+    selectDatabase: function(db)
+    {
+        var database;
+        for (var i = 0, len = this._databases.length; i < len; ++i) {
+            database = this._databases[i];
+            if (database.isDatabase(db)) {
+                this.showDatabase(database);
+                database._databasesTreeElement.select();
+                return;
+            }
+        }
+    },
+
+    selectDOMStorage: function(s)
+    {
+        var isLocalStorage = (s === InspectorController.inspectedWindow().localStorage);
+        for (var i = 0, len = this._domStorage.length; i < len; ++i) {
+            var storage = this._domStorage[i];
+            if ( isLocalStorage === storage.isLocalStorage ) {
+                this.showDOMStorage(storage);
+                storage._domStorageTreeElement.select();
+                return;
+            }
+        }
+    },
+
+    showDatabase: function(database, tableName)
+    {
+        if (!database)
+            return;
+
+        if (this.visibleView)
+            this.visibleView.hide();
+
+        var view;
+        if (tableName) {
+            if (!("_tableViews" in database))
+                database._tableViews = {};
+            view = database._tableViews[tableName];
+            if (!view) {
+                view = new WebInspector.DatabaseTableView(database, tableName);
+                database._tableViews[tableName] = view;
+            }
+        } else {
+            view = database._queryView;
+            if (!view) {
+                view = new WebInspector.DatabaseQueryView(database);
+                database._queryView = view;
+            }
+        }
+
+        view.show(this.storageViews);
+
+        this.visibleView = view;
+
+        this.storageViewStatusBarItemsContainer.removeChildren();
+        var statusBarItems = view.statusBarItems || [];
+        for (var i = 0; i < statusBarItems.length; ++i)
+            this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i].element);
+    },
+
+    showDOMStorage: function(domStorage)
+    {
+        if (!domStorage)
+            return;
+
+        if (this.visibleView)
+            this.visibleView.hide();
+
+        var view;
+        view = domStorage._domStorageView;
+        if (!view) {
+            view = new WebInspector.DOMStorageItemsView(domStorage);
+            domStorage._domStorageView = view;
+        }
+
+        view.show(this.storageViews);
+
+        this.visibleView = view;
+
+        this.storageViewStatusBarItemsContainer.removeChildren();
+        var statusBarItems = view.statusBarItems;
+        for (var i = 0; i < statusBarItems.length; ++i)
+            this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
+    },
+
+    showCookies: function()
+    {
+        if (this.visibleView)
+            this.visibleView.hide();
+
+        var view = this._cookieView;
+        if (!view) {
+            view = new WebInspector.CookieItemsView();
+            this._cookieView = view;
+        }
+
+        view.show(this.storageViews);
+
+        this.visibleView = view;
+
+        this.storageViewStatusBarItemsContainer.removeChildren();
+        var statusBarItems = view.statusBarItems;
+        for (var i = 0; i < statusBarItems.length; ++i)
+            this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
+    },
+
+    closeVisibleView: function()
+    {
+        if (this.visibleView)
+            this.visibleView.hide();
+        delete this.visibleView;
+    },
+
+    updateDatabaseTables: function(database)
+    {
+        if (!database || !database._databasesTreeElement)
+            return;
+
+        database._databasesTreeElement.shouldRefreshChildren = true;
+
+        if (!("_tableViews" in database))
+            return;
+
+        var tableNamesHash = {};
+        var self = this;
+        function tableNamesCallback(tableNames)
+        {
+            var tableNamesLength = tableNames.length;
+            for (var i = 0; i < tableNamesLength; ++i)
+                tableNamesHash[tableNames[i]] = true;
+
+            for (var tableName in database._tableViews) {
+                if (!(tableName in tableNamesHash)) {
+                    if (self.visibleView === database._tableViews[tableName])
+                        self.closeVisibleView();
+                    delete database._tableViews[tableName];
+                }
+            }
+        }
+        database.getTableNames(tableNamesCallback);
+    },
+
+    dataGridForResult: function(result)
+    {
+        if (!result.rows.length)
+            return null;
+
+        var columns = {};
+
+        var rows = result.rows;
+        for (var columnIdentifier in rows.item(0)) {
+            var column = {};
+            column.width = columnIdentifier.length;
+            column.title = columnIdentifier;
+
+            columns[columnIdentifier] = column;
+        }
+
+        var nodes = [];
+        var length = rows.length;
+        for (var i = 0; i < length; ++i) {
+            var data = {};
+
+            var row = rows.item(i);
+            for (var columnIdentifier in row) {
+                // FIXME: (Bug 19439) We should specially format SQL NULL here
+                // (which is represented by JavaScript null here, and turned
+                // into the string "null" by the String() function).
+                var text = String(row[columnIdentifier]);
+                data[columnIdentifier] = text;
+                if (text.length > columns[columnIdentifier].width)
+                    columns[columnIdentifier].width = text.length;
+            }
+
+            var node = new WebInspector.DataGridNode(data, false);
+            node.selectable = false;
+            nodes.push(node);
+        }
+
+        var totalColumnWidths = 0;
+        for (var columnIdentifier in columns)
+            totalColumnWidths += columns[columnIdentifier].width;
+
+        // Calculate the percentage width for the columns.
+        const minimumPrecent = 5;
+        var recoupPercent = 0;
+        for (var columnIdentifier in columns) {
+            var width = columns[columnIdentifier].width;
+            width = Math.round((width / totalColumnWidths) * 100);
+            if (width < minimumPrecent) {
+                recoupPercent += (minimumPrecent - width);
+                width = minimumPrecent;
+            }
+
+            columns[columnIdentifier].width = width;
+        }
+
+        // Enforce the minimum percentage width.
+        while (recoupPercent > 0) {
+            for (var columnIdentifier in columns) {
+                if (columns[columnIdentifier].width > minimumPrecent) {
+                    --columns[columnIdentifier].width;
+                    --recoupPercent;
+                    if (!recoupPercent)
+                        break;
+                }
+            }
+        }
+
+        // Change the width property to a string suitable for a style width.
+        for (var columnIdentifier in columns)
+            columns[columnIdentifier].width += "%";
+
+        var dataGrid = new WebInspector.DataGrid(columns);
+        var length = nodes.length;
+        for (var i = 0; i < length; ++i)
+            dataGrid.appendChild(nodes[i]);
+
+        return dataGrid;
+    },
+
+    dataGridForDOMStorage: function(domStorage)
+    {
+        if (!domStorage.length)
+            return null;
+
+        var columns = {};
+        columns[0] = {};
+        columns[1] = {};
+        columns[0].title = WebInspector.UIString("Key");
+        columns[0].width = columns[0].title.length;
+        columns[1].title = WebInspector.UIString("Value");
+        columns[1].width = columns[1].title.length;
+
+        var nodes = [];
+        
+        var length = domStorage.length;
+        for (var index = 0; index < domStorage.length; index++) {
+            var data = {};
+       
+            var key = String(domStorage.key(index));
+            data[0] = key;
+            if (key.length > columns[0].width)
+                columns[0].width = key.length;
+        
+            var value = String(domStorage.getItem(key));
+            data[1] = value;
+            if (value.length > columns[1].width)
+                columns[1].width = value.length;
+            var node = new WebInspector.DataGridNode(data, false);
+            node.selectable = true;
+            nodes.push(node);
+        }
+
+        var totalColumnWidths = columns[0].width + columns[1].width;
+        var width = Math.round((columns[0].width * 100) / totalColumnWidths);
+        const minimumPrecent = 10;
+        if (width < minimumPrecent)
+            width = minimumPrecent;
+        if (width > 100 - minimumPrecent)
+            width = 100 - minimumPrecent;
+        columns[0].width = width;
+        columns[1].width = 100 - width;
+        columns[0].width += "%";
+        columns[1].width += "%";
+
+        var dataGrid = new WebInspector.DOMStorageDataGrid(columns);
+        var length = nodes.length;
+        for (var i = 0; i < length; ++i)
+            dataGrid.appendChild(nodes[i]);
+        dataGrid.addCreationNode(false);
+        if (length > 0)
+            nodes[0].selected = true;
+        return dataGrid;
+    },
+
+    resize: function()
+    {
+        var visibleView = this.visibleView;
+        if (visibleView && "resize" in visibleView)
+            visibleView.resize();
+    },
+
+    _registerStorageEventListener: function()
+    {
+        var inspectedWindow = InspectorController.inspectedWindow();
+        if (!inspectedWindow || !inspectedWindow.document)
+            return;
+
+        this._storageEventListener = InspectorController.wrapCallback(this._storageEvent.bind(this));
+        inspectedWindow.addEventListener("storage", this._storageEventListener, true);
+    },
+
+    _unregisterStorageEventListener: function()
+    {
+        if (!this._storageEventListener)
+            return;
+
+        var inspectedWindow = InspectorController.inspectedWindow();
+        if (!inspectedWindow || !inspectedWindow.document)
+            return;
+
+        inspectedWindow.removeEventListener("storage", this._storageEventListener, true);
+        delete this._storageEventListener;
+    },
+
+    _storageEvent: function(event)
+    {
+        if (!this._domStorage)
+            return;
+
+        var isLocalStorage = (event.storageArea === InspectorController.inspectedWindow().localStorage);
+        var domStorageLength = this._domStorage.length;
+        for (var i = 0; i < domStorageLength; ++i) {
+            var domStorage = this._domStorage[i];
+            if (isLocalStorage === domStorage.isLocalStorage) {
+                var view = domStorage._domStorageView;
+                if (this.visibleView && view === this.visibleView)
+                    domStorage._domStorageView.update();
+            }
+        }
+    },
+
+    _startSidebarDragging: function(event)
+    {
+        WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
+    },
+
+    _sidebarDragging: function(event)
+    {
+        this._updateSidebarWidth(event.pageX);
+
+        event.preventDefault();
+    },
+
+    _endSidebarDragging: function(event)
+    {
+        WebInspector.elementDragEnd(event);
+    },
+
+    _updateSidebarWidth: function(width)
+    {
+        if (this.sidebarElement.offsetWidth <= 0) {
+            // The stylesheet hasn't loaded yet or the window is closed,
+            // so we can't calculate what is need. Return early.
+            return;
+        }
+
+        if (!("_currentSidebarWidth" in this))
+            this._currentSidebarWidth = this.sidebarElement.offsetWidth;
+
+        if (typeof width === "undefined")
+            width = this._currentSidebarWidth;
+
+        width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
+
+        this._currentSidebarWidth = width;
+
+        this.sidebarElement.style.width = width + "px";
+        this.storageViews.style.left = width + "px";
+        this.storageViewStatusBarItemsContainer.style.left = width + "px";
+        this.sidebarResizeElement.style.left = (width - 3) + "px";
+        
+        var visibleView = this.visibleView;
+        if (visibleView && "resize" in visibleView)
+            visibleView.resize();
+    }
+}
+
+WebInspector.StoragePanel.prototype.__proto__ = WebInspector.Panel.prototype;
+
+WebInspector.DatabaseSidebarTreeElement = function(database)
+{
+    this.database = database;
+
+    WebInspector.SidebarTreeElement.call(this, "database-sidebar-tree-item", "", "", database, true);
+
+    this.refreshTitles();
+}
+
+WebInspector.DatabaseSidebarTreeElement.prototype = {
+    onselect: function()
+    {
+        WebInspector.panels.storage.showDatabase(this.database);
+    },
+
+    oncollapse: function()
+    {
+        // Request a refresh after every collapse so the next
+        // expand will have an updated table list.
+        this.shouldRefreshChildren = true;
+    },
+
+    onpopulate: function()
+    {
+        this.removeChildren();
+
+        var self = this;
+        function tableNamesCallback(tableNames)
+        {
+            var tableNamesLength = tableNames.length;
+            for (var i = 0; i < tableNamesLength; ++i)
+                self.appendChild(new WebInspector.SidebarDatabaseTableTreeElement(self.database, tableNames[i]));
+        }
+        this.database.getTableNames(tableNamesCallback);
+    },
+
+    get mainTitle()
+    {
+        return this.database.name;
+    },
+
+    set mainTitle(x)
+    {
+        // Do nothing.
+    },
+
+    get subtitle()
+    {
+        return this.database.displayDomain;
+    },
+
+    set subtitle(x)
+    {
+        // Do nothing.
+    }
+}
+
+WebInspector.DatabaseSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
+
+WebInspector.SidebarDatabaseTableTreeElement = function(database, tableName)
+{
+    this.database = database;
+    this.tableName = tableName;
+
+    WebInspector.SidebarTreeElement.call(this, "database-table-sidebar-tree-item small", tableName, "", null, false);
+}
+
+WebInspector.SidebarDatabaseTableTreeElement.prototype = {
+    onselect: function()
+    {
+        WebInspector.panels.storage.showDatabase(this.database, this.tableName);
+    }
+}
+
+WebInspector.SidebarDatabaseTableTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
+
+WebInspector.DOMStorageSidebarTreeElement = function(domStorage, className)
+{
+
+    this.domStorage = domStorage;
+
+    WebInspector.SidebarTreeElement.call(this, "domstorage-sidebar-tree-item " + className, domStorage, "", null, false);
+
+    this.refreshTitles();
+}
+
+WebInspector.DOMStorageSidebarTreeElement.prototype = {
+    onselect: function()
+    {
+        WebInspector.panels.storage.showDOMStorage(this.domStorage);
+    },
+
+    get mainTitle()
+    {
+        return this.domStorage.domain;
+    },
+
+    set mainTitle(x)
+    {
+        // Do nothing.
+    },
+
+    get subtitle()
+    {
+        return ""; //this.database.displayDomain;
+    },
+
+    set subtitle(x)
+    {
+        // Do nothing.
+    }
+}
+
+WebInspector.DOMStorageSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
+
+WebInspector.CookieSidebarTreeElement = function()
+{
+    WebInspector.SidebarTreeElement.call(this, "cookie-sidebar-tree-item", null, "", null, false);
+
+    this.refreshTitles();
+}
+
+WebInspector.CookieSidebarTreeElement.prototype = {
+    onselect: function()
+    {
+        WebInspector.panels.storage.showCookies();
+    },
+
+    get mainTitle()
+    {
+        return WebInspector.UIString("Cookies");
+    },
+
+    set mainTitle(x)
+    {
+        // Do nothing.
+    },
+
+    get subtitle()
+    {
+        return "";
+    },
+
+    set subtitle(x)
+    {
+        // Do nothing.
+    }
+}
+
+WebInspector.CookieSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StylesSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StylesSidebarPane.js
new file mode 100644
index 0000000..6185aff
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StylesSidebarPane.js
@@ -0,0 +1,1373 @@
+/*
+ * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.StylesSidebarPane = function()
+{
+    WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles"));
+
+    this.settingsSelectElement = document.createElement("select");
+
+    var option = document.createElement("option");
+    option.value = "hex";
+    option.action = this._changeColorFormat.bind(this);
+    if (Preferences.colorFormat === "hex")
+        option.selected = true;
+    option.label = WebInspector.UIString("Hex Colors");
+    this.settingsSelectElement.appendChild(option);
+
+    option = document.createElement("option");
+    option.value = "rgb";
+    option.action = this._changeColorFormat.bind(this);
+    if (Preferences.colorFormat === "rgb")
+        option.selected = true;
+    option.label = WebInspector.UIString("RGB Colors");
+    this.settingsSelectElement.appendChild(option);
+
+    option = document.createElement("option");
+    option.value = "hsl";
+    option.action = this._changeColorFormat.bind(this);
+    if (Preferences.colorFormat === "hsl")
+        option.selected = true;
+    option.label = WebInspector.UIString("HSL Colors");
+    this.settingsSelectElement.appendChild(option);
+
+    this.settingsSelectElement.appendChild(document.createElement("hr"));
+
+    option = document.createElement("option");
+    option.action = this._createNewRule.bind(this);
+    option.label = WebInspector.UIString("New Style Rule");
+    this.settingsSelectElement.appendChild(option);
+
+    this.settingsSelectElement.addEventListener("click", function(event) { event.stopPropagation() }, false);
+    this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false);
+
+    this.titleElement.appendChild(this.settingsSelectElement);
+}
+
+WebInspector.StylesSidebarPane.prototype = {
+    update: function(node, editedSection, forceUpdate)
+    {
+        var refresh = false;
+
+        if (forceUpdate)
+            delete this.node;
+
+        if (!forceUpdate && (!node || node === this.node))
+            refresh = true;
+
+        if (node && node.nodeType === Node.TEXT_NODE && node.parentNode)
+            node = node.parentNode;
+
+        if (node && node.nodeType !== Node.ELEMENT_NODE)
+            node = null;
+
+        if (node)
+            this.node = node;
+        else
+            node = this.node;
+
+        var body = this.bodyElement;
+        if (!refresh || !node) {
+            body.removeChildren();
+            this.sections = [];
+        }
+
+        if (!node)
+            return;
+
+        var self = this;
+        function callback(styles)
+        {
+            if (!styles)
+                return;
+            node._setStyles(styles.computedStyle, styles.inlineStyle, styles.styleAttributes, styles.matchedCSSRules);
+            self._update(refresh, body, node, editedSection, forceUpdate);
+        }
+
+        InjectedScriptAccess.getStyles(node.id, !Preferences.showUserAgentStyles, callback);
+    },
+
+    _update: function(refresh, body, node, editedSection, forceUpdate)
+    {
+        if (!refresh) {
+            body.removeChildren();
+            this.sections = [];
+        }
+
+        var styleRules = [];
+
+        if (refresh) {
+            for (var i = 0; i < this.sections.length; ++i) {
+                var section = this.sections[i];
+                if (section instanceof WebInspector.BlankStylePropertiesSection)
+                    continue;
+                if (section.computedStyle)
+                    section.styleRule.style = node.ownerDocument.defaultView.getComputedStyle(node);
+                var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule };
+                styleRules.push(styleRule);
+            }
+        } else {
+            var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
+            styleRules.push({ computedStyle: true, selectorText: WebInspector.UIString("Computed Style"), style: computedStyle, editable: false });
+
+            var nodeName = node.nodeName.toLowerCase();
+            for (var i = 0; i < node.attributes.length; ++i) {
+                var attr = node.attributes[i];
+                if (attr.style) {
+                    var attrStyle = { style: attr.style, editable: false };
+                    attrStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", attr.name);
+                    attrStyle.selectorText = nodeName + "[" + attr.name;
+                    if (attr.value.length)
+                        attrStyle.selectorText += "=" + attr.value;
+                    attrStyle.selectorText += "]";
+                    styleRules.push(attrStyle);
+                }
+            }
+
+            // Always Show element's Style Attributes
+            if (node.nodeType === Node.ELEMENT_NODE) {
+                var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: node.style, isAttribute: true };
+                inlineStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", "style");
+                styleRules.push(inlineStyle);
+            }
+
+            var matchedStyleRules = node.ownerDocument.defaultView.getMatchedCSSRules(node, "", !Preferences.showUserAgentStyles);
+            if (matchedStyleRules) {
+                // Add rules in reverse order to match the cascade order.
+                for (var i = (matchedStyleRules.length - 1); i >= 0; --i) {
+                    var rule = matchedStyleRules[i];
+                    styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule });
+                }
+            }
+        }
+
+        function deleteDisabledProperty(style, name)
+        {
+            if (!style || !name)
+                return;
+            if (style.__disabledPropertyValues)
+                delete style.__disabledPropertyValues[name];
+            if (style.__disabledPropertyPriorities)
+                delete style.__disabledPropertyPriorities[name];
+            if (style.__disabledProperties)
+                delete style.__disabledProperties[name];
+        }
+
+        var usedProperties = {};
+        var disabledComputedProperties = {};
+        var priorityUsed = false;
+
+        // Walk the style rules and make a list of all used and overloaded properties.
+        for (var i = 0; i < styleRules.length; ++i) {
+            var styleRule = styleRules[i];
+            if (styleRule.computedStyle)
+                continue;
+            if (styleRule.section && styleRule.section.noAffect)
+                continue;
+
+            styleRule.usedProperties = {};
+
+            var style = styleRule.style;
+            for (var j = 0; j < style.length; ++j) {
+                var name = style[j];
+
+                if (!priorityUsed && style.getPropertyPriority(name).length)
+                    priorityUsed = true;
+
+                // If the property name is already used by another rule then this rule's
+                // property is overloaded, so don't add it to the rule's usedProperties.
+                if (!(name in usedProperties))
+                    styleRule.usedProperties[name] = true;
+
+                if (name === "font") {
+                    // The font property is not reported as a shorthand. Report finding the individual
+                    // properties so they are visible in computed style.
+                    // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15598 is fixed.
+                    styleRule.usedProperties["font-family"] = true;
+                    styleRule.usedProperties["font-size"] = true;
+                    styleRule.usedProperties["font-style"] = true;
+                    styleRule.usedProperties["font-variant"] = true;
+                    styleRule.usedProperties["font-weight"] = true;
+                    styleRule.usedProperties["line-height"] = true;
+                }
+
+                // Delete any disabled properties, since the property does exist.
+                // This prevents it from showing twice.
+                deleteDisabledProperty(style, name);
+                deleteDisabledProperty(style, style.getPropertyShorthand(name));
+            }
+
+            // Add all the properties found in this style to the used properties list.
+            // Do this here so only future rules are affect by properties used in this rule.
+            for (var name in styleRules[i].usedProperties)
+                usedProperties[name] = true;
+
+            // Remember all disabled properties so they show up in computed style.
+            if (style.__disabledProperties)
+                for (var name in style.__disabledProperties)
+                    disabledComputedProperties[name] = true;
+        }
+
+        if (priorityUsed) {
+            // Walk the properties again and account for !important.
+            var foundPriorityProperties = [];
+
+            // Walk in reverse to match the order !important overrides.
+            for (var i = (styleRules.length - 1); i >= 0; --i) {
+                if (styleRules[i].computedStyle)
+                    continue;
+
+                var style = styleRules[i].style;
+                var uniqueProperties = style.uniqueStyleProperties;
+                for (var j = 0; j < uniqueProperties.length; ++j) {
+                    var name = uniqueProperties[j];
+                    if (style.getPropertyPriority(name).length) {
+                        if (!(name in foundPriorityProperties))
+                            styleRules[i].usedProperties[name] = true;
+                        else
+                            delete styleRules[i].usedProperties[name];
+                        foundPriorityProperties[name] = true;
+                    } else if (name in foundPriorityProperties)
+                        delete styleRules[i].usedProperties[name];
+                }
+            }
+        }
+
+        if (refresh) {
+            // Walk the style rules and update the sections with new overloaded and used properties.
+            for (var i = 0; i < styleRules.length; ++i) {
+                var styleRule = styleRules[i];
+                var section = styleRule.section;
+                if (styleRule.computedStyle)
+                    section.disabledComputedProperties = disabledComputedProperties;
+                section._usedProperties = (styleRule.usedProperties || usedProperties);
+                section.update((section === editedSection) || styleRule.computedStyle);
+            }
+        } else {
+            // Make a property section for each style rule.
+            for (var i = 0; i < styleRules.length; ++i) {
+                var styleRule = styleRules[i];
+                var subtitle = styleRule.subtitle;
+                delete styleRule.subtitle;
+
+                var computedStyle = styleRule.computedStyle;
+                delete styleRule.computedStyle;
+
+                var ruleUsedProperties = styleRule.usedProperties;
+                delete styleRule.usedProperties;
+
+                var editable = styleRule.editable;
+                delete styleRule.editable;
+
+                var isAttribute = styleRule.isAttribute;
+                delete styleRule.isAttribute;
+
+                // Default editable to true if it was omitted.
+                if (typeof editable === "undefined")
+                    editable = true;
+
+                var section = new WebInspector.StylePropertiesSection(styleRule, subtitle, computedStyle, (ruleUsedProperties || usedProperties), editable);
+                if (computedStyle)
+                    section.disabledComputedProperties = disabledComputedProperties;
+                section.pane = this;
+
+                if (Preferences.styleRulesExpandedState && section.identifier in Preferences.styleRulesExpandedState)
+                    section.expanded = Preferences.styleRulesExpandedState[section.identifier];
+                else if (computedStyle)
+                    section.collapse(true);
+                else if (isAttribute && styleRule.style.length === 0)
+                    section.collapse(true);
+                else
+                    section.expand(true);
+
+                body.appendChild(section.element);
+                this.sections.push(section);
+            }
+        }
+    },
+
+    _changeSetting: function(event)
+    {
+        var options = this.settingsSelectElement.options;
+        var selectedOption = options[this.settingsSelectElement.selectedIndex];
+        selectedOption.action(event);
+
+        // Select the correct color format setting again, since it needs to be selected.
+        var selectedIndex = 0;
+        for (var i = 0; i < options.length; ++i) {
+            if (options[i].value === Preferences.colorFormat) {
+                selectedIndex = i;
+                break;
+            }
+        }
+
+        this.settingsSelectElement.selectedIndex = selectedIndex;
+    },
+
+    _changeColorFormat: function(event)
+    {
+        var selectedOption = this.settingsSelectElement[this.settingsSelectElement.selectedIndex];
+        Preferences.colorFormat = selectedOption.value;
+
+        InspectorController.setSetting("color-format", Preferences.colorFormat);
+
+        for (var i = 0; i < this.sections.length; ++i)
+            this.sections[i].update(true);
+    },
+
+    _createNewRule: function(event)
+    {
+        this.addBlankSection().startEditingSelector();
+    },
+
+    addBlankSection: function()
+    {
+        var blankSection = new WebInspector.BlankStylePropertiesSection(this.appropriateSelectorForNode());
+        blankSection.pane = this;
+
+        var elementStyleSection = this.sections[1];        
+        this.bodyElement.insertBefore(blankSection.element, elementStyleSection.element.nextSibling);
+
+        this.sections.splice(2, 0, blankSection);
+
+        return blankSection;
+    },
+
+    removeSection: function(section)
+    {
+        var index = this.sections.indexOf(section);
+        if (index === -1)
+            return;
+        this.sections.splice(index, 1);
+        if (section.element.parentNode)
+            section.element.parentNode.removeChild(section.element);
+    },
+
+    appropriateSelectorForNode: function()
+    {
+        var node = this.node;
+        if (!node)
+            return "";
+
+        var id = node.getAttribute("id");
+        if (id)
+            return "#" + id;
+
+        var className = node.getAttribute("class");
+        if (className)
+            return "." + className.replace(/\s+/, ".");
+
+        var nodeName = node.nodeName.toLowerCase();
+        if (nodeName === "input" && node.getAttribute("type"))
+            return nodeName + "[type=\"" + node.getAttribute("type") + "\"]";
+
+        return nodeName;
+    }
+}
+
+WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
+
+WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable)
+{
+    WebInspector.PropertiesSection.call(this, styleRule.selectorText);
+
+    this.titleElement.addEventListener("click", function(e) { e.stopPropagation(); }, false);
+    this.titleElement.addEventListener("dblclick", this._dblclickSelector.bind(this), false);
+    this.element.addEventListener("dblclick", this._dblclickEmptySpace.bind(this), false);
+
+    this.styleRule = styleRule;
+    this.rule = this.styleRule.rule;
+    this.computedStyle = computedStyle;
+    this.editable = (editable && !computedStyle);
+
+    // Prevent editing the user agent and user rules.
+    var isUserAgent = this.rule && this.rule.isUserAgent;
+    var isUser = this.rule && this.rule.isUser;
+    var isViaInspector = this.rule && this.rule.isViaInspector;
+
+    if (isUserAgent || isUser)
+        this.editable = false;
+
+    this._usedProperties = usedProperties;
+
+    if (computedStyle) {
+        this.element.addStyleClass("computed-style");
+
+        if (Preferences.showInheritedComputedStyleProperties)
+            this.element.addStyleClass("show-inherited");
+
+        var showInheritedLabel = document.createElement("label");
+        var showInheritedInput = document.createElement("input");
+        showInheritedInput.type = "checkbox";
+        showInheritedInput.checked = Preferences.showInheritedComputedStyleProperties;
+
+        var computedStyleSection = this;
+        var showInheritedToggleFunction = function(event) {
+            Preferences.showInheritedComputedStyleProperties = showInheritedInput.checked;
+            if (Preferences.showInheritedComputedStyleProperties)
+                computedStyleSection.element.addStyleClass("show-inherited");
+            else
+                computedStyleSection.element.removeStyleClass("show-inherited");
+            event.stopPropagation();
+        };
+
+        showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false);
+
+        showInheritedLabel.appendChild(showInheritedInput);
+        showInheritedLabel.appendChild(document.createTextNode(WebInspector.UIString("Show inherited")));
+        this.subtitleElement.appendChild(showInheritedLabel);
+    } else {
+        if (!subtitle) {
+            if (this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.href) {
+                var url = this.styleRule.parentStyleSheet.href;
+                subtitle = WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url));
+                this.subtitleElement.addStyleClass("file");
+            } else if (isUserAgent)
+                subtitle = WebInspector.UIString("user agent stylesheet");
+            else if (isUser)
+                subtitle = WebInspector.UIString("user stylesheet");
+            else if (isViaInspector)
+                subtitle = WebInspector.UIString("via inspector");
+            else
+                subtitle = WebInspector.UIString("inline stylesheet");
+        }
+
+        this.subtitle = subtitle;
+    }
+
+    this.identifier = styleRule.selectorText;
+    if (this.subtitle)
+        this.identifier += ":" + this.subtitleElement.textContent;    
+}
+
+WebInspector.StylePropertiesSection.prototype = {
+    get usedProperties()
+    {
+        return this._usedProperties || {};
+    },
+
+    set usedProperties(x)
+    {
+        this._usedProperties = x;
+        this.update();
+    },
+
+    expand: function(dontRememberState)
+    {
+        WebInspector.PropertiesSection.prototype.expand.call(this);
+        if (dontRememberState)
+            return;
+
+        if (!Preferences.styleRulesExpandedState)
+            Preferences.styleRulesExpandedState = {};
+        Preferences.styleRulesExpandedState[this.identifier] = true;
+    },
+
+    collapse: function(dontRememberState)
+    {
+        WebInspector.PropertiesSection.prototype.collapse.call(this);
+        if (dontRememberState)
+            return;
+
+        if (!Preferences.styleRulesExpandedState)
+            Preferences.styleRulesExpandedState = {};
+        Preferences.styleRulesExpandedState[this.identifier] = false;
+    },
+
+    isPropertyInherited: function(property)
+    {
+        if (!this.computedStyle || !this._usedProperties || this.noAffect)
+            return false;
+        // These properties should always show for Computed Style.
+        var alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
+        return !(property in this.usedProperties) && !(property in alwaysShowComputedProperties) && !(property in this.disabledComputedProperties);
+    },
+
+    isPropertyOverloaded: function(property, shorthand)
+    {
+        if (this.computedStyle || !this._usedProperties || this.noAffect)
+            return false;
+
+        var used = (property in this.usedProperties);
+        if (used || !shorthand)
+            return !used;
+
+        // Find out if any of the individual longhand properties of the shorthand
+        // are used, if none are then the shorthand is overloaded too.
+        var longhandProperties = this.styleRule.style.getLonghandProperties(property);
+        for (var j = 0; j < longhandProperties.length; ++j) {
+            var individualProperty = longhandProperties[j];
+            if (individualProperty in this.usedProperties)
+                return false;
+        }
+
+        return true;
+    },
+
+    isInspectorStylesheet: function()
+    {
+        return (this.styleRule.parentStyleSheet === WebInspector.panels.elements.stylesheet);
+    },
+
+    update: function(full)
+    {
+        if (full || this.computedStyle) {
+            this.propertiesTreeOutline.removeChildren();
+            this.populated = false;
+        } else {
+            var child = this.propertiesTreeOutline.children[0];
+            while (child) {
+                child.overloaded = this.isPropertyOverloaded(child.name, child.shorthand);
+                child = child.traverseNextTreeElement(false, null, true);
+            }
+        }
+
+        if (this._afterUpdate) {
+            this._afterUpdate(this);
+            delete this._afterUpdate;
+        }
+    },
+
+    onpopulate: function()
+    {
+        var style = this.styleRule.style;
+
+        var foundShorthands = {};
+        var uniqueProperties = style.uniqueStyleProperties;
+        var disabledProperties = style.__disabledPropertyValues || {};
+
+        for (var name in disabledProperties)
+            uniqueProperties.push(name);
+
+        uniqueProperties.sort();
+
+        for (var i = 0; i < uniqueProperties.length; ++i) {
+            var name = uniqueProperties[i];
+            var disabled = name in disabledProperties;
+            if (!disabled && this.disabledComputedProperties && !(name in this.usedProperties) && name in this.disabledComputedProperties)
+                disabled = true;
+
+            var shorthand = !disabled ? style.getPropertyShorthand(name) : null;
+
+            if (shorthand && shorthand in foundShorthands)
+                continue;
+
+            if (shorthand) {
+                foundShorthands[shorthand] = true;
+                name = shorthand;
+            }
+
+            var isShorthand = (shorthand ? true : false);
+            var inherited = this.isPropertyInherited(name);
+            var overloaded = this.isPropertyOverloaded(name, isShorthand);
+
+            var item = new WebInspector.StylePropertyTreeElement(this.styleRule, style, name, isShorthand, inherited, overloaded, disabled);
+            this.propertiesTreeOutline.appendChild(item);
+        }
+    },
+
+    findTreeElementWithName: function(name)
+    {
+        var treeElement = this.propertiesTreeOutline.children[0];
+        while (treeElement) {
+            if (treeElement.name === name)
+                return treeElement;
+            treeElement = treeElement.traverseNextTreeElement(true, null, true);
+        }
+        return null;
+    },
+
+    addNewBlankProperty: function()
+    {
+        var item = new WebInspector.StylePropertyTreeElement(this.styleRule, this.styleRule.style, "", false, false, false, false);
+        this.propertiesTreeOutline.appendChild(item);
+        item.listItemElement.textContent = "";
+        item._newProperty = true;
+        return item;
+    },
+
+    _dblclickEmptySpace: function(event)
+    {
+        this.expand();
+        this.addNewBlankProperty().startEditing();
+    },
+
+    _dblclickSelector: function(event)
+    {
+        if (!this.editable)
+            return;
+
+        if (!this.rule && this.propertiesTreeOutline.children.length === 0) {
+            this.expand();
+            this.addNewBlankProperty().startEditing();
+            return;
+        }
+
+        if (!this.rule)
+            return;
+
+        this.startEditingSelector();
+        event.stopPropagation();
+    },
+
+    startEditingSelector: function()
+    {
+        var element = this.titleElement;
+        if (WebInspector.isBeingEdited(element))
+            return;
+
+        WebInspector.startEditing(this.titleElement, this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled.bind(this), null);
+        window.getSelection().setBaseAndExtent(element, 0, element, 1);
+    },
+
+    editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
+    {
+        function moveToNextIfNeeded() {
+            if (!moveDirection || moveDirection !== "forward")
+                return;
+
+            this.expand();
+            if (this.propertiesTreeOutline.children.length === 0)
+                this.addNewBlankProperty().startEditing();
+            else {
+                var item = this.propertiesTreeOutline.children[0]
+                item.startEditing(item.valueElement);
+            }
+        }
+
+        if (newContent === oldContent)
+            return moveToNextIfNeeded.call(this);
+
+        var self = this;
+        function callback(result)
+        {
+            if (!result) {
+                // Invalid Syntax for a Selector
+                moveToNextIfNeeded.call(self);
+                return;
+            }
+
+            var newRulePayload = result[0];
+            var doesAffectSelectedNode = result[1];
+            if (!doesAffectSelectedNode) {
+                self.noAffect = true;
+                self.element.addStyleClass("no-affect");
+            } else {
+                delete self.noAffect;
+                self.element.removeStyleClass("no-affect");
+            }
+
+            var newRule = WebInspector.CSSStyleDeclaration.parseRule(newRulePayload);
+            self.rule = newRule;
+            self.styleRule = { section: self, style: newRule.style, selectorText: newRule.selectorText, parentStyleSheet: newRule.parentStyleSheet, rule: newRule };
+
+            var oldIdentifier = this.identifier;
+            self.identifier = newRule.selectorText + ":" + self.subtitleElement.textContent;
+
+            self.pane.update();
+
+            WebInspector.panels.elements.renameSelector(oldIdentifier, this.identifier, oldContent, newContent);
+
+            moveToNextIfNeeded.call(self);
+        }
+
+        InjectedScriptAccess.applyStyleRuleText(this.rule.id, newContent, this.pane.node.id, callback);
+    },
+
+    editingSelectorCancelled: function()
+    {
+        // Do nothing, this is overridden by BlankStylePropertiesSection.
+    }
+}
+
+WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
+
+WebInspector.BlankStylePropertiesSection = function(defaultSelectorText)
+{
+    WebInspector.StylePropertiesSection.call(this, {selectorText: defaultSelectorText, rule: {isViaInspector: true}}, "", false, {}, false);
+
+    this.element.addStyleClass("blank-section");
+}
+
+WebInspector.BlankStylePropertiesSection.prototype = {
+    expand: function()
+    {
+        // Do nothing, blank sections are not expandable.
+    },
+
+    editingSelectorCommitted: function(element, newContent, oldContent, context)
+    {
+        var self = this;
+        function callback(result)
+        {
+            if (!result) {
+                // Invalid Syntax for a Selector
+                self.editingSelectorCancelled();
+                return;
+            }
+
+            var rule = result[0];
+            var doesSelectorAffectSelectedNode = result[1];
+
+            var styleRule = WebInspector.CSSStyleDeclaration.parseRule(rule);
+            styleRule.rule = rule;
+
+            self.makeNormal(styleRule);
+
+            if (!doesSelectorAffectSelectedNode) {
+                self.noAffect = true;
+                self.element.addStyleClass("no-affect");
+            }
+
+            self.subtitleElement.textContent = WebInspector.UIString("via inspector");
+            self.expand();
+
+            self.addNewBlankProperty().startEditing();
+        }
+
+        InjectedScriptAccess.addStyleSelector(newContent, this.pane.node.id, callback);
+    },
+
+    editingSelectorCancelled: function()
+    {
+        this.pane.removeSection(this);
+    },
+
+    makeNormal: function(styleRule)
+    {
+        this.element.removeStyleClass("blank-section");
+
+        this.styleRule = styleRule;
+        this.rule = styleRule.rule;
+        this.computedStyle = false;
+        this.editable = true;
+        this.identifier = styleRule.selectorText + ":via inspector";
+
+        this.__proto__ = WebInspector.StylePropertiesSection.prototype;
+    }
+}
+
+WebInspector.BlankStylePropertiesSection.prototype.__proto__ = WebInspector.StylePropertiesSection.prototype;
+
+WebInspector.StylePropertyTreeElement = function(styleRule, style, name, shorthand, inherited, overloaded, disabled)
+{
+    this._styleRule = styleRule;
+    this.style = style;
+    this.name = name;
+    this.shorthand = shorthand;
+    this._inherited = inherited;
+    this._overloaded = overloaded;
+    this._disabled = disabled;
+
+    // Pass an empty title, the title gets made later in onattach.
+    TreeElement.call(this, "", null, shorthand);
+}
+
+WebInspector.StylePropertyTreeElement.prototype = {
+    get inherited()
+    {
+        return this._inherited;
+    },
+
+    set inherited(x)
+    {
+        if (x === this._inherited)
+            return;
+        this._inherited = x;
+        this.updateState();
+    },
+
+    get overloaded()
+    {
+        return this._overloaded;
+    },
+
+    set overloaded(x)
+    {
+        if (x === this._overloaded)
+            return;
+        this._overloaded = x;
+        this.updateState();
+    },
+
+    get disabled()
+    {
+        return this._disabled;
+    },
+
+    set disabled(x)
+    {
+        if (x === this._disabled)
+            return;
+        this._disabled = x;
+        this.updateState();
+    },
+
+    get priority()
+    {
+        if (this.disabled && this.style.__disabledPropertyPriorities && this.name in this.style.__disabledPropertyPriorities)
+            return this.style.__disabledPropertyPriorities[this.name];
+        return (this.shorthand ? this.style.getShorthandPriority(this.name) : this.style.getPropertyPriority(this.name));
+    },
+
+    get value()
+    {
+        if (this.disabled && this.style.__disabledPropertyValues && this.name in this.style.__disabledPropertyValues)
+            return this.style.__disabledPropertyValues[this.name];
+        return (this.shorthand ? this.style.getShorthandValue(this.name) : this.style.getPropertyValue(this.name));
+    },
+
+    onattach: function()
+    {
+        this.updateTitle();
+    },
+
+    updateTitle: function()
+    {
+        var priority = this.priority;
+        var value = this.value;
+
+        if (priority && !priority.length)
+            delete priority;
+        if (priority)
+            priority = "!" + priority;
+
+        this.updateState();
+
+        var enabledCheckboxElement = document.createElement("input");
+        enabledCheckboxElement.className = "enabled-button";
+        enabledCheckboxElement.type = "checkbox";
+        enabledCheckboxElement.checked = !this.disabled;
+        enabledCheckboxElement.addEventListener("change", this.toggleEnabled.bind(this), false);
+
+        var nameElement = document.createElement("span");
+        nameElement.className = "name";
+        nameElement.textContent = this.name;
+        this.nameElement = nameElement;
+
+        var valueElement = document.createElement("span");
+        valueElement.className = "value";
+        this.valueElement = valueElement;
+
+        if (value) {
+            function processValue(regex, processor, nextProcessor, valueText)
+            {
+                var container = document.createDocumentFragment();
+
+                var items = valueText.replace(regex, "\0$1\0").split("\0");
+                for (var i = 0; i < items.length; ++i) {
+                    if ((i % 2) === 0) {
+                        if (nextProcessor)
+                            container.appendChild(nextProcessor(items[i]));
+                        else
+                            container.appendChild(document.createTextNode(items[i]));
+                    } else {
+                        var processedNode = processor(items[i]);
+                        if (processedNode)
+                            container.appendChild(processedNode);
+                    }
+                }
+
+                return container;
+            }
+
+            function linkifyURL(url)
+            {
+                var container = document.createDocumentFragment();
+                container.appendChild(document.createTextNode("url("));
+                container.appendChild(WebInspector.linkifyURLAsNode(url, url, null, (url in WebInspector.resourceURLMap)));
+                container.appendChild(document.createTextNode(")"));
+                return container;
+            }
+
+            function processColor(text)
+            {
+                try {
+                    var color = new WebInspector.Color(text);
+                } catch (e) {
+                    return document.createTextNode(text);
+                }
+
+                var swatchElement = document.createElement("span");
+                swatchElement.title = WebInspector.UIString("Click to change color format");
+                swatchElement.className = "swatch";
+                swatchElement.style.setProperty("background-color", text);
+
+                swatchElement.addEventListener("click", changeColorDisplay, false);
+                swatchElement.addEventListener("dblclick", function(event) { event.stopPropagation() }, false);
+
+                var format;
+                if (Preferences.showColorNicknames && color.nickname)
+                    format = "nickname";
+                else if (Preferences.colorFormat === "rgb")
+                    format = (color.simple ? "rgb" : "rgba");
+                else if (Preferences.colorFormat === "hsl")
+                    format = (color.simple ? "hsl" : "hsla");
+                else if (color.simple)
+                    format = (color.hasShortHex() ? "shorthex" : "hex");
+                else
+                    format = "rgba";
+
+                var colorValueElement = document.createElement("span");
+                colorValueElement.textContent = color.toString(format);
+
+                function changeColorDisplay(event)
+                {
+                    switch (format) {
+                        case "rgb":
+                            format = "hsl";
+                            break;
+
+                        case "shorthex":
+                            format = "hex";
+                            break;
+
+                        case "hex":
+                            format = "rgb";
+                            break;
+
+                        case "nickname":
+                            if (color.simple) {
+                                if (color.hasShortHex())
+                                    format = "shorthex";
+                                else
+                                    format = "hex";
+                                break;
+                            }
+
+                            format = "rgba";
+                            break;
+
+                        case "hsl":
+                            if (color.nickname)
+                                format = "nickname";
+                            else if (color.hasShortHex())
+                                format = "shorthex";
+                            else
+                                format = "hex";
+                            break;
+
+                        case "rgba":
+                            format = "hsla";
+                            break;
+
+                        case "hsla":
+                            if (color.nickname)
+                                format = "nickname";
+                            else
+                                format = "rgba";
+                            break;
+                    }
+
+                    colorValueElement.textContent = color.toString(format);
+                }
+
+                var container = document.createDocumentFragment();
+                container.appendChild(swatchElement);
+                container.appendChild(colorValueElement);
+                return container;
+            }
+
+            var colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b)/g;
+            var colorProcessor = processValue.bind(window, colorRegex, processColor, null);
+
+            valueElement.appendChild(processValue(/url\(([^)]+)\)/g, linkifyURL, colorProcessor, value));
+        }
+
+        if (priority) {
+            var priorityElement = document.createElement("span");
+            priorityElement.className = "priority";
+            priorityElement.textContent = priority;
+        }
+
+        this.listItemElement.removeChildren();
+
+        // Append the checkbox for root elements of an editable section.
+        if (this.treeOutline.section && this.treeOutline.section.editable && this.parent.root)
+            this.listItemElement.appendChild(enabledCheckboxElement);
+        this.listItemElement.appendChild(nameElement);
+        this.listItemElement.appendChild(document.createTextNode(": "));
+        this.listItemElement.appendChild(valueElement);
+
+        if (priorityElement) {
+            this.listItemElement.appendChild(document.createTextNode(" "));
+            this.listItemElement.appendChild(priorityElement);
+        }
+
+        this.listItemElement.appendChild(document.createTextNode(";"));
+
+        this.tooltip = this.name + ": " + valueElement.textContent + (priority ? " " + priority : "");
+    },
+
+    updateAll: function(updateAllRules)
+    {
+        if (updateAllRules && this.treeOutline.section && this.treeOutline.section.pane)
+            this.treeOutline.section.pane.update(null, this.treeOutline.section);
+        else if (this.treeOutline.section)
+            this.treeOutline.section.update(true);
+        else
+            this.updateTitle(); // FIXME: this will not show new properties. But we don't hit his case yet.
+    },
+
+    toggleEnabled: function(event)
+    {
+        var disabled = !event.target.checked;
+
+        var self = this;
+        function callback(newPayload)
+        {
+            if (!newPayload)
+                return;
+
+            self.style = WebInspector.CSSStyleDeclaration.parseStyle(newPayload);
+            self._styleRule.style = self.style;
+
+            // Set the disabled property here, since the code above replies on it not changing
+            // until after the value and priority are retrieved.
+            self.disabled = disabled;
+
+            if (self.treeOutline.section && self.treeOutline.section.pane)
+                self.treeOutline.section.pane.dispatchEventToListeners("style property toggled");
+
+            self.updateAll(true);
+        }
+
+        InjectedScriptAccess.toggleStyleEnabled(this.style.id, this.name, disabled, callback);
+    },
+
+    updateState: function()
+    {
+        if (!this.listItemElement)
+            return;
+
+        if (this.style.isPropertyImplicit(this.name) || this.value === "initial")
+            this.listItemElement.addStyleClass("implicit");
+        else
+            this.listItemElement.removeStyleClass("implicit");
+
+        if (this.inherited)
+            this.listItemElement.addStyleClass("inherited");
+        else
+            this.listItemElement.removeStyleClass("inherited");
+
+        if (this.overloaded)
+            this.listItemElement.addStyleClass("overloaded");
+        else
+            this.listItemElement.removeStyleClass("overloaded");
+
+        if (this.disabled)
+            this.listItemElement.addStyleClass("disabled");
+        else
+            this.listItemElement.removeStyleClass("disabled");
+    },
+
+    onpopulate: function()
+    {
+        // Only populate once and if this property is a shorthand.
+        if (this.children.length || !this.shorthand)
+            return;
+
+        var longhandProperties = this.style.getLonghandProperties(this.name);
+        for (var i = 0; i < longhandProperties.length; ++i) {
+            var name = longhandProperties[i];
+
+            if (this.treeOutline.section) {
+                var inherited = this.treeOutline.section.isPropertyInherited(name);
+                var overloaded = this.treeOutline.section.isPropertyOverloaded(name);
+            }
+
+            var item = new WebInspector.StylePropertyTreeElement(this._styleRule, this.style, name, false, inherited, overloaded);
+            this.appendChild(item);
+        }
+    },
+
+    ondblclick: function(element, event)
+    {
+        this.startEditing(event.target);
+        event.stopPropagation();
+    },
+
+    startEditing: function(selectElement)
+    {
+        // FIXME: we don't allow editing of longhand properties under a shorthand right now.
+        if (this.parent.shorthand)
+            return;
+
+        if (WebInspector.isBeingEdited(this.listItemElement) || (this.treeOutline.section && !this.treeOutline.section.editable))
+            return;
+
+        var context = { expanded: this.expanded, hasChildren: this.hasChildren };
+
+        // Lie about our children to prevent expanding on double click and to collapse shorthands.
+        this.hasChildren = false;
+
+        if (!selectElement)
+            selectElement = this.listItemElement;
+
+        this.listItemElement.handleKeyEvent = this.editingKeyDown.bind(this);
+
+        WebInspector.startEditing(this.listItemElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
+        window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
+    },
+
+    editingKeyDown: function(event)
+    {
+        var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down");
+        var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
+        if (!arrowKeyPressed && !pageKeyPressed)
+            return;
+
+        var selection = window.getSelection();
+        if (!selection.rangeCount)
+            return;
+
+        var selectionRange = selection.getRangeAt(0);
+        if (selectionRange.commonAncestorContainer !== this.listItemElement && !selectionRange.commonAncestorContainer.isDescendant(this.listItemElement))
+            return;
+
+        const styleValueDelimeters = " \t\n\"':;,/()";
+        var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, styleValueDelimeters, this.listItemElement);
+        var wordString = wordRange.toString();
+        var replacementString = wordString;
+
+        var matches = /(.*?)(-?\d+(?:\.\d+)?)(.*)/.exec(wordString);
+        if (matches && matches.length) {
+            var prefix = matches[1];
+            var number = parseFloat(matches[2]);
+            var suffix = matches[3];
+
+            // If the number is near zero or the number is one and the direction will take it near zero.
+            var numberNearZero = (number < 1 && number > -1);
+            if (number === 1 && event.keyIdentifier === "Down")
+                numberNearZero = true;
+            else if (number === -1 && event.keyIdentifier === "Up")
+                numberNearZero = true;
+
+            if (numberNearZero && event.altKey && arrowKeyPressed) {
+                if (event.keyIdentifier === "Down")
+                    number = Math.ceil(number - 1);
+                else
+                    number = Math.floor(number + 1);
+            } else {
+                // Jump by 10 when shift is down or jump by 0.1 when near zero or Alt/Option is down.
+                // Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
+                var changeAmount = 1;
+                if (event.shiftKey && pageKeyPressed)
+                    changeAmount = 100;
+                else if (event.shiftKey || pageKeyPressed)
+                    changeAmount = 10;
+                else if (event.altKey || numberNearZero)
+                    changeAmount = 0.1;
+
+                if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
+                    changeAmount *= -1;
+
+                // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
+                // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
+                number = Number((number + changeAmount).toFixed(6));
+            }
+
+            replacementString = prefix + number + suffix;
+        } else {
+            // FIXME: this should cycle through known keywords for the current property name.
+            return;
+        }
+
+        var replacementTextNode = document.createTextNode(replacementString);
+
+        wordRange.deleteContents();
+        wordRange.insertNode(replacementTextNode);
+
+        var finalSelectionRange = document.createRange();
+        finalSelectionRange.setStart(replacementTextNode, 0);
+        finalSelectionRange.setEnd(replacementTextNode, replacementString.length);
+
+        selection.removeAllRanges();
+        selection.addRange(finalSelectionRange);
+
+        event.preventDefault();
+        event.handled = true;
+
+        if (!this.originalCSSText) {
+            // Remember the rule's original CSS text, so it can be restored
+            // if the editing is canceled and before each apply.
+            this.originalCSSText = this.style.styleTextWithShorthands();
+        } else {
+            // Restore the original CSS text before applying user changes. This is needed to prevent
+            // new properties from sticking around if the user adds one, then removes it.
+            InjectedScriptAccess.setStyleText(this.style.id, this.originalCSSText);
+        }
+
+        this.applyStyleText(this.listItemElement.textContent);
+    },
+
+    editingEnded: function(context)
+    {
+        this.hasChildren = context.hasChildren;
+        if (context.expanded)
+            this.expand();
+        delete this.listItemElement.handleKeyEvent;
+        delete this.originalCSSText;
+    },
+
+    editingCancelled: function(element, context)
+    {
+        if (this._newProperty)
+            this.treeOutline.removeChild(this);
+        else if (this.originalCSSText) {
+            InjectedScriptAccess.setStyleText(this.style.id, this.originalCSSText);
+
+            if (this.treeOutline.section && this.treeOutline.section.pane)
+                this.treeOutline.section.pane.dispatchEventToListeners("style edited");
+
+            this.updateAll();
+        } else
+            this.updateTitle();
+
+        this.editingEnded(context);
+    },
+
+    editingCommitted: function(element, userInput, previousContent, context, moveDirection)
+    {
+        this.editingEnded(context);
+
+        // Determine where to move to before making changes
+        var newProperty, moveToPropertyName, moveToSelector;
+        var moveTo = (moveDirection === "forward" ? this.nextSibling : this.previousSibling);
+        if (moveTo)
+            moveToPropertyName = moveTo.name;
+        else if (moveDirection === "forward")
+            newProperty = true;
+        else if (moveDirection === "backward" && this.treeOutline.section.rule)
+            moveToSelector = true;
+
+        // Make the Changes and trigger the moveToNextCallback after updating
+        var blankInput = /^\s*$/.test(userInput);
+        if (userInput !== previousContent || (this._newProperty && blankInput)) { // only if something changed, or adding a new style and it was blank
+            this.treeOutline.section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput);
+            this.applyStyleText(userInput, true);
+        } else
+            moveToNextCallback(this._newProperty, false, this.treeOutline.section, false);
+
+        // The Callback to start editing the next property
+        function moveToNextCallback(alreadyNew, valueChanged, section)
+        {
+            if (!moveDirection)
+                return;
+
+            // User just tabbed through without changes
+            if (moveTo && moveTo.parent) {
+                moveTo.startEditing(moveTo.valueElement);
+                return;
+            }
+
+            // User has made a change then tabbed, wiping all the original treeElements,
+            // recalculate the new treeElement for the same property we were going to edit next
+            if (moveTo && !moveTo.parent) {
+                var treeElement = section.findTreeElementWithName(moveToPropertyName);
+                if (treeElement)
+                    treeElement.startEditing(treeElement.valueElement);
+                return;
+            }
+
+            // Create a new attribute in this section
+            if (newProperty) {
+                if (alreadyNew && !valueChanged)
+                    return;
+
+                var item = section.addNewBlankProperty();
+                item.startEditing();
+                return;
+            }
+
+            if (moveToSelector)
+                section.startEditingSelector();
+        }
+    },
+
+    applyStyleText: function(styleText, updateInterface)
+    {
+        var section = this.treeOutline.section;
+        var elementsPanel = WebInspector.panels.elements;
+        var styleTextLength = styleText.trimWhitespace().length;
+        if (!styleTextLength && updateInterface) {
+            if (this._newProperty) {
+                // The user deleted everything, so remove the tree element and update.
+                this.parent.removeChild(this);
+                return;
+            } else {
+                delete section._afterUpdate;
+            }
+        }
+
+        var self = this;
+        function callback(result)
+        {
+            if (!result) {
+                // The user typed something, but it didn't parse. Just abort and restore
+                // the original title for this property.  If this was a new attribute and
+                // we couldn't parse, then just remove it.
+                if (self._newProperty) {
+                    self.parent.removeChild(self);
+                    return;
+                }
+                if (updateInterface)
+                    self.updateTitle();
+                return;
+            }
+
+            var newPayload = result[0];
+            var changedProperties = result[1];
+            elementsPanel.removeStyleChange(section.identifier, self.style, self.name);
+
+            if (!styleTextLength) {
+                // Do remove ourselves from UI when the property removal is confirmed.
+                self.parent.removeChild(self);
+            } else {
+                self.style = WebInspector.CSSStyleDeclaration.parseStyle(newPayload);
+                for (var i = 0; i < changedProperties.length; ++i)
+                    elementsPanel.addStyleChange(section.identifier, self.style, changedProperties[i]);
+                self._styleRule.style = self.style;
+            }
+
+            if (section && section.pane)
+                section.pane.dispatchEventToListeners("style edited");
+
+            if (updateInterface)
+                self.updateAll(true);
+
+            if (!self.rule)
+                WebInspector.panels.elements.treeOutline.update();
+        }
+
+        InjectedScriptAccess.applyStyleText(this.style.id, styleText.trimWhitespace(), this.name, callback);
+    }
+}
+
+WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SummaryBar.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SummaryBar.js
new file mode 100644
index 0000000..bbf2b1a2
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SummaryBar.js
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2008, 2009 Anthony Ricaud <[email protected]>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.SummaryBar = function(categories)
+{
+    this.categories = categories;
+
+    this.element = document.createElement("div");
+    this.element.className = "summary-bar";
+
+    this.graphElement = document.createElement("canvas");
+    this.graphElement.setAttribute("width", "450");
+    this.graphElement.setAttribute("height", "38");
+    this.graphElement.className = "summary-graph";
+    this.element.appendChild(this.graphElement);
+
+    this.legendElement = document.createElement("div");
+    this.legendElement.className = "summary-graph-legend";
+    this.element.appendChild(this.legendElement);
+}
+
+WebInspector.SummaryBar.prototype = {
+
+    get calculator() {
+        return this._calculator;
+    },
+
+    set calculator(x) {
+        this._calculator = x;
+    },
+
+    reset: function()
+    {
+        this.legendElement.removeChildren();
+        this._drawSummaryGraph();
+    },
+
+    update: function(data)
+    {
+        var graphInfo = this.calculator.computeSummaryValues(data);
+
+        var fillSegments = [];
+
+        this.legendElement.removeChildren();
+
+        for (var category in this.categories) {
+            var size = graphInfo.categoryValues[category];
+            if (!size)
+                continue;
+
+            var color = this.categories[category].color;
+            var colorString = "rgb(" + color.r + ", " + color.g + ", " + color.b + ")";
+
+            var fillSegment = {color: colorString, value: size};
+            fillSegments.push(fillSegment);
+
+            var legendLabel = this._makeLegendElement(this.categories[category].title, this.calculator.formatValue(size), colorString);
+            this.legendElement.appendChild(legendLabel);
+        }
+
+        if (graphInfo.total) {
+            var totalLegendLabel = this._makeLegendElement(WebInspector.UIString("Total"), this.calculator.formatValue(graphInfo.total));
+            totalLegendLabel.addStyleClass("total");
+            this.legendElement.appendChild(totalLegendLabel);
+        }
+
+        this._drawSummaryGraph(fillSegments);
+    },
+
+    _drawSwatch: function(canvas, color)
+    {
+        var ctx = canvas.getContext("2d");
+
+        function drawSwatchSquare() {
+            ctx.fillStyle = color;
+            ctx.fillRect(0, 0, 13, 13);
+
+            var gradient = ctx.createLinearGradient(0, 0, 13, 13);
+            gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)");
+            gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");
+
+            ctx.fillStyle = gradient;
+            ctx.fillRect(0, 0, 13, 13);
+
+            gradient = ctx.createLinearGradient(13, 13, 0, 0);
+            gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)");
+            gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)");
+
+            ctx.fillStyle = gradient;
+            ctx.fillRect(0, 0, 13, 13);
+
+            ctx.strokeStyle = "rgba(0, 0, 0, 0.6)";
+            ctx.strokeRect(0.5, 0.5, 12, 12);
+        }
+
+        ctx.clearRect(0, 0, 13, 24);
+
+        drawSwatchSquare();
+
+        ctx.save();
+
+        ctx.translate(0, 25);
+        ctx.scale(1, -1);
+
+        drawSwatchSquare();
+
+        ctx.restore();
+
+        this._fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0);
+    },
+
+    _drawSummaryGraph: function(segments)
+    {
+        if (!segments || !segments.length) {
+            segments = [{color: "white", value: 1}];
+            this._showingEmptySummaryGraph = true;
+        } else
+            delete this._showingEmptySummaryGraph;
+
+        // Calculate the total of all segments.
+        var total = 0;
+        for (var i = 0; i < segments.length; ++i)
+            total += segments[i].value;
+
+        // Calculate the percentage of each segment, rounded to the nearest percent.
+        var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) });
+
+        // Calculate the total percentage.
+        var percentTotal = 0;
+        for (var i = 0; i < percents.length; ++i)
+            percentTotal += percents[i];
+
+        // Make sure our percentage total is not greater-than 100, it can be greater
+        // if we rounded up for a few segments.
+        while (percentTotal > 100) {
+            for (var i = 0; i < percents.length && percentTotal > 100; ++i) {
+                if (percents[i] > 1) {
+                    --percents[i];
+                    --percentTotal;
+                }
+            }
+        }
+
+        // Make sure our percentage total is not less-than 100, it can be less
+        // if we rounded down for a few segments.
+        while (percentTotal < 100) {
+            for (var i = 0; i < percents.length && percentTotal < 100; ++i) {
+                ++percents[i];
+                ++percentTotal;
+            }
+        }
+
+        var ctx = this.graphElement.getContext("2d");
+
+        var x = 0;
+        var y = 0;
+        var w = 450;
+        var h = 19;
+        var r = (h / 2);
+
+        function drawPillShadow()
+        {
+            // This draws a line with a shadow that is offset away from the line. The line is stroked
+            // twice with different X shadow offsets to give more feathered edges. Later we erase the
+            // line with destination-out 100% transparent black, leaving only the shadow. This only
+            // works if nothing has been drawn into the canvas yet.
+
+            ctx.beginPath();
+            ctx.moveTo(x + 4, y + h - 3 - 0.5);
+            ctx.lineTo(x + w - 4, y + h - 3 - 0.5);
+            ctx.closePath();
+
+            ctx.save();
+
+            ctx.shadowBlur = 2;
+            ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
+            ctx.shadowOffsetX = 3;
+            ctx.shadowOffsetY = 5;
+
+            ctx.strokeStyle = "white";
+            ctx.lineWidth = 1;
+
+            ctx.stroke();
+
+            ctx.shadowOffsetX = -3;
+
+            ctx.stroke();
+
+            ctx.restore();
+
+            ctx.save();
+
+            ctx.globalCompositeOperation = "destination-out";
+            ctx.strokeStyle = "rgba(0, 0, 0, 1)";
+            ctx.lineWidth = 1;
+
+            ctx.stroke();
+
+            ctx.restore();
+        }
+
+        function drawPill()
+        {
+            // Make a rounded rect path.
+            ctx.beginPath();
+            ctx.moveTo(x, y + r);
+            ctx.lineTo(x, y + h - r);
+            ctx.quadraticCurveTo(x, y + h, x + r, y + h);
+            ctx.lineTo(x + w - r, y + h);
+            ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - r);
+            ctx.lineTo(x + w, y + r);
+            ctx.quadraticCurveTo(x + w, y, x + w - r, y);
+            ctx.lineTo(x + r, y);
+            ctx.quadraticCurveTo(x, y, x, y + r);
+            ctx.closePath();
+
+            // Clip to the rounded rect path.
+            ctx.save();
+            ctx.clip();
+
+            // Fill the segments with the associated color.
+            var previousSegmentsWidth = 0;
+            for (var i = 0; i < segments.length; ++i) {
+                var segmentWidth = Math.round(w * percents[i] / 100);
+                ctx.fillStyle = segments[i].color;
+                ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h);
+                previousSegmentsWidth += segmentWidth;
+            }
+
+            // Draw the segment divider lines.
+            ctx.lineWidth = 1;
+            for (var i = 1; i < 20; ++i) {
+                ctx.beginPath();
+                ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y);
+                ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h);
+                ctx.closePath();
+
+                ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
+                ctx.stroke();
+
+                ctx.beginPath();
+                ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y);
+                ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h);
+                ctx.closePath();
+
+                ctx.strokeStyle = "rgba(255, 255, 255, 0.2)";
+                ctx.stroke();
+            }
+
+            // Draw the pill shading.
+            var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5));
+            lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)");
+            lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)");
+            lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");
+
+            var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h);
+            darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)");
+            darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)");
+            darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)");
+
+            ctx.fillStyle = darkGradient;
+            ctx.fillRect(x, y, w, h);
+
+            ctx.fillStyle = lightGradient;
+            ctx.fillRect(x, y, w, h);
+
+            ctx.restore();
+        }
+
+        ctx.clearRect(x, y, w, (h * 2));
+
+        drawPillShadow();
+        drawPill();
+
+        ctx.save();
+
+        ctx.translate(0, (h * 2) + 1);
+        ctx.scale(1, -1);
+
+        drawPill();
+
+        ctx.restore();
+
+        this._fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0);
+    },
+
+    _fadeOutRect: function(ctx, x, y, w, h, a1, a2)
+    {
+        ctx.save();
+
+        var gradient = ctx.createLinearGradient(x, y, x, y + h);
+        gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")");
+        gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")");
+        gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)");
+
+        ctx.globalCompositeOperation = "destination-out";
+
+        ctx.fillStyle = gradient;
+        ctx.fillRect(x, y, w, h);
+
+        ctx.restore();
+    },
+
+    _makeLegendElement: function(label, value, color)
+    {
+        var legendElement = document.createElement("label");
+        legendElement.className = "summary-graph-legend-item";
+
+        if (color) {
+            var swatch = document.createElement("canvas");
+            swatch.className = "summary-graph-legend-swatch";
+            swatch.setAttribute("width", "13");
+            swatch.setAttribute("height", "24");
+
+            legendElement.appendChild(swatch);
+
+            this._drawSwatch(swatch, color);
+        }
+
+        var labelElement = document.createElement("div");
+        labelElement.className = "summary-graph-legend-label";
+        legendElement.appendChild(labelElement);
+
+        var headerElement = document.createElement("div");
+        headerElement.className = "summary-graph-legend-header";
+        headerElement.textContent = label;
+        labelElement.appendChild(headerElement);
+
+        var valueElement = document.createElement("div");
+        valueElement.className = "summary-graph-legend-value";
+        valueElement.textContent = value;
+        labelElement.appendChild(valueElement);
+
+        return legendElement;
+    }
+}
+
+WebInspector.SummaryBar.prototype.__proto__ = WebInspector.Object.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TextPrompt.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TextPrompt.js
new file mode 100644
index 0000000..5ff774f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TextPrompt.js
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2008 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+WebInspector.TextPrompt = function(element, completions, stopCharacters)
+{
+    this.element = element;
+    this.completions = completions;
+    this.completionStopCharacters = stopCharacters;
+    this.history = [];
+    this.historyOffset = 0;
+}
+
+WebInspector.TextPrompt.prototype = {
+    get text()
+    {
+        return this.element.textContent;
+    },
+
+    set text(x)
+    {
+        if (!x) {
+            // Append a break element instead of setting textContent to make sure the selection is inside the prompt.
+            this.element.removeChildren();
+            this.element.appendChild(document.createElement("br"));
+        } else
+            this.element.textContent = x;
+
+        this.moveCaretToEndOfPrompt();
+    },
+
+    handleKeyEvent: function(event)
+    {
+        switch (event.keyIdentifier) {
+            case "Up":
+                this._upKeyPressed(event);
+                break;
+            case "Down":
+                this._downKeyPressed(event);
+                break;
+            case "U+0009": // Tab
+                this._tabKeyPressed(event);
+                break;
+            case "Right":
+            case "End":
+                if (!this.acceptAutoComplete())
+                    this.autoCompleteSoon();
+                break;
+            default:
+                this.clearAutoComplete();
+                this.autoCompleteSoon();
+                break;
+        }
+    },
+
+    acceptAutoComplete: function()
+    {
+        if (!this.autoCompleteElement || !this.autoCompleteElement.parentNode)
+            return false;
+
+        var text = this.autoCompleteElement.textContent;
+        var textNode = document.createTextNode(text);
+        this.autoCompleteElement.parentNode.replaceChild(textNode, this.autoCompleteElement);
+        delete this.autoCompleteElement;
+
+        var finalSelectionRange = document.createRange();
+        finalSelectionRange.setStart(textNode, text.length);
+        finalSelectionRange.setEnd(textNode, text.length);
+
+        var selection = window.getSelection();
+        selection.removeAllRanges();
+        selection.addRange(finalSelectionRange);
+
+        return true;
+    },
+
+    clearAutoComplete: function(includeTimeout)
+    {
+        if (includeTimeout && "_completeTimeout" in this) {
+            clearTimeout(this._completeTimeout);
+            delete this._completeTimeout;
+        }
+
+        if (!this.autoCompleteElement)
+            return;
+
+        if (this.autoCompleteElement.parentNode)
+            this.autoCompleteElement.parentNode.removeChild(this.autoCompleteElement);
+        delete this.autoCompleteElement;
+
+        if (!this._userEnteredRange || !this._userEnteredText)
+            return;
+
+        this._userEnteredRange.deleteContents();
+
+        var userTextNode = document.createTextNode(this._userEnteredText);
+        this._userEnteredRange.insertNode(userTextNode);           
+
+        var selectionRange = document.createRange();
+        selectionRange.setStart(userTextNode, this._userEnteredText.length);
+        selectionRange.setEnd(userTextNode, this._userEnteredText.length);
+
+        var selection = window.getSelection();
+        selection.removeAllRanges();
+        selection.addRange(selectionRange);
+
+        delete this._userEnteredRange;
+        delete this._userEnteredText;
+    },
+
+    autoCompleteSoon: function()
+    {
+        if (!("_completeTimeout" in this))
+            this._completeTimeout = setTimeout(this.complete.bind(this, true), 250);
+    },
+
+    complete: function(auto)
+    {
+        this.clearAutoComplete(true);
+        var selection = window.getSelection();
+        if (!selection.rangeCount)
+            return;
+
+        var selectionRange = selection.getRangeAt(0);
+        if (!selectionRange.commonAncestorContainer.isDescendant(this.element))
+            return;
+        if (auto && !this.isCaretAtEndOfPrompt())
+            return;
+        var wordPrefixRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, this.completionStopCharacters, this.element, "backward");
+        this.completions(wordPrefixRange, auto, this._completionsReady.bind(this, selection, auto, wordPrefixRange));
+    },
+
+    _completionsReady: function(selection, auto, originalWordPrefixRange, completions)
+    {
+        if (!completions || !completions.length)
+            return;
+
+        var selectionRange = selection.getRangeAt(0);
+
+        var fullWordRange = document.createRange();
+        fullWordRange.setStart(originalWordPrefixRange.startContainer, originalWordPrefixRange.startOffset);
+        fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset);
+
+        if (originalWordPrefixRange.toString() + selectionRange.toString() != fullWordRange.toString())
+            return;
+
+        if (completions.length === 1 || selection.isCollapsed || auto) {
+            var completionText = completions[0];
+        } else {
+            var currentText = fullWordRange.toString();
+
+            var foundIndex = null;
+            for (var i = 0; i < completions.length; ++i) {
+                if (completions[i] === currentText)
+                    foundIndex = i;
+            }
+
+            if (foundIndex === null || (foundIndex + 1) >= completions.length)
+                var completionText = completions[0];
+            else
+                var completionText = completions[foundIndex + 1];
+        }
+
+        var wordPrefixLength = originalWordPrefixRange.toString().length;
+
+        this._userEnteredRange = fullWordRange;
+        this._userEnteredText = fullWordRange.toString();
+
+        fullWordRange.deleteContents();
+
+        var finalSelectionRange = document.createRange();
+
+        if (auto) {
+            var prefixText = completionText.substring(0, wordPrefixLength);
+            var suffixText = completionText.substring(wordPrefixLength);
+
+            var prefixTextNode = document.createTextNode(prefixText);
+            fullWordRange.insertNode(prefixTextNode);           
+
+            this.autoCompleteElement = document.createElement("span");
+            this.autoCompleteElement.className = "auto-complete-text";
+            this.autoCompleteElement.textContent = suffixText;
+
+            prefixTextNode.parentNode.insertBefore(this.autoCompleteElement, prefixTextNode.nextSibling);
+
+            finalSelectionRange.setStart(prefixTextNode, wordPrefixLength);
+            finalSelectionRange.setEnd(prefixTextNode, wordPrefixLength);
+        } else {
+            var completionTextNode = document.createTextNode(completionText);
+            fullWordRange.insertNode(completionTextNode);           
+
+            if (completions.length > 1)
+                finalSelectionRange.setStart(completionTextNode, wordPrefixLength);
+            else
+                finalSelectionRange.setStart(completionTextNode, completionText.length);
+
+            finalSelectionRange.setEnd(completionTextNode, completionText.length);
+        }
+
+        selection.removeAllRanges();
+        selection.addRange(finalSelectionRange);
+    },
+
+    isCaretInsidePrompt: function()
+    {
+        return this.element.isInsertionCaretInside();
+    },
+
+    isCaretAtEndOfPrompt: function()
+    {
+        var selection = window.getSelection();
+        if (!selection.rangeCount || !selection.isCollapsed)
+            return false;
+
+        var selectionRange = selection.getRangeAt(0);
+        var node = selectionRange.startContainer;
+        if (node !== this.element && !node.isDescendant(this.element))
+            return false;
+
+        if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < node.nodeValue.length)
+            return false;
+
+        var foundNextText = false;
+        while (node) {
+            if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) {
+                if (foundNextText)
+                    return false;
+                foundNextText = true;
+            }
+
+            node = node.traverseNextNode(false, this.element);
+        }
+
+        return true;
+    },
+
+    moveCaretToEndOfPrompt: function()
+    {
+        var selection = window.getSelection();
+        var selectionRange = document.createRange();
+
+        var offset = this.element.childNodes.length;
+        selectionRange.setStart(this.element, offset);
+        selectionRange.setEnd(this.element, offset);
+
+        selection.removeAllRanges();
+        selection.addRange(selectionRange);
+    },
+
+    _tabKeyPressed: function(event)
+    {
+        event.preventDefault();
+        event.stopPropagation();
+
+        this.complete();
+    },
+
+    _upKeyPressed: function(event)
+    {
+        event.preventDefault();
+        event.stopPropagation();
+
+        if (this.historyOffset == this.history.length)
+            return;
+
+        this.clearAutoComplete(true);
+
+        if (this.historyOffset == 0)
+            this.tempSavedCommand = this.text;
+
+        ++this.historyOffset;
+        this.text = this.history[this.history.length - this.historyOffset];
+    },
+
+    _downKeyPressed: function(event)
+    {
+        event.preventDefault();
+        event.stopPropagation();
+
+        if (this.historyOffset == 0)
+            return;
+
+        this.clearAutoComplete(true);
+
+        --this.historyOffset;
+
+        if (this.historyOffset == 0) {
+            this.text = this.tempSavedCommand;
+            delete this.tempSavedCommand;
+            return;
+        }
+
+        this.text = this.history[this.history.length - this.historyOffset];
+    }
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TimelineAgent.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TimelineAgent.js
new file mode 100644
index 0000000..6d18732
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TimelineAgent.js
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009 Google Inc. 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.
+ */
+
+WebInspector.TimelineAgent = function() {
+    // Not implemented.
+}
+
+// Must be kept in sync with TimelineItem.h
+WebInspector.TimelineAgent.ItemType = {
+    DOMDispatch       : 0,
+    Layout            : 1,
+    RecalculateStyles : 2,
+    Paint             : 3,
+    ParseHTML         : 4
+};
+
+WebInspector.addItemToTimeline = function(record) {
+    // Not implemented.
+}
+
+WebInspector.timelineWasEnabled = function() {
+    // Not implemented.
+}
+
+WebInspector.timelineWasDisabled = function() {
+    // Not implemented.
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TopDownProfileDataGridTree.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TopDownProfileDataGridTree.js
new file mode 100644
index 0000000..b9d8b94b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TopDownProfileDataGridTree.js
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2009 280 North Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.TopDownProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*TopDownProfileDataGridTree*/ owningTree)
+{
+    var hasChildren = (profileNode.children && profileNode.children.length);
+
+    WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, hasChildren);
+
+    this._remainingChildren = profileNode.children;
+}
+
+WebInspector.TopDownProfileDataGridNode.prototype = {
+    _populate: function(event)
+    {
+        var children = this._remainingChildren;
+        var childrenLength = children.length;
+
+        for (var i = 0; i < childrenLength; ++i)
+            this.appendChild(new WebInspector.TopDownProfileDataGridNode(this.profileView, children[i], this.tree));
+
+        if (this.removeEventListener)
+            this.removeEventListener("populate", this._populate, this);
+
+        this._remainingChildren = null;
+    },
+
+    _exclude: function(aCallUID)
+    {
+        if (this._remainingChildren)
+            this._populate();
+
+        this._save();
+
+        var children = this.children;
+        var index = this.children.length;
+
+        while (index--)
+            children[index]._exclude(aCallUID);
+
+        var child = this.childrenByCallUID[aCallUID];
+
+        if (child)
+            this._merge(child, true);
+    }
+}
+
+WebInspector.TopDownProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype;
+
+WebInspector.TopDownProfileDataGridTree = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode)
+{
+    WebInspector.ProfileDataGridTree.call(this, profileView, profileNode);
+
+    this._remainingChildren = profileNode.children;
+
+    WebInspector.TopDownProfileDataGridNode.prototype._populate.call(this);
+}
+
+WebInspector.TopDownProfileDataGridTree.prototype = {
+    focus: function(/*ProfileDataGridNode*/ profileDataGrideNode)
+    {
+        if (!profileDataGrideNode)
+            return;
+
+        this._save();
+
+        this.children = [profileDataGrideNode];
+        this.totalTime = profileDataGrideNode.totalTime;
+    },
+
+    exclude: function(/*ProfileDataGridNode*/ profileDataGrideNode)
+    {
+        if (!profileDataGrideNode)
+            return;
+
+        this._save();
+
+        var excludedCallUID = profileDataGrideNode.callUID;
+
+        WebInspector.TopDownProfileDataGridNode.prototype._exclude.call(this, excludedCallUID);
+
+        if (this.lastComparator)
+            this.sort(this.lastComparator, true);
+    },
+
+    _merge: WebInspector.TopDownProfileDataGridNode.prototype._merge
+}
+
+WebInspector.TopDownProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/View.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/View.js
new file mode 100644
index 0000000..632a61ae
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/View.js
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
+ */
+
+WebInspector.View = function(element)
+{
+    this.element = element || document.createElement("div");
+    this._visible = false;
+}
+
+WebInspector.View.prototype = {
+    get visible()
+    {
+        return this._visible;
+    },
+
+    set visible(x)
+    {
+        if (this._visible === x)
+            return;
+
+        if (x)
+            this.show();
+        else
+            this.hide();
+    },
+
+    show: function(parentElement)
+    {
+        this._visible = true;
+        if (parentElement && parentElement !== this.element.parentNode) {
+            this.detach();
+            parentElement.appendChild(this.element);
+        }
+        if (!this.element.parentNode && this.attach)
+            this.attach();
+        this.element.addStyleClass("visible");
+    },
+
+    hide: function()
+    {
+        this.element.removeStyleClass("visible");
+        this._visible = false;
+    },
+
+    detach: function()
+    {
+        if (this.element.parentNode)
+            this.element.parentNode.removeChild(this.element);
+    }
+}
+
+WebInspector.View.prototype.__proto__ = WebInspector.Object.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/WatchExpressionsSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/WatchExpressionsSidebarPane.js
new file mode 100644
index 0000000..b568939
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/WatchExpressionsSidebarPane.js
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) IBM Corp. 2009  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 IBM Corp. 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.
+ */
+
+WebInspector.WatchExpressionsSidebarPane = function()
+{
+    WebInspector.SidebarPane.call(this, WebInspector.UIString("Watch Expressions"));
+
+    this.section = new WebInspector.WatchExpressionsSection();
+
+    this.bodyElement.appendChild(this.section.element);
+
+    var addElement = document.createElement("button");
+    addElement.setAttribute("type", "button");
+    addElement.textContent = WebInspector.UIString("Add");
+    addElement.addEventListener("click", this.section.addExpression.bind(this.section), false);
+
+    var refreshElement = document.createElement("button");
+    refreshElement.setAttribute("type", "button");
+    refreshElement.textContent = WebInspector.UIString("Refresh");
+    refreshElement.addEventListener("click", this.section.update.bind(this.section), false);
+
+    var centerElement = document.createElement("div");
+    centerElement.addStyleClass("watch-expressions-buttons-container");
+    centerElement.appendChild(addElement);
+    centerElement.appendChild(refreshElement);
+    this.bodyElement.appendChild(centerElement);
+
+    this.expanded = this.section.loadSavedExpressions().length > 0;
+    this.onexpand = this.refreshExpressions.bind(this);
+}
+
+WebInspector.WatchExpressionsSidebarPane.prototype = {
+    refreshExpressions: function()
+    {
+        this.section.update();
+    }
+}
+
+WebInspector.WatchExpressionsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype;
+
+WebInspector.WatchExpressionsSection = function()
+{
+    WebInspector.ObjectPropertiesSection.call(this);
+
+    this.watchExpressions = this.loadSavedExpressions();
+
+    this.headerElement.className = "hidden";
+    this.editable = true;
+    this.expanded = true;
+    this.propertiesElement.addStyleClass("watch-expressions");
+}
+
+WebInspector.WatchExpressionsSection.NewWatchExpression = "\xA0";
+
+WebInspector.WatchExpressionsSection.prototype = {
+    update: function()
+    {
+        function appendResult(expression, watchIndex, result, exception)
+        {
+            // The null check catches some other cases, like null itself, and NaN
+            if ((typeof result !== "object") || (result == null))
+                result = new WebInspector.ObjectProxy(null, [], 0, String(result), false);
+
+            var property = new WebInspector.ObjectPropertyProxy(expression, result);
+            property.watchIndex = watchIndex;
+
+            // For newly added, empty expressions, set description to "",
+            // since otherwise you get DOMWindow
+            if (property.name === WebInspector.WatchExpressionsSection.NewWatchExpression) 
+                property.value.description = "";
+            
+            // To clarify what's going on here: 
+            // In the outer function, we calculate the number of properties
+            // that we're going to be updating, and set that in the
+            // propertyCount variable.  
+            // In this function, we test to see when we are processing the 
+            // last property, and then call the superclass's updateProperties() 
+            // method to get all the properties refreshed at once.
+            properties.push(property);
+            
+            if (properties.length == propertyCount)
+                this.updateProperties(properties, WebInspector.WatchExpressionTreeElement, WebInspector.WatchExpressionsSection.CompareProperties);
+        }
+
+        var properties = [];
+
+        // Count the properties, so we known when to call this.updateProperties()
+        // in appendResult()
+        var propertyCount = 0;
+        for (var i = 0; i < this.watchExpressions.length; ++i) {
+            if (!this.watchExpressions[i]) 
+                continue;
+            ++propertyCount;
+        }
+
+        // Now process all the expressions, since we have the actual count,
+        // which is checked in the appendResult inner function.
+        for (var i = 0; i < this.watchExpressions.length; ++i) {
+            var expression = this.watchExpressions[i];
+            if (!expression) 
+                continue;
+
+            WebInspector.console.evalInInspectedWindow("(" + expression + ")", appendResult.bind(this, expression, i));
+        }
+
+        // note this is setting the expansion of the tree, not the section;
+        // with no expressions, and expanded tree, we get some extra vertical
+        // white space
+        // FIXME: should change to use header buttons instead of the buttons
+        // at the bottom of the section, then we can add a "No Watch Expressions
+        // element when there are no watch expressions, and this issue should
+        // go away.
+        this.expanded = (propertyCount != 0);
+    },
+
+    addExpression: function()
+    {
+        this.watchExpressions.push(WebInspector.WatchExpressionsSection.NewWatchExpression);
+        this.update();
+
+        // After update(), the new empty expression to be edited
+        // will be in the tree, but we have to find it.
+        treeElement = this.findAddedTreeElement();
+        if (treeElement)
+            treeElement.startEditing();
+    },
+
+    updateExpression: function(element, value)
+    {
+        this.watchExpressions[element.property.watchIndex] = value;
+        this.saveExpressions();
+        this.update();
+    },
+
+    findAddedTreeElement: function()
+    {
+        var children = this.propertiesTreeOutline.children;
+        for (var i = 0; i < children.length; ++i)
+            if (children[i].property.name === WebInspector.WatchExpressionsSection.NewWatchExpression)
+                return children[i];
+    },
+
+    loadSavedExpressions: function()
+    {
+        var json = InspectorController.setting("watchExpressions");
+        if (!json)
+            return [];
+
+        try {
+            json = JSON.parse(json);
+        } catch(e) {
+            return [];
+        }
+
+        return json.expressions || [];
+    },
+
+    saveExpressions: function()
+    {
+        var toSave = [];
+        for (var i = 0; i < this.watchExpressions.length; i++)
+            if (this.watchExpressions[i])
+                toSave.push(this.watchExpressions[i]);
+
+        var json = JSON.stringify({expressions: toSave});
+        InspectorController.setSetting("watchExpressions", json);
+
+        return toSave.length;
+    }
+}
+
+WebInspector.WatchExpressionsSection.prototype.__proto__ = WebInspector.ObjectPropertiesSection.prototype;
+
+WebInspector.WatchExpressionsSection.CompareProperties = function(propertyA, propertyB) 
+{
+    if (propertyA.watchIndex == propertyB.watchIndex)
+        return 0;
+    else if (propertyA.watchIndex < propertyB.watchIndex)
+        return -1;
+    else
+        return 1;
+}
+
+WebInspector.WatchExpressionTreeElement = function(property)
+{
+    WebInspector.ObjectPropertyTreeElement.call(this, property);
+}
+
+WebInspector.WatchExpressionTreeElement.prototype = {
+    update: function()
+    {
+        WebInspector.ObjectPropertyTreeElement.prototype.update.call(this);
+
+        var deleteButton = document.createElement("input");
+        deleteButton.type = "button";
+        deleteButton.title = WebInspector.UIString("Delete watch expression.");
+        deleteButton.addStyleClass("enabled-button");
+        deleteButton.addStyleClass("delete-button");
+        deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false);
+
+        this.listItemElement.insertBefore(deleteButton, this.listItemElement.firstChild);
+    },
+
+    _deleteButtonClicked: function()
+    {
+        this.treeOutline.section.updateExpression(this, null);
+    },
+
+    startEditing: function()
+    {
+        if (WebInspector.isBeingEdited(this.nameElement) || !this.treeOutline.section.editable)
+            return;
+
+        this.nameElement.textContent = this.property.name.trimWhitespace();
+
+        var context = { expanded: this.expanded };
+
+        // collapse temporarily, if required
+        this.hasChildren = false;
+
+        this.listItemElement.addStyleClass("editing-sub-part");
+
+        WebInspector.startEditing(this.nameElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
+    },
+
+    editingCancelled: function(element, context)
+    {
+        if (!this.nameElement.textContent)
+            this.treeOutline.section.updateExpression(this, null);
+            
+        this.update();
+        this.editingEnded(context);
+    },
+
+    applyExpression: function(expression, updateInterface)
+    {
+        expression = expression.trimWhitespace();
+
+        if (!expression)
+            expression = null;
+
+        this.property.name = expression;
+        this.treeOutline.section.updateExpression(this, expression);
+    }
+}
+
+WebInspector.WatchExpressionTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/base.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/base.js
new file mode 100644
index 0000000..1a76aee
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/base.js
@@ -0,0 +1,1015 @@
+// Copyright 2006 Google Inc.
+// 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.
+//
+// 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.
+
+// NOTE: This file has been changed from the one on doctype. The following
+// changes were made:
+// - Removed goog.globalEval because it calls eval() which is not allowed from
+//   inside v8 extensions. If we ever need to use globalEval, we will need to
+//   find a way to work around this problem.
+// - Remove Function.prototype.apply() emulation for the same reason. This one
+//   is useless anyway because V8 supports apply() natively.
+
+/**
+ * @fileoverview Bootstrap for the Google JS Library
+ */
+
+/**
+ * @define {boolean} Overridden to true by the compiler when
+ *     --mark_as_compiled is specified.
+ */
+var COMPILED = true;
+
+
+/**
+ * Base namespace for the Google JS library.  Checks to see goog is
+ * already defined in the current scope before assigning to prevent
+ * clobbering if base.js is loaded more than once.
+ */
+var goog = {}; // Check to see if already defined in current scope
+
+
+/**
+ * Reference to the global context.  In most cases this will be 'window'.
+ */
+goog.global = this;
+
+
+/**
+ * Indicates whether or not we can call 'eval' directly to eval code in the
+ * global scope. Set to a Boolean by the first call to goog.globalEval (which
+ * empirically tests whether eval works for globals). @see goog.globalEval
+ * @type {boolean?}
+ * @private
+ */
+goog.evalWorksForGlobals_ = null;
+
+
+/**
+ * Creates object stubs for a namespace. When present in a file, goog.provide
+ * also indicates that the file defines the indicated object.
+ * @param {string} name name of the object that this file defines.
+ */
+goog.provide = function(name) {
+  if (!COMPILED) {
+    // Ensure that the same namespace isn't provided twice. This is intended
+    // to teach new developers that 'goog.provide' is effectively a variable
+    // declaration. And when JSCompiler transforms goog.provide into a real
+    // variable declaration, the compiled JS should work the same as the raw
+    // JS--even when the raw JS uses goog.provide incorrectly.
+    if (goog.getObjectByName(name) && !goog.implicitNamespaces_[name]) {
+      throw 'Namespace "' + name + '" already declared.';
+    }
+
+    var namespace = name;
+    while ((namespace = namespace.substr(0, namespace.lastIndexOf('.')))) {
+      goog.implicitNamespaces_[namespace] = true;
+    }
+  }
+
+  goog.exportPath_(name);
+};
+
+
+if (!COMPILED) {
+  /**
+   * Namespaces implicitly defined by goog.provide. For example,
+   * goog.provide('goog.events.Event') implicitly declares
+   * that 'goog' and 'goog.events' must be namespaces.
+   *
+   * @type {Object}
+   * @private
+   */
+  goog.implicitNamespaces_ = {};
+}
+
+
+/**
+ * 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={};
+ * Used by goog.provide and goog.exportSymbol.
+ * @param {string} name name of the object that this file defines.
+ * @param {Object} opt_object the object to expose at the end of the path.
+ * @private
+ */
+goog.exportPath_ = function(name, opt_object) {
+  var parts = name.split('.');
+  var cur = goog.global;
+  var part;
+
+  // Internet Explorer exhibits strange behavior when throwing errors from
+  // methods externed in this manner.  See the testExportSymbolExceptions in
+  // base_test.html for an example.
+  if (!(parts[0] in cur) && cur.execScript) {
+    cur.execScript('var ' + parts[0]);
+  }
+
+  // Parentheses added to eliminate strict JS warning in Firefox.
+  while ((part = parts.shift())) {
+    if (!parts.length && goog.isDef(opt_object)) {
+      // last part and we have an object; use it
+      cur[part] = opt_object;
+    } else if (cur[part]) {
+      cur = cur[part];
+    } else {
+      cur = cur[part] = {};
+    }
+  }
+};
+
+
+/**
+ * Returns an object based on its fully qualified name
+ * @param {string} name The fully qualified name.
+ * @return {Object?} The object or, if not found, null.
+ */
+goog.getObjectByName = function(name) {
+  var parts = name.split('.');
+  var cur = goog.global;
+  for (var part; part = parts.shift(); ) {
+    if (cur[part]) {
+      cur = cur[part];
+    } else {
+      return null;
+    }
+  }
+  return cur;
+};
+
+
+/**
+ * Globalizes a whole namespace, such as goog or goog.lang.
+ *
+ * @param {Object} obj The namespace to globalize.
+ * @param {Object} opt_global The object to add the properties to.
+ * @deprecated Properties may be explicitly exported to the global scope, but
+ *     this should no longer be done in bulk.
+ */
+goog.globalize = function(obj, opt_global) {
+  var global = opt_global || goog.global;
+  for (var x in obj) {
+    global[x] = obj[x];
+  }
+};
+
+
+/**
+ * Adds a dependency from a file to the files it requires.
+ * @param {string} relPath The path to the js file.
+ * @param {Array} provides An array of strings with the names of the objects
+ *                         this file provides.
+ * @param {Array} requires An array of strings with the names of the objects
+ *                         this file requires.
+ */
+goog.addDependency = function(relPath, provides, requires) {
+  if (!COMPILED) {
+    var provide, require;
+    var path = relPath.replace(/\\/g, '/');
+    var deps = goog.dependencies_;
+    for (var i = 0; provide = provides[i]; i++) {
+      deps.nameToPath[provide] = path;
+      if (!(path in deps.pathToNames)) {
+        deps.pathToNames[path] = {};
+      }
+      deps.pathToNames[path][provide] = true;
+    }
+    for (var j = 0; require = requires[j]; j++) {
+      if (!(path in deps.requires)) {
+        deps.requires[path] = {};
+      }
+      deps.requires[path][require] = true;
+    }
+  }
+};
+
+
+/**
+ * Implements a system for the dynamic resolution of dependencies
+ * that works in parallel with the BUILD system.
+ * @param {string} rule Rule to include, in the form goog.package.part.
+ */
+goog.require = function(rule) {
+
+  // if the object already exists we do not need do do anything
+  if (!COMPILED) {
+    if (goog.getObjectByName(rule)) {
+      return;
+    }
+    var path = goog.getPathFromDeps_(rule);
+    if (path) {
+      goog.included_[path] = true;
+      goog.writeScripts_();
+    } else {
+      // NOTE(nicksantos): We could throw an error, but this would break
+      // legacy users that depended on this failing silently. Instead, the
+      // compiler should warn us when there are invalid goog.require calls.
+    }
+  }
+};
+
+
+/**
+ * Path for included scripts
+ * @type {string}
+ */
+goog.basePath = '';
+
+
+/**
+ * Null function used for default values of callbacks, etc.
+ * @type {Function}
+ */
+goog.nullFunction = function() {};
+
+
+/**
+ * When defining a class Foo with an abstract method bar(), you can do:
+ *
+ * Foo.prototype.bar = goog.abstractMethod
+ *
+ * Now if a subclass of Foo fails to override bar(), an error
+ * will be thrown when bar() is invoked.
+ *
+ * Note: This does not take the name of the function to override as
+ * an argument because that would make it more difficult to obfuscate
+ * our JavaScript code.
+ *
+ * @throws {Error} when invoked to indicate the method should be
+ *   overridden.
+ */
+goog.abstractMethod = function() {
+  throw Error('unimplemented abstract method');
+};
+
+
+if (!COMPILED) {
+  /**
+   * Object used to keep track of urls that have already been added. This
+   * record allows the prevention of circular dependencies.
+   * @type {Object}
+   * @private
+   */
+  goog.included_ = {};
+
+
+  /**
+   * This object is used to keep track of dependencies and other data that is
+   * used for loading scripts
+   * @private
+   * @type {Object}
+   */
+  goog.dependencies_ = {
+    pathToNames: {}, // 1 to many
+    nameToPath: {}, // 1 to 1
+    requires: {}, // 1 to many
+    visited: {}, // used when resolving dependencies to prevent us from
+                 // visiting the file twice
+    written: {} // used to keep track of script files we have written
+  };
+
+
+  /**
+   * Tries to detect the base path of the base.js script that bootstraps
+   * Google JS Library
+   * @private
+   */
+  goog.findBasePath_ = function() {
+    var doc = goog.global.document;
+    if (typeof doc == 'undefined') {
+      return;
+    }
+    if (goog.global.GOOG_BASE_PATH) {
+      goog.basePath = goog.global.GOOG_BASE_PATH;
+      return;
+    } else {
+      goog.global.GOOG_BASE_PATH = null;
+    }
+    var scripts = doc.getElementsByTagName('script');
+    for (var script, i = 0; script = scripts[i]; i++) {
+      var src = script.src;
+      var l = src.length;
+      if (src.substr(l - 7) == 'base.js') {
+        goog.basePath = src.substr(0, l - 7);
+        return;
+      }
+    }
+  };
+
+
+  /**
+   * Writes a script tag if, and only if, that script hasn't already been added
+   * to the document.  (Must be called at execution time)
+   * @param {string} src Script source.
+   * @private
+   */
+  goog.writeScriptTag_ = function(src) {
+    var doc = goog.global.document;
+    if (typeof doc != 'undefined' &&
+        !goog.dependencies_.written[src]) {
+      goog.dependencies_.written[src] = true;
+      doc.write('<script type="text/javascript" src="' +
+                src + '"></' + 'script>');
+    }
+  };
+
+
+  /**
+   * Resolves dependencies based on the dependencies added using addDependency
+   * and calls writeScriptTag_ in the correct order.
+   * @private
+   */
+  goog.writeScripts_ = function() {
+    // the scripts we need to write this time
+    var scripts = [];
+    var seenScript = {};
+    var deps = goog.dependencies_;
+
+    function visitNode(path) {
+      if (path in deps.written) {
+        return;
+      }
+
+      // we have already visited this one. We can get here if we have cyclic
+      // dependencies
+      if (path in deps.visited) {
+        if (!(path in seenScript)) {
+          seenScript[path] = true;
+          scripts.push(path);
+        }
+        return;
+      }
+
+      deps.visited[path] = true;
+
+      if (path in deps.requires) {
+        for (var requireName in deps.requires[path]) {
+          visitNode(deps.nameToPath[requireName]);
+        }
+      }
+
+      if (!(path in seenScript)) {
+        seenScript[path] = true;
+        scripts.push(path);
+      }
+    }
+
+    for (var path in goog.included_) {
+      if (!deps.written[path]) {
+        visitNode(path);
+      }
+    }
+
+    for (var i = 0; i < scripts.length; i++) {
+      if (scripts[i]) {
+        goog.writeScriptTag_(goog.basePath + scripts[i]);
+      } else {
+        throw Error('Undefined script input');
+      }
+    }
+  };
+
+
+  /**
+   * Looks at the dependency rules and tries to determine the script file that
+   * fulfills a particular rule.
+   * @param {string} rule In the form goog.namespace.Class or project.script.
+   * @return {string?} Url corresponding to the rule, or null.
+   * @private
+   */
+  goog.getPathFromDeps_ = function(rule) {
+    if (rule in goog.dependencies_.nameToPath) {
+      return goog.dependencies_.nameToPath[rule];
+    } else {
+      return null;
+    }
+  };
+
+  goog.findBasePath_();
+  goog.writeScriptTag_(goog.basePath + 'deps.js');
+}
+
+
+
+//==============================================================================
+// Language Enhancements
+//==============================================================================
+
+
+/**
+ * This is a "fixed" version of the typeof operator.  It differs from the typeof
+ * operator in such a way that null returns 'null' and arrays return 'array'.
+ * @param {*} value The value to get the type of.
+ * @return {string} The name of the type.
+ */
+goog.typeOf = function(value) {
+  var s = typeof value;
+  if (s == 'object') {
+    if (value) {
+      // We cannot use constructor == Array or instanceof Array because
+      // different frames have different Array objects. In IE6, if the iframe
+      // where the array was created is destroyed, the array loses its
+      // prototype. Then dereferencing val.splice here throws an exception, so
+      // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
+      // so that will work. In this case, this function will return false and
+      // most array functions will still work because the array is still
+      // array-like (supports length and []) even though it has lost its
+      // prototype. Custom object cannot have non enumerable length and
+      // NodeLists don't have a slice method.
+      if (typeof value.length == 'number' &&
+          typeof value.splice != 'undefined' &&
+          !goog.propertyIsEnumerable_(value, 'length')) {
+        return 'array';
+      }
+
+      // IE in cross-window calls does not correctly marshal the function type
+      // (it appears just as an object) so we cannot use just typeof val ==
+      // 'function'. However, if the object has a call property, it is a
+      // function.
+      if (typeof value.call != 'undefined') {
+        return 'function';
+      }
+    } else {
+      return 'null';
+    }
+
+  // In Safari typeof nodeList returns function.  We would like to return
+  // object for those and we can detect an invalid function by making sure that
+  // the function object has a call method
+  } else if (s == 'function' && typeof value.call == 'undefined') {
+    return 'object';
+  }
+  return s;
+};
+
+if (Object.prototype.propertyIsEnumerable) {
+  /**
+   * Safe way to test whether a property is enumarable.  It allows testing
+   * for enumarable on objects where 'propertyIsEnumerable' is overridden or
+   * does not exist (like DOM nodes in IE).
+   * @param {Object} object The object to test if the property is enumerable.
+   * @param {string} propName The property name to check for.
+   * @return {boolean} True if the property is enumarable.
+   * @private
+   */
+  goog.propertyIsEnumerable_ = function(object, propName) {
+    return Object.prototype.propertyIsEnumerable.call(object, propName);
+  };
+} else {
+  /**
+   * Safe way to test whether a property is enumarable.  It allows testing
+   * for enumarable on objects where 'propertyIsEnumerable' is overridden or
+   * does not exist (like DOM nodes in IE).
+   * @param {Object} object The object to test if the property is enumerable.
+   * @param {string} propName The property name to check for.
+   * @return {boolean} True if the property is enumarable.
+   * @private
+   */
+  goog.propertyIsEnumerable_ = function(object, propName) {
+    // KJS in Safari 2 is not ECMAScript compatible and lacks crucial methods
+    // such as propertyIsEnumerable.  We therefore use a workaround.
+    // Does anyone know a more efficient work around?
+    if (propName in object) {
+      for (var key in object) {
+        if (key == propName) {
+          return true;
+        }
+      }
+    }
+    return false;
+  };
+}
+
+/**
+ * Returns true if the specified value is not |undefined|.
+ * WARNING: Do not use this to test if an object has a property. Use the in
+ * operator instead.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is defined.
+ */
+goog.isDef = function(val) {
+  return typeof val != 'undefined';
+};
+
+
+/**
+ * Returns true if the specified value is |null|
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is null.
+ */
+goog.isNull = function(val) {
+  return val === null;
+};
+
+
+/**
+ * Returns true if the specified value is defined and not null
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is defined and not null.
+ */
+goog.isDefAndNotNull = function(val) {
+  return goog.isDef(val) && !goog.isNull(val);
+};
+
+
+/**
+ * Returns true if the specified value is an array
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArray = function(val) {
+  return goog.typeOf(val) == 'array';
+};
+
+
+/**
+ * Returns true if the object looks like an array. To qualify as array like
+ * the value needs to be either a NodeList or an object with a Number length
+ * property.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArrayLike = function(val) {
+  var type = goog.typeOf(val);
+  return type == 'array' || type == 'object' && typeof val.length == 'number';
+};
+
+
+/**
+ * Returns true if the object looks like a Date. To qualify as Date-like
+ * the value needs to be an object and have a getFullYear() function.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a like a Date.
+ */
+goog.isDateLike = function(val) {
+  return goog.isObject(val) && typeof val.getFullYear == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is a string
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a string.
+ */
+goog.isString = function(val) {
+  return typeof val == 'string';
+};
+
+
+/**
+ * Returns true if the specified value is a boolean
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is boolean.
+ */
+goog.isBoolean = function(val) {
+  return typeof val == 'boolean';
+};
+
+
+/**
+ * Returns true if the specified value is a number
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a number.
+ */
+goog.isNumber = function(val) {
+  return typeof val == 'number';
+};
+
+
+/**
+ * Returns true if the specified value is a function
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a function.
+ */
+goog.isFunction = function(val) {
+  return goog.typeOf(val) == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is an object.  This includes arrays
+ * and functions.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an object.
+ */
+goog.isObject = function(val) {
+  var type = goog.typeOf(val);
+  return type == 'object' || type == 'array' || type == 'function';
+};
+
+
+/**
+ * Adds a hash code field to an object. The hash code is unique for the
+ * given object.
+ * @param {Object} obj The object to get the hash code for.
+ * @return {number} The hash code for the object.
+ */
+goog.getHashCode = function(obj) {
+  // In IE, DOM nodes do not extend Object so they do not have this method.
+  // we need to check hasOwnProperty because the proto might have this set.
+
+  if (obj.hasOwnProperty && obj.hasOwnProperty(goog.HASH_CODE_PROPERTY_)) {
+    return obj[goog.HASH_CODE_PROPERTY_];
+  }
+  if (!obj[goog.HASH_CODE_PROPERTY_]) {
+    obj[goog.HASH_CODE_PROPERTY_] = ++goog.hashCodeCounter_;
+  }
+  return obj[goog.HASH_CODE_PROPERTY_];
+};
+
+
+/**
+ * Removes the hash code field from an object.
+ * @param {Object} obj The object to remove the field from.
+ */
+goog.removeHashCode = function(obj) {
+  // DOM nodes in IE are not instance of Object and throws exception
+  // for delete. Instead we try to use removeAttribute
+  if ('removeAttribute' in obj) {
+    obj.removeAttribute(goog.HASH_CODE_PROPERTY_);
+  }
+  /** @preserveTry */
+  try {
+    delete obj[goog.HASH_CODE_PROPERTY_];
+  } catch (ex) {
+  }
+};
+
+
+/**
+ * {String} Name for hash code property
+ * @private
+ */
+goog.HASH_CODE_PROPERTY_ = 'goog_hashCode_';
+
+
+/**
+ * @type {number} Counter for hash codes.
+ * @private
+ */
+goog.hashCodeCounter_ = 0;
+
+
+/**
+ * Clone an object/array (recursively)
+ * @param {Object} proto Object to clone.
+ * @return {Object} Clone of x;.
+ */
+goog.cloneObject = function(proto) {
+  var type = goog.typeOf(proto);
+  if (type == 'object' || type == 'array') {
+    if (proto.clone) {
+      return proto.clone();
+    }
+    var clone = type == 'array' ? [] : {};
+    for (var key in proto) {
+      clone[key] = goog.cloneObject(proto[key]);
+    }
+    return clone;
+  }
+
+  return proto;
+};
+
+
+/**
+ * Partially applies this function to a particular 'this object' and zero or
+ * more arguments. The result is a new function with some arguments of the first
+ * function pre-filled and the value of |this| 'pre-specified'.<br><br>
+ *
+ * Remaining arguments specified at call-time are appended to the pre-
+ * specified ones.<br><br>
+ *
+ * Also see: {@link #partial}.<br><br>
+ *
+ * Note that bind and partial are optimized such that repeated calls to it do
+ * not create more than one function object, so there is no additional cost for
+ * something like:<br>
+ *
+ * <pre>var g = bind(f, obj);
+ * var h = partial(g, 1, 2, 3);
+ * var k = partial(h, a, b, c);</pre>
+ *
+ * Usage:
+ * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
+ * barMethBound('arg3', 'arg4');</pre>
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {Object} self Specifies the object which |this| should point to
+ *     when the function is run. If the value is null or undefined, it will
+ *     default to the global object.
+ * @param {Object} var_args Additional arguments that are partially
+ *     applied to the function.
+ *
+ * @return {Function} A partially-applied form of the function bind() was
+ *     invoked as a method of.
+ */
+goog.bind = function(fn, self, var_args) {
+  var boundArgs = fn.boundArgs_;
+
+  if (arguments.length > 2) {
+    var args = Array.prototype.slice.call(arguments, 2);
+    if (boundArgs) {
+      args.unshift.apply(args, boundArgs);
+    }
+    boundArgs = args;
+  }
+
+  self = fn.boundSelf_ || self;
+  fn = fn.boundFn_ || fn;
+
+  var newfn;
+  var context = self || goog.global;
+
+  if (boundArgs) {
+    newfn = function() {
+      // Combine the static args and the new args into one big array
+      var args = Array.prototype.slice.call(arguments);
+      args.unshift.apply(args, boundArgs);
+      return fn.apply(context, args);
+    }
+  } else {
+    newfn = function() {
+      return fn.apply(context, arguments);
+    }
+  }
+
+  newfn.boundArgs_ = boundArgs;
+  newfn.boundSelf_ = self;
+  newfn.boundFn_ = fn;
+
+  return newfn;
+};
+
+
+/**
+ * Like bind(), except that a 'this object' is not required. Useful when the
+ * target function is already bound.
+ *
+ * Usage:
+ * var g = partial(f, arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {Object} var_args Additional arguments that are partially
+ *     applied to fn.
+ * @return {Function} A partially-applied form of the function bind() was
+ *     invoked as a method of.
+ */
+goog.partial = function(fn, var_args) {
+  var args = Array.prototype.slice.call(arguments, 1);
+  args.unshift(fn, null);
+  return goog.bind.apply(null, args);
+};
+
+
+/**
+ * Copies all the members of a source object to a target object.
+ * This is deprecated. Use goog.object.extend instead.
+ * @param {Object} target Target.
+ * @param {Object} source Source.
+ * @deprecated
+ */
+goog.mixin = function(target, source) {
+  for (var x in source) {
+    target[x] = source[x];
+  }
+
+  // For IE the for-in-loop does not contain any properties that are not
+  // enumerable on the prototype object (for example, isPrototypeOf from
+  // Object.prototype) but also it will not include 'replace' on objects that
+  // extend String and change 'replace' (not that it is common for anyone to
+  // extend anything except Object).
+};
+
+
+/**
+ * A simple wrapper for new Date().getTime().
+ *
+ * @return {number} An integer value representing the number of milliseconds
+ *     between midnight, January 1, 1970 and the current time.
+ */
+goog.now = Date.now || (function() {
+  return new Date().getTime();
+});
+
+
+/**
+ * Abstract implementation of goog.getMsg for use with localized messages
+ * @param {string} str Translatable string, places holders in the form.{$foo}
+ * @param {Object} opt_values Map of place holder name to value.
+ */
+goog.getMsg = function(str, opt_values) {
+  var values = opt_values || {};
+  for (var key in values) {
+    str = str.replace(new RegExp('\\{\\$' + key + '\\}', 'gi'), values[key]);
+  }
+  return str;
+};
+
+
+/**
+ * Exposes an unobfuscated global namespace path for the given object.
+ * Note that fields of the exported object *will* be obfuscated,
+ * unless they are exported in turn via this function or
+ * goog.exportProperty
+ *
+ * <p>Also handy for making public items that are defined in anonymous
+ * closures.
+ *
+ * ex. goog.exportSymbol('Foo', Foo);
+ *
+ * ex. goog.exportSymbol('public.path.Foo.staticFunction',
+ *                       Foo.staticFunction);
+ *     public.path.Foo.staticFunction();
+ *
+ * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
+ *                       Foo.prototype.myMethod);
+ *     new public.path.Foo().myMethod();
+ *
+ * @param {string} publicPath Unobfuscated name to export.
+ * @param {Object} object Object the name should point to.
+ */
+goog.exportSymbol = function(publicPath, object) {
+  goog.exportPath_(publicPath, object);
+};
+
+
+/**
+ * Exports a property unobfuscated into the object's namespace.
+ * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
+ * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
+ * @param {Object} object Object whose static property is being exported.
+ * @param {string} publicName Unobfuscated name to export.
+ * @param {Object} symbol Object the name should point to.
+ */
+goog.exportProperty = function(object, publicName, symbol) {
+  object[publicName] = symbol;
+};
+
+
+
+//==============================================================================
+// Extending Function
+//==============================================================================
+
+
+/**
+ * An alias to the {@link goog.bind()} global function.
+ *
+ * Usage:
+ * var g = f.bind(obj, arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Object} self Specifies the object to which |this| should point
+ *     when the function is run. If the value is null or undefined, it will
+ *     default to the global object.
+ * @param {Object} var_args Additional arguments that are partially
+ *     applied to fn.
+ * @return {Function} A partially-applied form of the Function on which bind()
+ *     was invoked as a method.
+ * @deprecated
+ */
+Function.prototype.bind = function(self, var_args) {
+  if (arguments.length > 1) {
+    var args = Array.prototype.slice.call(arguments, 1);
+    args.unshift(this, self);
+    return goog.bind.apply(null, args);
+  } else {
+    return goog.bind(this, self);
+  }
+};
+
+
+/**
+ * An alias to the {@link goog.partial()} global function.
+ *
+ * Usage:
+ * var g = f.partial(arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Object} var_args Additional arguments that are partially
+ *     applied to fn.
+ * @return {Function} A partially-applied form of the function partial() was
+ *     invoked as a method of.
+ * @deprecated
+ */
+Function.prototype.partial = function(var_args) {
+  var args = Array.prototype.slice.call(arguments);
+  args.unshift(this, null);
+  return goog.bind.apply(null, args);
+};
+
+
+/**
+ * Inherit the prototype methods from one constructor into another.
+ *
+ * Usage:
+ * <pre>
+ * function ParentClass(a, b) { }
+ * ParentClass.prototype.foo = function(a) { }
+ *
+ * function ChildClass(a, b, c) {
+ *   ParentClass.call(this, a, b);
+ * }
+ *
+ * ChildClass.inherits(ParentClass);
+ *
+ * var child = new ChildClass('a', 'b', 'see');
+ * child.foo(); // works
+ * </pre>
+ *
+ * In addition, a superclass' implementation of a method can be invoked
+ * as follows:
+ *
+ * <pre>
+ * ChildClass.prototype.foo = function(a) {
+ *   ChildClass.superClass_.foo.call(this, a);
+ *   // other code
+ * };
+ * </pre>
+ *
+ * @param {Function} parentCtor Parent class.
+ */
+Function.prototype.inherits = function(parentCtor) {
+  goog.inherits(this, parentCtor);
+};
+
+
+/**
+ * Static variant of Function.prototype.inherits.
+ * @param {Function} childCtor Child class.
+ * @param {Function} parentCtor Parent class.
+ */
+goog.inherits = function(childCtor, parentCtor) {
+  /** @constructor */
+  function tempCtor() {};
+  tempCtor.prototype = parentCtor.prototype;
+  childCtor.superClass_ = parentCtor.prototype;
+  childCtor.prototype = new tempCtor();
+  childCtor.prototype.constructor = childCtor;
+};
+
+
+/**
+ * Mixes in an object's properties and methods into the callee's prototype.
+ * Basically mixin based inheritance, thus providing an alternative method for
+ * adding properties and methods to a class' prototype.
+ *
+ * <pre>
+ * function X() {}
+ * X.mixin({
+ *   one: 1,
+ *   two: 2,
+ *   three: 3,
+ *   doit: function() { return this.one + this.two + this.three; }
+ * });
+ *
+ * function Y() { }
+ * Y.mixin(X.prototype);
+ * Y.prototype.four = 15;
+ * Y.prototype.doit2 = function() { return this.doit() + this.four; }
+ * });
+ *
+ * // or
+ *
+ * function Y() { }
+ * Y.inherits(X);
+ * Y.mixin({
+ *   one: 10,
+ *   four: 15,
+ *   doit2: function() { return this.doit() + this.four; }
+ * });
+ * </pre>
+ *
+ * @param {Object} source from which to copy properties.
+ * @see goog.mixin
+ * @deprecated
+ */
+Function.prototype.mixin = function(source) {
+  goog.mixin(this.prototype, source);
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/codemap.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/codemap.js
new file mode 100644
index 0000000..404127f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/codemap.js
@@ -0,0 +1,258 @@
+// Copyright 2009 the V8 project 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.
+
+
+// Initlialize namespaces
+var devtools = devtools || {};
+devtools.profiler = devtools.profiler || {};
+
+
+/**
+ * Constructs a mapper that maps addresses into code entries.
+ *
+ * @constructor
+ */
+devtools.profiler.CodeMap = function() {
+  /**
+   * Dynamic code entries. Used for JIT compiled code.
+   */
+  this.dynamics_ = new goog.structs.SplayTree();
+
+  /**
+   * Name generator for entries having duplicate names.
+   */
+  this.dynamicsNameGen_ = new devtools.profiler.CodeMap.NameGenerator();
+
+  /**
+   * Static code entries. Used for statically compiled code.
+   */
+  this.statics_ = new goog.structs.SplayTree();
+
+  /**
+   * Libraries entries. Used for the whole static code libraries.
+   */
+  this.libraries_ = new goog.structs.SplayTree();
+
+  /**
+   * Map of memory pages occupied with static code.
+   */
+  this.pages_ = [];
+};
+
+
+/**
+ * The number of alignment bits in a page address.
+ */
+devtools.profiler.CodeMap.PAGE_ALIGNMENT = 12;
+
+
+/**
+ * Page size in bytes.
+ */
+devtools.profiler.CodeMap.PAGE_SIZE =
+    1 << devtools.profiler.CodeMap.PAGE_ALIGNMENT;
+
+
+/**
+ * Adds a dynamic (i.e. moveable and discardable) code entry.
+ *
+ * @param {number} start The starting address.
+ * @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object.
+ */
+devtools.profiler.CodeMap.prototype.addCode = function(start, codeEntry) {
+  this.dynamics_.insert(start, codeEntry);
+};
+
+
+/**
+ * Moves a dynamic code entry. Throws an exception if there is no dynamic
+ * code entry with the specified starting address.
+ *
+ * @param {number} from The starting address of the entry being moved.
+ * @param {number} to The destination address.
+ */
+devtools.profiler.CodeMap.prototype.moveCode = function(from, to) {
+  var removedNode = this.dynamics_.remove(from);
+  this.dynamics_.insert(to, removedNode.value);
+};
+
+
+/**
+ * Discards a dynamic code entry. Throws an exception if there is no dynamic
+ * code entry with the specified starting address.
+ *
+ * @param {number} start The starting address of the entry being deleted.
+ */
+devtools.profiler.CodeMap.prototype.deleteCode = function(start) {
+  var removedNode = this.dynamics_.remove(start);
+};
+
+
+/**
+ * Adds a library entry.
+ *
+ * @param {number} start The starting address.
+ * @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object.
+ */
+devtools.profiler.CodeMap.prototype.addLibrary = function(
+    start, codeEntry) {
+  this.markPages_(start, start + codeEntry.size);
+  this.libraries_.insert(start, codeEntry);
+};
+
+
+/**
+ * Adds a static code entry.
+ *
+ * @param {number} start The starting address.
+ * @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object.
+ */
+devtools.profiler.CodeMap.prototype.addStaticCode = function(
+    start, codeEntry) {
+  this.statics_.insert(start, codeEntry);
+};
+
+
+/**
+ * @private
+ */
+devtools.profiler.CodeMap.prototype.markPages_ = function(start, end) {
+  for (var addr = start; addr <= end;
+       addr += devtools.profiler.CodeMap.PAGE_SIZE) {
+    this.pages_[addr >>> devtools.profiler.CodeMap.PAGE_ALIGNMENT] = 1;
+  }
+};
+
+
+/**
+ * @private
+ */
+devtools.profiler.CodeMap.prototype.isAddressBelongsTo_ = function(addr, node) {
+  return addr >= node.key && addr < (node.key + node.value.size);
+};
+
+
+/**
+ * @private
+ */
+devtools.profiler.CodeMap.prototype.findInTree_ = function(tree, addr) {
+  var node = tree.findGreatestLessThan(addr);
+  return node && this.isAddressBelongsTo_(addr, node) ? node.value : null;
+};
+
+
+/**
+ * Finds a code entry that contains the specified address. Both static and
+ * dynamic code entries are considered.
+ *
+ * @param {number} addr Address.
+ */
+devtools.profiler.CodeMap.prototype.findEntry = function(addr) {
+  var pageAddr = addr >>> devtools.profiler.CodeMap.PAGE_ALIGNMENT;
+  if (pageAddr in this.pages_) {
+    // Static code entries can contain "holes" of unnamed code.
+    // In this case, the whole library is assigned to this address.
+    return this.findInTree_(this.statics_, addr) ||
+        this.findInTree_(this.libraries_, addr);
+  }
+  var min = this.dynamics_.findMin();
+  var max = this.dynamics_.findMax();
+  if (max != null && addr < (max.key + max.value.size) && addr >= min.key) {
+    var dynaEntry = this.findInTree_(this.dynamics_, addr);
+    if (dynaEntry == null) return null;
+    // Dedupe entry name.
+    if (!dynaEntry.nameUpdated_) {
+      dynaEntry.name = this.dynamicsNameGen_.getName(dynaEntry.name);
+      dynaEntry.nameUpdated_ = true;
+    }
+    return dynaEntry;
+  }
+  return null;
+};
+
+
+/**
+ * Returns an array of all dynamic code entries.
+ */
+devtools.profiler.CodeMap.prototype.getAllDynamicEntries = function() {
+  return this.dynamics_.exportValues();
+};
+
+
+/**
+ * Returns an array of all static code entries.
+ */
+devtools.profiler.CodeMap.prototype.getAllStaticEntries = function() {
+  return this.statics_.exportValues();
+};
+
+
+/**
+ * Returns an array of all libraries entries.
+ */
+devtools.profiler.CodeMap.prototype.getAllLibrariesEntries = function() {
+  return this.libraries_.exportValues();
+};
+
+
+/**
+ * Creates a code entry object.
+ *
+ * @param {number} size Code entry size in bytes.
+ * @param {string} opt_name Code entry name.
+ * @constructor
+ */
+devtools.profiler.CodeMap.CodeEntry = function(size, opt_name) {
+  this.size = size;
+  this.name = opt_name || '';
+  this.nameUpdated_ = false;
+};
+
+
+devtools.profiler.CodeMap.CodeEntry.prototype.getName = function() {
+  return this.name;
+};
+
+
+devtools.profiler.CodeMap.CodeEntry.prototype.toString = function() {
+  return this.name + ': ' + this.size.toString(16);
+};
+
+
+devtools.profiler.CodeMap.NameGenerator = function() {
+  this.knownNames_ = [];
+};
+
+
+devtools.profiler.CodeMap.NameGenerator.prototype.getName = function(name) {
+  if (!(name in this.knownNames_)) {
+    this.knownNames_[name] = 0;
+    return name;
+  }
+  var count = ++this.knownNames_[name];
+  return name + ' {' + count + '}';
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/consarray.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/consarray.js
new file mode 100644
index 0000000..c67abb79
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/consarray.js
@@ -0,0 +1,93 @@
+// Copyright 2009 the V8 project 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.
+
+
+/**
+ * Constructs a ConsArray object. It is used mainly for tree traversal.
+ * In this use case we have lots of arrays that we need to iterate
+ * sequentally. The internal Array implementation is horribly slow
+ * when concatenating on large (10K items) arrays due to memory copying.
+ * That's why we avoid copying memory and insead build a linked list
+ * of arrays to iterate through.
+ *
+ * @constructor
+ */
+function ConsArray() {
+  this.tail_ = new ConsArray.Cell(null, null);
+  this.currCell_ = this.tail_;
+  this.currCellPos_ = 0;
+};
+
+
+/**
+ * Concatenates another array for iterating. Empty arrays are ignored.
+ * This operation can be safely performed during ongoing ConsArray
+ * iteration.
+ *
+ * @param {Array} arr Array to concatenate.
+ */
+ConsArray.prototype.concat = function(arr) {
+  if (arr.length > 0) {
+    this.tail_.data = arr;
+    this.tail_ = this.tail_.next = new ConsArray.Cell(null, null);
+  }
+};
+
+
+/**
+ * Whether the end of iteration is reached.
+ */
+ConsArray.prototype.atEnd = function() {
+  return this.currCell_ === null ||
+      this.currCell_.data === null ||
+      this.currCellPos_ >= this.currCell_.data.length;
+};
+
+
+/**
+ * Returns the current item, moves to the next one.
+ */
+ConsArray.prototype.next = function() {
+  var result = this.currCell_.data[this.currCellPos_++];
+  if (this.currCellPos_ >= this.currCell_.data.length) {
+    this.currCell_ = this.currCell_.next;
+    this.currCellPos_ = 0;
+  }
+  return result;
+};
+
+
+/**
+ * A cell object used for constructing a list in ConsArray.
+ *
+ * @constructor
+ */
+ConsArray.Cell = function(data, next) {
+  this.data = data;
+  this.next = next;
+};
+
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/csvparser.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/csvparser.js
new file mode 100644
index 0000000..9e58dea
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/csvparser.js
@@ -0,0 +1,98 @@
+// Copyright 2009 the V8 project 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.
+
+
+// Initlialize namespaces.
+var devtools = devtools || {};
+devtools.profiler = devtools.profiler || {};
+
+
+/**
+ * Creates a CSV lines parser.
+ */
+devtools.profiler.CsvParser = function() {
+};
+
+
+/**
+ * A regex for matching a trailing quote.
+ * @private
+ */
+devtools.profiler.CsvParser.TRAILING_QUOTE_RE_ = /\"$/;
+
+
+/**
+ * A regex for matching a double quote.
+ * @private
+ */
+devtools.profiler.CsvParser.DOUBLE_QUOTE_RE_ = /\"\"/g;
+
+
+/**
+ * Parses a line of CSV-encoded values. Returns an array of fields.
+ *
+ * @param {string} line Input line.
+ */
+devtools.profiler.CsvParser.prototype.parseLine = function(line) {
+  var insideQuotes = false;
+  var fields = [];
+  var prevPos = 0;
+  for (var i = 0, n = line.length; i < n; ++i) {
+    switch (line.charAt(i)) {
+      case ',':
+        if (!insideQuotes) {
+          fields.push(line.substring(prevPos, i));
+          prevPos = i + 1;
+        }
+        break;
+      case '"':
+        if (!insideQuotes) {
+          insideQuotes = true;
+          // Skip the leading quote.
+          prevPos++;
+        } else {
+          if (i + 1 < n && line.charAt(i + 1) != '"') {
+            insideQuotes = false;
+          } else {
+            i++;
+          }
+        }
+        break;
+    }
+  }
+  if (n > 0) {
+    fields.push(line.substring(prevPos));
+  }
+
+  for (i = 0; i < fields.length; ++i) {
+    // Eliminate trailing quotes.
+    fields[i] = fields[i].replace(devtools.profiler.CsvParser.TRAILING_QUOTE_RE_, '');
+    // Convert quoted quotes into single ones.
+    fields[i] = fields[i].replace(devtools.profiler.CsvParser.DOUBLE_QUOTE_RE_, '"');
+  }
+  return fields;
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/debugger_agent.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/debugger_agent.js
new file mode 100644
index 0000000..1d566eb
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/debugger_agent.js
@@ -0,0 +1,1490 @@
+// Copyright (c) 2009 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 communication interface to remote v8 debugger. See
+ * protocol decription at http://code.google.com/p/v8/wiki/DebuggerProtocol
+ */
+goog.provide('devtools.DebuggerAgent');
+
+
+/**
+ * @constructor
+ */
+devtools.DebuggerAgent = function() {
+  RemoteDebuggerAgent.DebuggerOutput =
+      goog.bind(this.handleDebuggerOutput_, this);
+  RemoteDebuggerAgent.SetContextId =
+      goog.bind(this.setContextId_, this);
+  RemoteDebuggerAgent.DidGetActiveProfilerModules =
+      goog.bind(this.didGetActiveProfilerModules_, this);
+  RemoteDebuggerAgent.DidGetNextLogLines =
+      goog.bind(this.didGetNextLogLines_, this);
+
+  /**
+   * Id of the inspected page global context. It is used for filtering scripts.
+   * @type {number}
+   */
+  this.contextId_ = null;
+
+  /**
+   * Mapping from script id to script info.
+   * @type {Object}
+   */
+  this.parsedScripts_ = null;
+
+  /**
+   * Mapping from the request id to the devtools.BreakpointInfo for the
+   * breakpoints whose v8 ids are not set yet. These breakpoints are waiting for
+   * 'setbreakpoint' responses to learn their ids in the v8 debugger.
+   * @see #handleSetBreakpointResponse_
+   * @type {Object}
+   */
+  this.requestNumberToBreakpointInfo_ = null;
+
+  /**
+   * Information on current stack frames.
+   * @type {Array.<devtools.CallFrame>}
+   */
+  this.callFrames_ = [];
+
+  /**
+   * Whether to stop in the debugger on the exceptions.
+   * @type {boolean}
+   */
+  this.pauseOnExceptions_ = true;
+
+  /**
+   * Mapping: request sequence number->callback.
+   * @type {Object}
+   */
+  this.requestSeqToCallback_ = null;
+
+  /**
+   * Whether the scripts list has been requested.
+   * @type {boolean}
+   */
+  this.scriptsCacheInitialized_ = false;
+
+  /**
+   * Whether the scripts list should be requested next time when context id is
+   * set.
+   * @type {boolean}
+   */
+  this.requestScriptsWhenContextIdSet_ = false;
+
+  /**
+   * Active profiler modules flags.
+   * @type {number}
+   */
+  this.activeProfilerModules_ =
+      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE;
+
+  /**
+   * Profiler processor instance.
+   * @type {devtools.profiler.Processor}
+   */
+  this.profilerProcessor_ = new devtools.profiler.Processor();
+
+  /**
+   * Container of all breakpoints set using resource URL. These breakpoints
+   * survive page reload. Breakpoints set by script id(for scripts that don't
+   * have URLs) are stored in ScriptInfo objects.
+   * @type {Object}
+   */
+  this.urlToBreakpoints_ = {};
+
+
+  /**
+   * Exception message that is shown to user while on exception break.
+   * @type {WebInspector.ConsoleMessage}
+   */
+  this.currentExceptionMessage_ = null;
+};
+
+
+/**
+ * A copy of the scope types from v8/src/mirror-delay.js
+ * @enum {number}
+ */
+devtools.DebuggerAgent.ScopeType = {
+  Global: 0,
+  Local: 1,
+  With: 2,
+  Closure: 3,
+  Catch: 4
+};
+
+
+/**
+ * A copy of enum from include/v8.h
+ * @enum {number}
+ */
+devtools.DebuggerAgent.ProfilerModules = {
+  PROFILER_MODULE_NONE: 0,
+  PROFILER_MODULE_CPU: 1,
+  PROFILER_MODULE_HEAP_STATS: 1 << 1,
+  PROFILER_MODULE_JS_CONSTRUCTORS: 1 << 2,
+  PROFILER_MODULE_HEAP_SNAPSHOT: 1 << 16
+};
+
+
+/**
+ * Resets debugger agent to its initial state.
+ */
+devtools.DebuggerAgent.prototype.reset = function() {
+  this.contextId_ = null;
+  // No need to request scripts since they all will be pushed in AfterCompile
+  // events.
+  this.requestScriptsWhenContextIdSet_ = false;
+  this.parsedScripts_ = {};
+  this.requestNumberToBreakpointInfo_ = {};
+  this.callFrames_ = [];
+  this.requestSeqToCallback_ = {};
+
+  // Profiler isn't reset because it contains no data that is
+  // specific for a particular V8 instance. All such data is
+  // managed by an agent on the Render's side.
+};
+
+
+/**
+ * Initializes scripts UI. This method is called every time Scripts panel
+ * is shown. It will send request for context id if it's not set yet.
+ */
+devtools.DebuggerAgent.prototype.initUI = function() {
+  // There can be a number of scripts from after-compile events that are
+  // pending addition into the UI.
+  for (var scriptId in this.parsedScripts_) {
+    var script = this.parsedScripts_[scriptId];
+    WebInspector.parsedScriptSource(scriptId, script.getUrl(),
+        undefined /* script source */, script.getLineOffset());
+  }
+
+  // Initialize scripts cache when Scripts panel is shown first time.
+  if (this.scriptsCacheInitialized_) {
+    return;
+  }
+  this.scriptsCacheInitialized_ = true;
+  if (this.contextId_) {
+    // We already have context id. This means that we are here from the
+    // very beginning of the page load cycle and hence will get all scripts
+    // via after-compile events. No need to request scripts for this session.
+    return;
+  }
+  // Script list should be requested only when current context id is known.
+  RemoteDebuggerAgent.GetContextId();
+  this.requestScriptsWhenContextIdSet_ = true;
+};
+
+
+/**
+ * Asynchronously requests the debugger for the script source.
+ * @param {number} scriptId Id of the script whose source should be resolved.
+ * @param {function(source:?string):void} callback Function that will be called
+ *     when the source resolution is completed. 'source' parameter will be null
+ *     if the resolution fails.
+ */
+devtools.DebuggerAgent.prototype.resolveScriptSource = function(
+    scriptId, callback) {
+  var script = this.parsedScripts_[scriptId];
+  if (!script || script.isUnresolved()) {
+    callback(null);
+    return;
+  }
+
+  var cmd = new devtools.DebugCommand('scripts', {
+    'ids': [scriptId],
+    'includeSource': true
+  });
+  devtools.DebuggerAgent.sendCommand_(cmd);
+  // Force v8 execution so that it gets to processing the requested command.
+  RemoteToolsAgent.ExecuteVoidJavaScript();
+
+  this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
+    if (msg.isSuccess()) {
+      var scriptJson = msg.getBody()[0];
+      callback(scriptJson.source);
+    } else {
+      callback(null);
+    }
+  };
+};
+
+
+/**
+ * Tells the v8 debugger to stop on as soon as possible.
+ */
+devtools.DebuggerAgent.prototype.pauseExecution = function() {
+  RemoteDebuggerAgent.DebugBreak();
+};
+
+
+/**
+ * @param {number} sourceId Id of the script fot the breakpoint.
+ * @param {number} line Number of the line for the breakpoint.
+ * @param {?string} condition The breakpoint condition.
+ */
+devtools.DebuggerAgent.prototype.addBreakpoint = function(
+    sourceId, line, condition) {
+  var script = this.parsedScripts_[sourceId];
+  if (!script) {
+    return;
+  }
+
+  line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);
+
+  var commandArguments;
+  if (script.getUrl()) {
+    var breakpoints = this.urlToBreakpoints_[script.getUrl()];
+    if (breakpoints && breakpoints[line]) {
+      return;
+    }
+    if (!breakpoints) {
+      breakpoints = {};
+      this.urlToBreakpoints_[script.getUrl()] = breakpoints;
+    }
+
+    var breakpointInfo = new devtools.BreakpointInfo(line);
+    breakpoints[line] = breakpointInfo;
+
+    commandArguments = {
+      'groupId': this.contextId_,
+      'type': 'script',
+      'target': script.getUrl(),
+      'line': line,
+      'condition': condition
+    };
+  } else {
+    var breakpointInfo = script.getBreakpointInfo(line);
+    if (breakpointInfo) {
+      return;
+    }
+
+    breakpointInfo = new devtools.BreakpointInfo(line);
+    script.addBreakpointInfo(breakpointInfo);
+
+    commandArguments = {
+      'groupId': this.contextId_,
+      'type': 'scriptId',
+      'target': sourceId,
+      'line': line,
+      'condition': condition
+    };
+  }
+
+  var cmd = new devtools.DebugCommand('setbreakpoint', commandArguments);
+
+  this.requestNumberToBreakpointInfo_[cmd.getSequenceNumber()] = breakpointInfo;
+
+  devtools.DebuggerAgent.sendCommand_(cmd);
+  // Force v8 execution so that it gets to processing the requested command.
+  // It is necessary for being able to change a breakpoint just after it
+  // has been created (since we need an existing breakpoint id for that).
+  RemoteToolsAgent.ExecuteVoidJavaScript();
+};
+
+
+/**
+ * @param {number} sourceId Id of the script for the breakpoint.
+ * @param {number} line Number of the line for the breakpoint.
+ */
+devtools.DebuggerAgent.prototype.removeBreakpoint = function(sourceId, line) {
+  var script = this.parsedScripts_[sourceId];
+  if (!script) {
+    return;
+  }
+
+  line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);
+
+  var breakpointInfo;
+  if (script.getUrl()) {
+    var breakpoints = this.urlToBreakpoints_[script.getUrl()];
+    breakpointInfo = breakpoints[line];
+    delete breakpoints[line];
+  } else {
+    breakpointInfo = script.getBreakpointInfo(line);
+    if (breakpointInfo) {
+      script.removeBreakpointInfo(breakpointInfo);
+    }
+  }
+
+  if (!breakpointInfo) {
+    return;
+  }
+
+  breakpointInfo.markAsRemoved();
+
+  var id = breakpointInfo.getV8Id();
+
+  // If we don't know id of this breakpoint in the v8 debugger we cannot send
+  // 'clearbreakpoint' request. In that case it will be removed in
+  // 'setbreakpoint' response handler when we learn the id.
+  if (id != -1) {
+    this.requestClearBreakpoint_(id);
+  }
+};
+
+
+/**
+ * @param {number} sourceId Id of the script for the breakpoint.
+ * @param {number} line Number of the line for the breakpoint.
+ * @param {?string} condition New breakpoint condition.
+ */
+devtools.DebuggerAgent.prototype.updateBreakpoint = function(
+    sourceId, line, condition) {
+  var script = this.parsedScripts_[sourceId];
+  if (!script) {
+    return;
+  }
+
+  line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);
+
+  var breakpointInfo;
+  if (script.getUrl()) {
+    var breakpoints = this.urlToBreakpoints_[script.getUrl()];
+    breakpointInfo = breakpoints[line];
+  } else {
+    breakpointInfo = script.getBreakpointInfo(line);
+  }
+
+  var id = breakpointInfo.getV8Id();
+
+  // If we don't know id of this breakpoint in the v8 debugger we cannot send
+  // the 'changebreakpoint' request.
+  if (id != -1) {
+    // TODO(apavlov): make use of the real values for 'enabled' and
+    // 'ignoreCount' when appropriate.
+    this.requestChangeBreakpoint_(id, true, condition, null);
+  }
+};
+
+
+/**
+ * Tells the v8 debugger to step into the next statement.
+ */
+devtools.DebuggerAgent.prototype.stepIntoStatement = function() {
+  this.stepCommand_('in');
+};
+
+
+/**
+ * Tells the v8 debugger to step out of current function.
+ */
+devtools.DebuggerAgent.prototype.stepOutOfFunction = function() {
+  this.stepCommand_('out');
+};
+
+
+/**
+ * Tells the v8 debugger to step over the next statement.
+ */
+devtools.DebuggerAgent.prototype.stepOverStatement = function() {
+  this.stepCommand_('next');
+};
+
+
+/**
+ * Tells the v8 debugger to continue execution after it has been stopped on a
+ * breakpoint or an exception.
+ */
+devtools.DebuggerAgent.prototype.resumeExecution = function() {
+  this.clearExceptionMessage_();
+  var cmd = new devtools.DebugCommand('continue');
+  devtools.DebuggerAgent.sendCommand_(cmd);
+};
+
+
+/**
+ * Creates exception message and schedules it for addition to the resource upon
+ * backtrace availability.
+ * @param {string} url Resource url.
+ * @param {number} line Resource line number.
+ * @param {string} message Exception text.
+ */
+devtools.DebuggerAgent.prototype.createExceptionMessage_ = function(
+    url, line, message) {
+  this.currentExceptionMessage_ = new WebInspector.ConsoleMessage(
+      WebInspector.ConsoleMessage.MessageSource.JS,
+      WebInspector.ConsoleMessage.MessageType.Log,
+      WebInspector.ConsoleMessage.MessageLevel.Error,
+      line,
+      url,
+      0 /* group level */,
+      1 /* repeat count */,
+      '[Exception] ' + message);
+};
+
+
+/**
+ * Shows pending exception message that is created with createExceptionMessage_
+ * earlier.
+ */
+devtools.DebuggerAgent.prototype.showPendingExceptionMessage_ = function() {
+  if (!this.currentExceptionMessage_) {
+    return;
+  }
+  var msg = this.currentExceptionMessage_;
+  var resource = WebInspector.resourceURLMap[msg.url];
+  if (resource) {
+    msg.resource = resource;
+    WebInspector.panels.resources.addMessageToResource(resource, msg);
+  } else {
+    this.currentExceptionMessage_ = null;
+  }
+};
+
+
+/**
+ * Clears exception message from the resource.
+ */
+devtools.DebuggerAgent.prototype.clearExceptionMessage_ = function() {
+  if (this.currentExceptionMessage_) {
+    var messageElement =
+        this.currentExceptionMessage_._resourceMessageLineElement;
+    var bubble = messageElement.parentElement;
+    bubble.removeChild(messageElement);
+    if (!bubble.firstChild) {
+      // Last message in bubble removed.
+      bubble.parentElement.removeChild(bubble);
+    }
+    this.currentExceptionMessage_ = null;
+  }
+};
+
+
+/**
+ * @return {boolean} True iff the debugger will pause execution on the
+ * exceptions.
+ */
+devtools.DebuggerAgent.prototype.pauseOnExceptions = function() {
+  return this.pauseOnExceptions_;
+};
+
+
+/**
+ * Tells whether to pause in the debugger on the exceptions or not.
+ * @param {boolean} value True iff execution should be stopped in the debugger
+ * on the exceptions.
+ */
+devtools.DebuggerAgent.prototype.setPauseOnExceptions = function(value) {
+  this.pauseOnExceptions_ = value;
+};
+
+
+/**
+ * Sends 'evaluate' request to the debugger.
+ * @param {Object} arguments Request arguments map.
+ * @param {function(devtools.DebuggerMessage)} callback Callback to be called
+ *     when response is received.
+ */
+devtools.DebuggerAgent.prototype.requestEvaluate = function(
+    arguments, callback) {
+  var cmd = new devtools.DebugCommand('evaluate', arguments);
+  devtools.DebuggerAgent.sendCommand_(cmd);
+  this.requestSeqToCallback_[cmd.getSequenceNumber()] = callback;
+};
+
+
+/**
+ * Sends 'lookup' request for each unresolved property of the object. When
+ * response is received the properties will be changed with their resolved
+ * values.
+ * @param {Object} object Object whose properties should be resolved.
+ * @param {function(devtools.DebuggerMessage)} Callback to be called when all
+ *     children are resolved.
+ * @param {boolean} noIntrinsic Whether intrinsic properties should be included.
+ */
+devtools.DebuggerAgent.prototype.resolveChildren = function(object, callback,
+                                                            noIntrinsic) {
+  if ('handle' in object) {
+    var result = [];
+    devtools.DebuggerAgent.formatObjectProperties_(object, result,
+                                                   noIntrinsic);
+    callback(result);
+  } else {
+    this.requestLookup_([object.ref], function(msg) {
+      var result = [];
+      if (msg.isSuccess()) {
+        var handleToObject = msg.getBody();
+        var resolved = handleToObject[object.ref];
+        devtools.DebuggerAgent.formatObjectProperties_(resolved, result,
+                                                       noIntrinsic);
+        callback(result);
+      } else {
+        callback([]);
+      }
+    });
+  }
+};
+
+
+/**
+ * Sends 'scope' request for the scope object to resolve its variables.
+ * @param {Object} scope Scope to be resolved.
+ * @param {function(Array.<WebInspector.ObjectPropertyProxy>)} callback
+ *     Callback to be called when all scope variables are resolved.
+ */
+devtools.DebuggerAgent.prototype.resolveScope = function(scope, callback) {
+  var cmd = new devtools.DebugCommand('scope', {
+    'frameNumber': scope.frameNumber,
+    'number': scope.index,
+    'compactFormat': true
+  });
+  devtools.DebuggerAgent.sendCommand_(cmd);
+  this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
+      var result = [];
+    if (msg.isSuccess()) {
+      var scopeObjectJson = msg.getBody().object;
+      devtools.DebuggerAgent.formatObjectProperties_(scopeObjectJson, result,
+                                                     true /* no intrinsic */);
+    }
+    callback(result);
+  };
+};
+
+
+/**
+ * Sets up callbacks that deal with profiles processing.
+ */
+devtools.DebuggerAgent.prototype.setupProfilerProcessorCallbacks = function() {
+  // A temporary icon indicating that the profile is being processed.
+  var processingIcon = new WebInspector.SidebarTreeElement(
+      'profile-sidebar-tree-item',
+      WebInspector.UIString('Processing...'),
+      '', null, false);
+  var profilesSidebar = WebInspector.panels.profiles.sidebarTree;
+
+  this.profilerProcessor_.setCallbacks(
+      function onProfileProcessingStarted() {
+        // Set visually empty string. Subtitle hiding is done via styles
+        // manipulation which doesn't play well with dynamic append / removal.
+        processingIcon.subtitle = ' ';
+        profilesSidebar.appendChild(processingIcon);
+      },
+      function onProfileProcessingStatus(ticksCount) {
+        processingIcon.subtitle =
+            WebInspector.UIString('%d ticks processed', ticksCount);
+      },
+      function onProfileProcessingFinished(profile) {
+        profilesSidebar.removeChild(processingIcon);
+        WebInspector.addProfile(profile);
+        // If no profile is currently shown, show the new one.
+        var profilesPanel = WebInspector.panels.profiles;
+        if (!profilesPanel.visibleView) {
+          profilesPanel.showProfile(profile);
+        }
+      }
+  );
+};
+
+
+/**
+ * Initializes profiling state.
+ */
+devtools.DebuggerAgent.prototype.initializeProfiling = function() {
+  this.setupProfilerProcessorCallbacks();
+  RemoteDebuggerAgent.GetActiveProfilerModules();
+};
+
+
+/**
+ * Starts profiling.
+ * @param {number} modules List of modules to enable.
+ */
+devtools.DebuggerAgent.prototype.startProfiling = function(modules) {
+  RemoteDebuggerAgent.StartProfiling(modules);
+  if (modules &
+      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT) {
+    // Active modules will not change, instead, a snapshot will be logged.
+    RemoteDebuggerAgent.GetNextLogLines();
+  } else {
+    RemoteDebuggerAgent.GetActiveProfilerModules();
+  }
+};
+
+
+/**
+ * Stops profiling.
+ */
+devtools.DebuggerAgent.prototype.stopProfiling = function(modules) {
+  RemoteDebuggerAgent.StopProfiling(modules);
+};
+
+
+/**
+ * @param{number} scriptId
+ * @return {string} Type of the context of the script with specified id.
+ */
+devtools.DebuggerAgent.prototype.getScriptContextType = function(scriptId) {
+  return this.parsedScripts_[scriptId].getContextType();
+};
+
+
+/**
+ * Removes specified breakpoint from the v8 debugger.
+ * @param {number} breakpointId Id of the breakpoint in the v8 debugger.
+ */
+devtools.DebuggerAgent.prototype.requestClearBreakpoint_ = function(
+    breakpointId) {
+  var cmd = new devtools.DebugCommand('clearbreakpoint', {
+    'breakpoint': breakpointId
+  });
+  devtools.DebuggerAgent.sendCommand_(cmd);
+};
+
+
+/**
+ * Changes breakpoint parameters in the v8 debugger.
+ * @param {number} breakpointId Id of the breakpoint in the v8 debugger.
+ * @param {boolean} enabled Whether to enable the breakpoint.
+ * @param {?string} condition New breakpoint condition.
+ * @param {number} ignoreCount New ignore count for the breakpoint.
+ */
+devtools.DebuggerAgent.prototype.requestChangeBreakpoint_ = function(
+    breakpointId, enabled, condition, ignoreCount) {
+  var cmd = new devtools.DebugCommand('changebreakpoint', {
+    'breakpoint': breakpointId,
+    'enabled': enabled,
+    'condition': condition,
+    'ignoreCount': ignoreCount
+  });
+  devtools.DebuggerAgent.sendCommand_(cmd);
+};
+
+
+/**
+ * Sends 'backtrace' request to v8.
+ */
+devtools.DebuggerAgent.prototype.requestBacktrace_ = function() {
+  var cmd = new devtools.DebugCommand('backtrace', {
+    'compactFormat':true
+  });
+  devtools.DebuggerAgent.sendCommand_(cmd);
+};
+
+
+/**
+ * Sends command to v8 debugger.
+ * @param {devtools.DebugCommand} cmd Command to execute.
+ */
+devtools.DebuggerAgent.sendCommand_ = function(cmd) {
+  RemoteDebuggerCommandExecutor.DebuggerCommand(cmd.toJSONProtocol());
+};
+
+
+/**
+ * Tells the v8 debugger to make the next execution step.
+ * @param {string} action 'in', 'out' or 'next' action.
+ */
+devtools.DebuggerAgent.prototype.stepCommand_ = function(action) {
+  this.clearExceptionMessage_();
+  var cmd = new devtools.DebugCommand('continue', {
+    'stepaction': action,
+    'stepcount': 1
+  });
+  devtools.DebuggerAgent.sendCommand_(cmd);
+};
+
+
+/**
+ * Sends 'lookup' request to v8.
+ * @param {number} handle Handle to the object to lookup.
+ */
+devtools.DebuggerAgent.prototype.requestLookup_ = function(handles, callback) {
+  var cmd = new devtools.DebugCommand('lookup', {
+    'compactFormat':true,
+    'handles': handles
+  });
+  devtools.DebuggerAgent.sendCommand_(cmd);
+  this.requestSeqToCallback_[cmd.getSequenceNumber()] = callback;
+};
+
+
+/**
+ * Sets debugger context id for scripts filtering.
+ * @param {number} contextId Id of the inspected page global context.
+ */
+devtools.DebuggerAgent.prototype.setContextId_ = function(contextId) {
+  this.contextId_ = contextId;
+
+  // If it's the first time context id is set request scripts list.
+  if (this.requestScriptsWhenContextIdSet_) {
+    this.requestScriptsWhenContextIdSet_ = false;
+    var cmd = new devtools.DebugCommand('scripts', {
+      'includeSource': false
+    });
+    devtools.DebuggerAgent.sendCommand_(cmd);
+    // Force v8 execution so that it gets to processing the requested command.
+    RemoteToolsAgent.ExecuteVoidJavaScript();
+
+    var debuggerAgent = this;
+    this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
+      // Handle the response iff the context id hasn't changed since the request
+      // was issued. Otherwise if the context id did change all up-to-date
+      // scripts will be pushed in after compile events and there is no need to
+      // handle the response.
+      if (contextId == debuggerAgent.contextId_) {
+        debuggerAgent.handleScriptsResponse_(msg);
+      }
+    };
+  }
+};
+
+
+/**
+ * Handles output sent by v8 debugger. The output is either asynchronous event
+ * or response to a previously sent request.  See protocol definitioun for more
+ * details on the output format.
+ * @param {string} output
+ */
+devtools.DebuggerAgent.prototype.handleDebuggerOutput_ = function(output) {
+  var msg;
+  try {
+    msg = new devtools.DebuggerMessage(output);
+  } catch(e) {
+    debugPrint('Failed to handle debugger reponse:\n' + e);
+    throw e;
+  }
+
+  if (msg.getType() == 'event') {
+    if (msg.getEvent() == 'break') {
+      this.handleBreakEvent_(msg);
+    } else if (msg.getEvent() == 'exception') {
+      this.handleExceptionEvent_(msg);
+    } else if (msg.getEvent() == 'afterCompile') {
+      this.handleAfterCompileEvent_(msg);
+    }
+  } else if (msg.getType() == 'response') {
+    if (msg.getCommand() == 'scripts') {
+      this.invokeCallbackForResponse_(msg);
+    } else if (msg.getCommand() == 'setbreakpoint') {
+      this.handleSetBreakpointResponse_(msg);
+    } else if (msg.getCommand() == 'clearbreakpoint') {
+      this.handleClearBreakpointResponse_(msg);
+    } else if (msg.getCommand() == 'backtrace') {
+      this.handleBacktraceResponse_(msg);
+    } else if (msg.getCommand() == 'lookup') {
+      this.invokeCallbackForResponse_(msg);
+    } else if (msg.getCommand() == 'evaluate') {
+      this.invokeCallbackForResponse_(msg);
+    } else if (msg.getCommand() == 'scope') {
+      this.invokeCallbackForResponse_(msg);
+    }
+  }
+};
+
+
+/**
+ * @param {devtools.DebuggerMessage} msg
+ */
+devtools.DebuggerAgent.prototype.handleBreakEvent_ = function(msg) {
+  // Force scrips panel to be shown first.
+  WebInspector.currentPanel = WebInspector.panels.scripts;
+
+  var body = msg.getBody();
+
+  var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(body.sourceLine);
+  this.requestBacktrace_();
+};
+
+
+/**
+ * @param {devtools.DebuggerMessage} msg
+ */
+devtools.DebuggerAgent.prototype.handleExceptionEvent_ = function(msg) {
+  // Force scrips panel to be shown first.
+  WebInspector.currentPanel = WebInspector.panels.scripts;
+
+  var body = msg.getBody();
+  if (this.pauseOnExceptions_) {
+    var body = msg.getBody();
+    var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(body.sourceLine);
+    this.createExceptionMessage_(body.script.name, line, body.exception.text);
+    this.requestBacktrace_();
+  } else {
+    this.resumeExecution();
+  }
+};
+
+
+/**
+ * @param {devtools.DebuggerMessage} msg
+ */
+devtools.DebuggerAgent.prototype.handleScriptsResponse_ = function(msg) {
+  var scripts = msg.getBody();
+  for (var i = 0; i < scripts.length; i++) {
+    var script = scripts[i];
+
+    // Skip scripts from other tabs.
+    if (!this.isScriptFromInspectedContext_(script, msg)) {
+      continue;
+    }
+
+    // We may already have received the info in an afterCompile event.
+    if (script.id in this.parsedScripts_) {
+      continue;
+    }
+    this.addScriptInfo_(script, msg);
+  }
+};
+
+
+/**
+ * @param {Object} script Json object representing script.
+ * @param {devtools.DebuggerMessage} msg Debugger response.
+ */
+devtools.DebuggerAgent.prototype.isScriptFromInspectedContext_ = function(
+    script, msg) {
+  if (!script.context) {
+    // Always ignore scripts from the utility context.
+    return false;
+  }
+  var context = msg.lookup(script.context.ref);
+  var scriptContextId = context.data;
+  if (!goog.isDef(scriptContextId)) {
+    return false; // Always ignore scripts from the utility context.
+  }
+  if (this.contextId_ === null) {
+    return true;
+  }
+  return (scriptContextId.value == this.contextId_);
+};
+
+
+/**
+ * @param {devtools.DebuggerMessage} msg
+ */
+devtools.DebuggerAgent.prototype.handleSetBreakpointResponse_ = function(msg) {
+  var requestSeq = msg.getRequestSeq();
+  var breakpointInfo = this.requestNumberToBreakpointInfo_[requestSeq];
+  if (!breakpointInfo) {
+    // TODO(yurys): handle this case
+    return;
+  }
+  delete this.requestNumberToBreakpointInfo_[requestSeq];
+  if (!msg.isSuccess()) {
+    // TODO(yurys): handle this case
+    return;
+  }
+  var idInV8 = msg.getBody().breakpoint;
+  breakpointInfo.setV8Id(idInV8);
+
+  if (breakpointInfo.isRemoved()) {
+    this.requestClearBreakpoint_(idInV8);
+  }
+};
+
+
+/**
+ * @param {devtools.DebuggerMessage} msg
+ */
+devtools.DebuggerAgent.prototype.handleAfterCompileEvent_ = function(msg) {
+  if (!this.contextId_) {
+    // Ignore scripts delta if main request has not been issued yet.
+    return;
+  }
+  var script = msg.getBody().script;
+
+  // Ignore scripts from other tabs.
+  if (!this.isScriptFromInspectedContext_(script, msg)) {
+    return;
+  }
+  this.addScriptInfo_(script, msg);
+};
+
+
+/**
+ * Handles current profiler status.
+ * @param {number} modules List of active (started) modules.
+ */
+devtools.DebuggerAgent.prototype.didGetActiveProfilerModules_ = function(
+    modules) {
+  var profModules = devtools.DebuggerAgent.ProfilerModules;
+  var profModuleNone = profModules.PROFILER_MODULE_NONE;
+  if (modules != profModuleNone &&
+      this.activeProfilerModules_ == profModuleNone) {
+    // Start to query log data.
+    RemoteDebuggerAgent.GetNextLogLines();
+  }
+  this.activeProfilerModules_ = modules;
+  // Update buttons.
+  WebInspector.setRecordingProfile(modules & profModules.PROFILER_MODULE_CPU);
+  if (modules != profModuleNone) {
+    // Monitor profiler state. It can stop itself on buffer fill-up.
+    setTimeout(
+        function() { RemoteDebuggerAgent.GetActiveProfilerModules(); }, 1000);
+  }
+};
+
+
+/**
+ * Handles a portion of a profiler log retrieved by GetNextLogLines call.
+ * @param {string} log A portion of profiler log.
+ */
+devtools.DebuggerAgent.prototype.didGetNextLogLines_ = function(log) {
+  if (log.length > 0) {
+    this.profilerProcessor_.processLogChunk(log);
+  } else if (this.activeProfilerModules_ ==
+      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE) {
+    // No new data and profiling is stopped---suspend log reading.
+    return;
+  }
+  setTimeout(function() { RemoteDebuggerAgent.GetNextLogLines(); }, 500);
+};
+
+
+/**
+ * Adds the script info to the local cache. This method assumes that the script
+ * is not in the cache yet.
+ * @param {Object} script Script json object from the debugger message.
+ * @param {devtools.DebuggerMessage} msg Debugger message containing the script
+ *     data.
+ */
+devtools.DebuggerAgent.prototype.addScriptInfo_ = function(script, msg) {
+  var context = msg.lookup(script.context.ref);
+  var contextType = context.data.type;
+  this.parsedScripts_[script.id] = new devtools.ScriptInfo(
+      script.id, script.name, script.lineOffset, contextType);
+  if (WebInspector.panels.scripts.element.parentElement) {
+    // Only report script as parsed after scripts panel has been shown.
+    WebInspector.parsedScriptSource(
+        script.id, script.name, script.source, script.lineOffset);
+  }
+};
+
+
+/**
+ * @param {devtools.DebuggerMessage} msg
+ */
+devtools.DebuggerAgent.prototype.handleClearBreakpointResponse_ = function(
+    msg) {
+  // Do nothing.
+};
+
+
+/**
+ * Handles response to 'backtrace' command.
+ * @param {devtools.DebuggerMessage} msg
+ */
+devtools.DebuggerAgent.prototype.handleBacktraceResponse_ = function(msg) {
+  var frames = msg.getBody().frames;
+  this.callFrames_ = [];
+  for (var i = 0; i <  frames.length; ++i) {
+    this.callFrames_.push(this.formatCallFrame_(frames[i]));
+  }
+  WebInspector.pausedScript(this.callFrames_);
+  this.showPendingExceptionMessage_();
+  DevToolsHost.activateWindow();
+};
+
+
+/**
+ * Evaluates code on given callframe.
+ */
+devtools.DebuggerAgent.prototype.evaluateInCallFrame = function(
+    callFrameId, code, callback) {
+  var callFrame = this.callFrames_[callFrameId];
+  callFrame.evaluate_(code, callback);
+};
+
+
+/**
+ * Handles response to a command by invoking its callback (if any).
+ * @param {devtools.DebuggerMessage} msg
+ * @return {boolean} Whether a callback for the given message was found and
+ *     excuted.
+ */
+devtools.DebuggerAgent.prototype.invokeCallbackForResponse_ = function(msg) {
+  var callback = this.requestSeqToCallback_[msg.getRequestSeq()];
+  if (!callback) {
+    // It may happend if reset was called.
+    return false;
+  }
+  delete this.requestSeqToCallback_[msg.getRequestSeq()];
+  callback(msg);
+  return true;
+};
+
+
+/**
+ * @param {Object} stackFrame Frame json object from 'backtrace' response.
+ * @return {!devtools.CallFrame} Object containing information related to the
+ *     call frame in the format expected by ScriptsPanel and its panes.
+ */
+devtools.DebuggerAgent.prototype.formatCallFrame_ = function(stackFrame) {
+  var func = stackFrame.func;
+  var sourceId = func.scriptId;
+
+  // Add service script if it does not exist.
+  var existingScript = this.parsedScripts_[sourceId];
+  if (!existingScript) {
+    this.parsedScripts_[sourceId] = new devtools.ScriptInfo(
+        sourceId, null /* name */, 0 /* line */, 'unknown' /* type */,
+        true /* unresolved */);
+    WebInspector.parsedScriptSource(sourceId, null, null, 0);
+  }
+
+  var funcName = func.name || func.inferredName || '(anonymous function)';
+  var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(stackFrame.line);
+
+  // Add basic scope chain info with scope variables.
+  var scopeChain = [];
+  var ScopeType = devtools.DebuggerAgent.ScopeType;
+  for (var i = 0; i < stackFrame.scopes.length; i++) {
+    var scope = stackFrame.scopes[i];
+    scope.frameNumber = stackFrame.index;
+    var scopeObjectProxy = new WebInspector.ObjectProxy(scope, [], 0, '', true);
+    scopeObjectProxy.isScope = true;
+    scopeObjectProxy.properties = {};  // TODO(pfeldman): Fix autocomplete.
+    switch(scope.type) {
+      case ScopeType.Global:
+        scopeObjectProxy.isDocument = true;
+        break;
+      case ScopeType.Local:
+        scopeObjectProxy.isLocal = true;
+        scopeObjectProxy.thisObject =
+            devtools.DebuggerAgent.formatObjectProxy_(stackFrame.receiver);
+        break;
+      case ScopeType.With:
+      // Catch scope is treated as a regular with scope by WebKit so we
+      // also treat it this way.
+      case ScopeType.Catch:
+        scopeObjectProxy.isWithBlock = true;
+        break;
+      case ScopeType.Closure:
+        scopeObjectProxy.isClosure = true;
+        break;
+    }
+    scopeChain.push(scopeObjectProxy);
+  }
+  return new devtools.CallFrame(stackFrame.index, 'function', funcName,
+      sourceId, line, scopeChain);
+};
+
+
+/**
+ * Collects properties for an object from the debugger response.
+ * @param {Object} object An object from the debugger protocol response.
+ * @param {Array.<WebInspector.ObjectPropertyProxy>} result An array to put the
+ *     properties into.
+ * @param {boolean} noIntrinsic Whether intrinsic properties should be
+ *     included.
+ */
+devtools.DebuggerAgent.formatObjectProperties_ = function(object, result,
+                                                          noIntrinsic) {
+  devtools.DebuggerAgent.propertiesToProxies_(object.properties, result);
+  if (noIntrinsic) {
+    return;
+  }
+
+  result.push(new WebInspector.ObjectPropertyProxy('__proto__',
+      devtools.DebuggerAgent.formatObjectProxy_(object.protoObject)));
+  result.push(new WebInspector.ObjectPropertyProxy('prototype',
+      devtools.DebuggerAgent.formatObjectProxy_(object.prototypeObject)));
+  result.push(new WebInspector.ObjectPropertyProxy('constructor',
+      devtools.DebuggerAgent.formatObjectProxy_(object.constructorFunction)));
+};
+
+
+/**
+ * For each property in 'properties' creates its proxy representative.
+ * @param {Array.<Object>} properties Receiver properties or locals array from
+ *     'backtrace' response.
+ * @param {Array.<WebInspector.ObjectPropertyProxy>} Results holder.
+ */
+devtools.DebuggerAgent.propertiesToProxies_ = function(properties, result) {
+  var map = {};
+  for (var i = 0; i < properties.length; ++i) {
+    var property = properties[i];
+    var name = String(property.name);
+    if (name in map) {
+      continue;
+    }
+    map[name] = true;
+    var value = devtools.DebuggerAgent.formatObjectProxy_(property.value);
+    var propertyProxy = new WebInspector.ObjectPropertyProxy(name, value);
+    result.push(propertyProxy);
+  }
+};
+
+
+/**
+ * @param {Object} v An object reference from the debugger response.
+ * @return {*} The value representation expected by ScriptsPanel.
+ */
+devtools.DebuggerAgent.formatObjectProxy_ = function(v) {
+  var description;
+  var hasChildren = false;
+  if (v.type == 'object') {
+    description = v.className;
+    hasChildren = true;
+  } else if (v.type == 'function') {
+    if (v.source) {
+      description = v.source;
+    } else {
+      description = 'function ' + v.name + '()';
+    }
+    hasChildren = true;
+  } else if (goog.isDef(v.value)) {
+    description = v.value;
+  } else if (v.type == 'undefined') {
+    description = 'undefined';
+  } else if (v.type == 'null') {
+    description = 'null';
+  } else {
+    description = '<unresolved ref: ' + v.ref + ', type: ' + v.type + '>';
+  }
+  var proxy = new WebInspector.ObjectProxy(v, [], 0, description, hasChildren);
+  proxy.type = v.type;
+  proxy.isV8Ref = true;
+  return proxy;
+};
+
+
+/**
+ * Converts line number from Web Inspector UI(1-based) to v8(0-based).
+ * @param {number} line Resource line number in Web Inspector UI.
+ * @return {number} The line number in v8.
+ */
+devtools.DebuggerAgent.webkitToV8LineNumber_ = function(line) {
+  return line - 1;
+};
+
+
+/**
+ * Converts line number from v8(0-based) to Web Inspector UI(1-based).
+ * @param {number} line Resource line number in v8.
+ * @return {number} The line number in Web Inspector.
+ */
+devtools.DebuggerAgent.v8ToWwebkitLineNumber_ = function(line) {
+  return line + 1;
+};
+
+
+/**
+ * @param {number} scriptId Id of the script.
+ * @param {?string} url Script resource URL if any.
+ * @param {number} lineOffset First line 0-based offset in the containing
+ *     document.
+ * @param {string} contextType Type of the script's context:
+ *     "page" - regular script from html page
+ *     "injected" - extension content script
+ * @param {bool} opt_isUnresolved If true, script will not be resolved.
+ * @constructor
+ */
+devtools.ScriptInfo = function(
+    scriptId, url, lineOffset, contextType, opt_isUnresolved) {
+  this.scriptId_ = scriptId;
+  this.lineOffset_ = lineOffset;
+  this.contextType_ = contextType;
+  this.url_ = url;
+  this.isUnresolved_ = opt_isUnresolved;
+
+  this.lineToBreakpointInfo_ = {};
+};
+
+
+/**
+ * @return {number}
+ */
+devtools.ScriptInfo.prototype.getLineOffset = function() {
+  return this.lineOffset_;
+};
+
+
+/**
+ * @return {string}
+ */
+devtools.ScriptInfo.prototype.getContextType = function() {
+  return this.contextType_;
+};
+
+
+/**
+ * @return {?string}
+ */
+devtools.ScriptInfo.prototype.getUrl = function() {
+  return this.url_;
+};
+
+
+/**
+ * @return {?bool}
+ */
+devtools.ScriptInfo.prototype.isUnresolved = function() {
+  return this.isUnresolved_;
+};
+
+
+/**
+ * @param {number} line 0-based line number in the script.
+ * @return {?devtools.BreakpointInfo} Information on a breakpoint at the
+ *     specified line in the script or undefined if there is no breakpoint at
+ *     that line.
+ */
+devtools.ScriptInfo.prototype.getBreakpointInfo = function(line) {
+  return this.lineToBreakpointInfo_[line];
+};
+
+
+/**
+ * Adds breakpoint info to the script.
+ * @param {devtools.BreakpointInfo} breakpoint
+ */
+devtools.ScriptInfo.prototype.addBreakpointInfo = function(breakpoint) {
+  this.lineToBreakpointInfo_[breakpoint.getLine()] = breakpoint;
+};
+
+
+/**
+ * @param {devtools.BreakpointInfo} breakpoint Breakpoint info to be removed.
+ */
+devtools.ScriptInfo.prototype.removeBreakpointInfo = function(breakpoint) {
+  var line = breakpoint.getLine();
+  delete this.lineToBreakpointInfo_[line];
+};
+
+
+
+/**
+ * @param {number} line Breakpoint 0-based line number in the containing script.
+ * @constructor
+ */
+devtools.BreakpointInfo = function(line) {
+  this.line_ = line;
+  this.v8id_ = -1;
+  this.removed_ = false;
+};
+
+
+/**
+ * @return {number}
+ */
+devtools.BreakpointInfo.prototype.getLine = function(n) {
+  return this.line_;
+};
+
+
+/**
+ * @return {number} Unique identifier of this breakpoint in the v8 debugger.
+ */
+devtools.BreakpointInfo.prototype.getV8Id = function(n) {
+  return this.v8id_;
+};
+
+
+/**
+ * Sets id of this breakpoint in the v8 debugger.
+ * @param {number} id
+ */
+devtools.BreakpointInfo.prototype.setV8Id = function(id) {
+  this.v8id_ = id;
+};
+
+
+/**
+ * Marks this breakpoint as removed from the  front-end.
+ */
+devtools.BreakpointInfo.prototype.markAsRemoved = function() {
+  this.removed_ = true;
+};
+
+
+/**
+ * @return {boolean} Whether this breakpoint has been removed from the
+ *     front-end.
+ */
+devtools.BreakpointInfo.prototype.isRemoved = function() {
+  return this.removed_;
+};
+
+
+/**
+ * Call stack frame data.
+ * @param {string} id CallFrame id.
+ * @param {string} type CallFrame type.
+ * @param {string} functionName CallFrame type.
+ * @param {string} sourceID Source id.
+ * @param {number} line Source line.
+ * @param {Array.<Object>} scopeChain Array of scoped objects.
+ * @construnctor
+ */
+devtools.CallFrame = function(id, type, functionName, sourceID, line,
+    scopeChain) {
+  this.id = id;
+  this.type = type;
+  this.functionName = functionName;
+  this.sourceID = sourceID;
+  this.line = line;
+  this.scopeChain = scopeChain;
+};
+
+
+/**
+ * This method issues asynchronous evaluate request, reports result to the
+ * callback.
+ * @param {string} expression An expression to be evaluated in the context of
+ *     this call frame.
+ * @param {function(Object):undefined} callback Callback to report result to.
+ */
+devtools.CallFrame.prototype.evaluate_ = function(expression, callback) {
+  devtools.tools.getDebuggerAgent().requestEvaluate({
+      'expression': expression,
+      'frame': this.id,
+      'global': false,
+      'disable_break': false,
+      'compactFormat': true
+    },
+    function(response) {
+      var result = {};
+      if (response.isSuccess()) {
+        result.value = devtools.DebuggerAgent.formatObjectProxy_(
+            response.getBody());
+      } else {
+        result.value = response.getMessage();
+        result.isException = true;
+      }
+      callback(result);
+    });
+};
+
+
+/**
+ * JSON based commands sent to v8 debugger.
+ * @param {string} command Name of the command to execute.
+ * @param {Object} opt_arguments Command-specific arguments map.
+ * @constructor
+ */
+devtools.DebugCommand = function(command, opt_arguments) {
+  this.command_ = command;
+  this.type_ = 'request';
+  this.seq_ = ++devtools.DebugCommand.nextSeq_;
+  if (opt_arguments) {
+    this.arguments_ = opt_arguments;
+  }
+};
+
+
+/**
+ * Next unique number to be used as debugger request sequence number.
+ * @type {number}
+ */
+devtools.DebugCommand.nextSeq_ = 1;
+
+
+/**
+ * @return {number}
+ */
+devtools.DebugCommand.prototype.getSequenceNumber = function() {
+  return this.seq_;
+};
+
+
+/**
+ * @return {string}
+ */
+devtools.DebugCommand.prototype.toJSONProtocol = function() {
+  var json = {
+    'seq': this.seq_,
+    'type': this.type_,
+    'command': this.command_
+  }
+  if (this.arguments_) {
+    json.arguments = this.arguments_;
+  }
+  return JSON.stringify(json);
+};
+
+
+/**
+ * JSON messages sent from v8 debugger. See protocol definition for more
+ * details: http://code.google.com/p/v8/wiki/DebuggerProtocol
+ * @param {string} msg Raw protocol packet as JSON string.
+ * @constructor
+ */
+devtools.DebuggerMessage = function(msg) {
+  this.packet_ = JSON.parse(msg);
+  this.refs_ = [];
+  if (this.packet_.refs) {
+    for (var i = 0; i < this.packet_.refs.length; i++) {
+      this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i];
+    }
+  }
+};
+
+
+/**
+ * @return {string} The packet type.
+ */
+devtools.DebuggerMessage.prototype.getType = function() {
+  return this.packet_.type;
+};
+
+
+/**
+ * @return {?string} The packet event if the message is an event.
+ */
+devtools.DebuggerMessage.prototype.getEvent = function() {
+  return this.packet_.event;
+};
+
+
+/**
+ * @return {?string} The packet command if the message is a response to a
+ *     command.
+ */
+devtools.DebuggerMessage.prototype.getCommand = function() {
+  return this.packet_.command;
+};
+
+
+/**
+ * @return {number} The packet request sequence.
+ */
+devtools.DebuggerMessage.prototype.getRequestSeq = function() {
+  return this.packet_.request_seq;
+};
+
+
+/**
+ * @return {number} Whether the v8 is running after processing the request.
+ */
+devtools.DebuggerMessage.prototype.isRunning = function() {
+  return this.packet_.running ? true : false;
+};
+
+
+/**
+ * @return {boolean} Whether the request succeeded.
+ */
+devtools.DebuggerMessage.prototype.isSuccess = function() {
+  return this.packet_.success ? true : false;
+};
+
+
+/**
+ * @return {string}
+ */
+devtools.DebuggerMessage.prototype.getMessage = function() {
+  return this.packet_.message;
+};
+
+
+/**
+ * @return {Object} Parsed message body json.
+ */
+devtools.DebuggerMessage.prototype.getBody = function() {
+  return this.packet_.body;
+};
+
+
+/**
+ * @param {number} handle Object handle.
+ * @return {?Object} Returns the object with the handle if it was sent in this
+ *    message(some objects referenced by handles may be missing in the message).
+ */
+devtools.DebuggerMessage.prototype.lookup = function(handle) {
+  return this.refs_[handle];
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.css b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.css
new file mode 100644
index 0000000..eb649c56
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.css
@@ -0,0 +1,99 @@
+#scripts-files option.injected {
+    color: rgb(70, 134, 240);
+}
+
+.data-grid table {
+    line-height: 120%;
+}
+
+body.attached #toolbar {
+    height: 34px;
+    border-top: 1px solid rgb(100, 100, 100);
+    cursor: default; /* overriden */
+    padding-left: 0;
+}
+
+
+/* Chrome theme overrides */
+#toolbar {
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(242, 247, 253)), to(rgb(223, 234, 248)));
+}
+
+
+/* Heap Profiler Styles */
+
+#heap-snapshot-views {
+    position: absolute;
+    top: 0;
+    right: 0;
+    left: 200px;
+    bottom: 0;
+}
+
+#heap-snapshot-status-bar-items {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 200px;
+    overflow: hidden;
+    border-left: 1px solid rgb(184, 184, 184);
+    margin-left: -1px;
+}
+
+.heap-snapshot-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/focusButtonGlyph.png);
+}
+
+.heap-snapshot-sidebar-tree-item .icon {
+    content: url(Images/profileIcon.png);
+}
+
+.heap-snapshot-sidebar-tree-item.small .icon {
+    content: url(Images/profileSmallIcon.png);
+}
+
+.heap-snapshot-view {
+    display: none;
+    overflow: hidden;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+
+.heap-snapshot-view.visible {
+    display: block;
+}
+
+.heap-snapshot-view .data-grid {
+    border: none;
+    max-height: 100%;
+    position: absolute;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 93px;
+}
+
+#heap-snapshot-summary {
+    position: absolute;
+    padding-top: 20px;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    height: 93px;
+    margin-left: -1px;
+    border-left: 1px solid rgb(102, 102, 102);
+    background-color: rgb(101, 111, 130);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0)));
+    background-repeat: repeat-x;
+    background-position: top;
+    text-align: center;
+    text-shadow: black 0 1px 1px;
+    white-space: nowrap;
+    color: white;
+    -webkit-background-size: 1px 6px;
+    -webkit-background-origin: padding;
+    -webkit-background-clip: padding;
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.html b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.html
new file mode 100644
index 0000000..398bbed
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.html
@@ -0,0 +1,142 @@
+<!--
+Copyright (c) 2009 The Chromium Authors. All rights reserved.
+
+This is the Chromium version of the WebInspector. This html file serves
+as a deployment descriptor and specifies which js libraries to include into the
+app. Once the "main" frontend method that is building WebInspector
+from the js building blocks is extracted, we will be able have different
+implementations of it for Chromium and WebKit. That would allow us for
+example not to create WebKit Database tab and remove corresponding js files
+from here. Longer term we would like to employ closure + basic js compilation.
+That way js libraries would know their dependencies and js compiler would be
+able to compile them into a single file. After that this HTML file will
+include single <script src='fe-compiled.js'> and will become upstreamable.
+
+Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1.  Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+2.  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.
+3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+-->
+<!DOCTYPE html>
+<html>
+<head>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8">
+    <link rel="stylesheet" type="text/css" href="inspector.css">
+    <link rel="stylesheet" type="text/css" href="devtools.css">
+    <script type="text/javascript" src="base.js"></script>
+    <script type="text/javascript" src="utilities.js"></script>
+    <script type="text/javascript" src="treeoutline.js"></script>
+    <script type="text/javascript" src="devtools_callback.js"></script>
+    <script type="text/javascript" src="debugger_agent.js"></script>
+    <script type="text/javascript" src="inspector_controller.js"></script>
+    <script type="text/javascript" src="inspector.js"></script>
+    <script type="text/javascript" src="codemap.js"></script>
+    <script type="text/javascript" src="consarray.js"></script>
+    <script type="text/javascript" src="csvparser.js"></script>
+    <script type="text/javascript" src="logreader.js"></script>
+    <script type="text/javascript" src="profile.js"></script>
+    <script type="text/javascript" src="profile_view.js"></script>
+    <script type="text/javascript" src="profiler_processor.js"></script>
+    <script type="text/javascript" src="splaytree.js"></script>
+    <script type="text/javascript" src="Object.js"></script>
+    <script type="text/javascript" src="KeyboardShortcut.js"></script>
+    <script type="text/javascript" src="TextPrompt.js"></script>
+    <script type="text/javascript" src="Placard.js"></script>
+    <script type="text/javascript" src="View.js"></script>
+    <script type="text/javascript" src="ChangesView.js"></script>
+    <script type="text/javascript" src="ConsoleView.js"></script>
+    <script type="text/javascript" src="Drawer.js"></script>
+    <script type="text/javascript" src="Resource.js"></script>
+    <script type="text/javascript" src="ResourceCategory.js"></script>
+    <script type="text/javascript" src="Database.js"></script>
+    <script type="text/javascript" src="Callback.js"></script>
+    <script type="text/javascript" src="DOMAgent.js"></script>
+    <script type="text/javascript" src="TimelineAgent.js"></script>
+    <script type="text/javascript" src="InjectedScriptAccess.js"></script>
+    <script type="text/javascript" src="inspector_controller_impl.js"></script>
+    <script type="text/javascript" src="DOMStorage.js"></script>
+    <script type="text/javascript" src="DOMStorageItemsView.js"></script>
+    <script type="text/javascript" src="DataGrid.js"></script>
+    <script type="text/javascript" src="DOMStorageDataGrid.js"></script>
+    <script type="text/javascript" src="Script.js"></script>
+    <script type="text/javascript" src="Breakpoint.js"></script>
+    <script type="text/javascript" src="SidebarPane.js"></script>
+    <script type="text/javascript" src="ElementsTreeOutline.js"></script>
+    <script type="text/javascript" src="SidebarTreeElement.js"></script>
+    <script type="text/javascript" src="PropertiesSection.js"></script>
+    <script type="text/javascript" src="ObjectPropertiesSection.js"></script>
+    <script type="text/javascript" src="ObjectProxy.js"></script>
+    <script type="text/javascript" src="BreakpointsSidebarPane.js"></script>
+    <script type="text/javascript" src="CallStackSidebarPane.js"></script>
+    <script type="text/javascript" src="ScopeChainSidebarPane.js"></script>
+    <script type="text/javascript" src="WatchExpressionsSidebarPane.js"></script>
+    <script type="text/javascript" src="MetricsSidebarPane.js"></script>
+    <script type="text/javascript" src="PropertiesSidebarPane.js"></script>
+    <script type="text/javascript" src="Color.js"></script>
+    <script type="text/javascript" src="StylesSidebarPane.js"></script>
+    <script type="text/javascript" src="Panel.js"></script>
+    <script type="text/javascript" src="PanelEnablerView.js"></script>
+    <script type="text/javascript" src="StatusBarButton.js"></script>
+    <script type="text/javascript" src="SummaryBar.js"></script>
+    <script type="text/javascript" src="ElementsPanel.js"></script>
+    <script type="text/javascript" src="ResourcesPanel.js"></script>
+    <script type="text/javascript" src="ScriptsPanel.js"></script>
+    <script type="text/javascript" src="DatabasesPanel.js"></script>
+    <script type="text/javascript" src="ProfilesPanel.js"></script>
+    <script type="text/javascript" src="ResourceView.js"></script>
+    <script type="text/javascript" src="Popup.js"></script>
+    <script type="text/javascript" src="SourceFrame.js"></script>
+    <script type="text/javascript" src="SourceView.js"></script>
+    <script type="text/javascript" src="FontView.js"></script>
+    <script type="text/javascript" src="ImageView.js"></script>
+    <script type="text/javascript" src="DatabaseTableView.js"></script>
+    <script type="text/javascript" src="DatabaseQueryView.js"></script>
+    <script type="text/javascript" src="ScriptView.js"></script>
+    <script type="text/javascript" src="ProfileView.js"></script>
+    <script type="text/javascript" src="ProfileDataGridTree.js"></script>
+    <script type="text/javascript" src="BottomUpProfileDataGridTree.js"></script>
+    <script type="text/javascript" src="TopDownProfileDataGridTree.js"></script>
+    <script type="text/javascript" src="heap_profiler_panel.js"></script>
+    <script type="text/javascript" src="devtools.js"></script>
+    <script type="text/javascript" src="devtools_host_stub.js"></script>
+    <script type="text/javascript" src="tests.js"></script>
+</head>
+<body class="detached">
+    <div id="toolbar">
+        <div class="toolbar-item hidden"></div>
+        <div class="toolbar-item flexable-space"></div>
+        <div class="toolbar-item hidden" id="search-results-matches"></div>
+        <div class="toolbar-item"><input id="search" type="search" incremental results="0"><div id="search-toolbar-label" class="toolbar-label"></div></div>
+        <div class="toolbar-item close"><button id="close-button"></button></div>
+    </div>
+    <div id="main">
+        <div id="main-panels" tabindex="0" spellcheck="false"></div>
+        <div id="main-status-bar" class="status-bar"><div id="anchored-status-bar-items"><button id="dock-status-bar-item" class="status-bar-item toggled"><div class="glyph"></div><div class="glyph shadow"></div></button><button id="console-status-bar-item" class="status-bar-item"><div class="glyph"></div><div class="glyph shadow"></div></button><button id="changes-status-bar-item" class="status-bar-item hidden"></button><div id="count-items"><div id="changes-count" class="hidden"></div><div id="error-warning-count" class="hidden"></div></div></div></div>
+    </div>
+    <div id="drawer">
+        <div id="console-view"><div id="console-messages"><div id="console-prompt" spellcheck="false"><br></div></div></div>
+        <div id="drawer-status-bar" class="status-bar"><div id="other-drawer-status-bar-items"><button id="clear-console-status-bar-item" class="status-bar-item"><div class="glyph"></div><div class="glyph shadow"></div></button><div id="console-filter" class="status-bar-item"></div></div></div>
+    </div>
+</body>
+</html>
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.js
new file mode 100644
index 0000000..5086f80
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.js
@@ -0,0 +1,392 @@
+// Copyright (c) 2009 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 Tools is a main class that wires all components of the
+ * DevTools frontend together. It is also responsible for overriding existing
+ * WebInspector functionality while it is getting upstreamed into WebCore.
+ */
+goog.provide('devtools.Tools');
+
+goog.require('devtools.DebuggerAgent');
+
+
+/**
+ * Dispatches raw message from the host.
+ * @param {string} remoteName
+ * @prama {string} methodName
+ * @param {string} param1, param2, param3 Arguments to dispatch.
+ */
+devtools$$dispatch = function(remoteName, methodName, param1, param2, param3) {
+  remoteName = 'Remote' + remoteName.substring(0, remoteName.length - 8);
+  var agent = window[remoteName];
+  if (!agent) {
+    debugPrint('No remote agent "' + remoteName + '" found.');
+    return;
+  }
+  var method = agent[methodName];
+  if (!method) {
+    debugPrint('No method "' + remoteName + '.' + methodName + '" found.');
+    return;
+  }
+  method.call(this, param1, param2, param3);
+};
+
+
+devtools.ToolsAgent = function() {
+  RemoteToolsAgent.DidExecuteUtilityFunction =
+      devtools.Callback.processCallback;
+  RemoteToolsAgent.FrameNavigate =
+      goog.bind(this.frameNavigate_, this);
+  RemoteToolsAgent.DispatchOnClient =
+      goog.bind(this.dispatchOnClient_, this);
+  this.debuggerAgent_ = new devtools.DebuggerAgent();
+};
+
+
+/**
+ * Resets tools agent to its initial state.
+ */
+devtools.ToolsAgent.prototype.reset = function() {
+  DevToolsHost.reset();
+  this.debuggerAgent_.reset();
+};
+
+
+/**
+ * @param {string} script Script exression to be evaluated in the context of the
+ *     inspected page.
+ * @param {function(Object|string, boolean):undefined} opt_callback Function to
+ *     call with the result.
+ */
+devtools.ToolsAgent.prototype.evaluateJavaScript = function(script,
+    opt_callback) {
+  InspectorController.evaluate(script, opt_callback || function() {});
+};
+
+
+/**
+ * @return {devtools.DebuggerAgent} Debugger agent instance.
+ */
+devtools.ToolsAgent.prototype.getDebuggerAgent = function() {
+  return this.debuggerAgent_;
+};
+
+
+/**
+ * @param {string} url Url frame navigated to.
+ * @see tools_agent.h
+ * @private
+ */
+devtools.ToolsAgent.prototype.frameNavigate_ = function(url) {
+  this.reset();
+  // Do not reset Profiles panel.
+  var profiles = null;
+  if ('profiles' in WebInspector.panels) {
+    profiles = WebInspector.panels['profiles'];
+    delete WebInspector.panels['profiles'];
+  }
+  WebInspector.reset();
+  if (profiles != null) {
+    WebInspector.panels['profiles'] = profiles;
+  }
+};
+
+
+/**
+ * @param {string} message Serialized call to be dispatched on WebInspector.
+ * @private
+ */
+devtools.ToolsAgent.prototype.dispatchOnClient_ = function(message) {
+  WebInspector.dispatch.apply(WebInspector, JSON.parse(message));
+};
+
+
+/**
+ * Evaluates js expression.
+ * @param {string} expr
+ */
+devtools.ToolsAgent.prototype.evaluate = function(expr) {
+  RemoteToolsAgent.evaluate(expr);
+};
+
+
+/**
+ * Enables / disables resources panel in the ui.
+ * @param {boolean} enabled New panel status.
+ */
+WebInspector.setResourcesPanelEnabled = function(enabled) {
+  InspectorController.resourceTrackingEnabled_ = enabled;
+  WebInspector.panels.resources.reset();
+};
+
+
+/**
+ * Prints string  to the inspector console or shows alert if the console doesn't
+ * exist.
+ * @param {string} text
+ */
+function debugPrint(text) {
+  var console = WebInspector.console;
+  if (console) {
+    console.addMessage(new WebInspector.ConsoleMessage(
+        WebInspector.ConsoleMessage.MessageSource.JS,
+        WebInspector.ConsoleMessage.MessageType.Log,
+        WebInspector.ConsoleMessage.MessageLevel.Log,
+        1, 'chrome://devtools/<internal>', undefined, -1, text));
+  } else {
+    alert(text);
+  }
+}
+
+
+/**
+ * Global instance of the tools agent.
+ * @type {devtools.ToolsAgent}
+ */
+devtools.tools = null;
+
+
+var context = {};  // Used by WebCore's inspector routines.
+
+///////////////////////////////////////////////////////////////////////////////
+// Here and below are overrides to existing WebInspector methods only.
+// TODO(pfeldman): Patch WebCore and upstream changes.
+var oldLoaded = WebInspector.loaded;
+WebInspector.loaded = function() {
+  devtools.tools = new devtools.ToolsAgent();
+  devtools.tools.reset();
+
+  Preferences.ignoreWhitespace = false;
+  Preferences.samplingCPUProfiler = true;
+  oldLoaded.call(this);
+
+  // Hide dock button on Mac OS.
+  // TODO(pfeldman): remove once Mac OS docking is implemented.
+  if (InspectorController.platform().indexOf('mac') == 0) {
+    document.getElementById('dock-status-bar-item').addStyleClass('hidden');
+  }
+
+  // Mute refresh action.
+  document.addEventListener("keydown", function(event) {
+    if (event.keyIdentifier == 'F5') {
+      event.preventDefault();
+    } else if (event.keyIdentifier == 'U+0052' /* 'R' */ &&
+        (event.ctrlKey || event.metaKey)) {
+      event.preventDefault();
+    }
+  }, true);
+
+  DevToolsHost.loaded();
+};
+
+
+/**
+ * This override is necessary for adding script source asynchronously.
+ * @override
+ */
+WebInspector.ScriptView.prototype.setupSourceFrameIfNeeded = function() {
+  if (!this._frameNeedsSetup) {
+    return;
+  }
+
+  this.attach();
+
+  if (this.script.source) {
+    this.didResolveScriptSource_();
+  } else {
+    var self = this;
+    devtools.tools.getDebuggerAgent().resolveScriptSource(
+        this.script.sourceID,
+        function(source) {
+          self.script.source = source ||
+              WebInspector.UIString('<source is not available>');
+          self.didResolveScriptSource_();
+        });
+  }
+};
+
+
+/**
+ * Performs source frame setup when script source is aready resolved.
+ */
+WebInspector.ScriptView.prototype.didResolveScriptSource_ = function() {
+  if (!InspectorController.addSourceToFrame(
+      "text/javascript", this.script.source, this.sourceFrame.element)) {
+    return;
+  }
+
+  delete this._frameNeedsSetup;
+
+  this.sourceFrame.addEventListener(
+      "syntax highlighting complete", this._syntaxHighlightingComplete, this);
+  this.sourceFrame.syntaxHighlightJavascript();
+};
+
+
+/**
+ * @param {string} type Type of the the property value('object' or 'function').
+ * @param {string} className Class name of the property value.
+ * @constructor
+ */
+WebInspector.UnresolvedPropertyValue = function(type, className) {
+  this.type = type;
+  this.className = className;
+};
+
+
+/**
+ * This function overrides standard searchableViews getters to perform search
+ * only in the current view (other views are loaded asynchronously, no way to
+ * search them yet).
+ */
+WebInspector.searchableViews_ = function() {
+  var views = [];
+  const visibleView = this.visibleView;
+  if (visibleView && visibleView.performSearch) {
+    views.push(visibleView);
+  }
+  return views;
+};
+
+
+/**
+ * @override
+ */
+WebInspector.ResourcesPanel.prototype.__defineGetter__(
+    'searchableViews',
+    WebInspector.searchableViews_);
+
+
+/**
+ * @override
+ */
+WebInspector.ScriptsPanel.prototype.__defineGetter__(
+    'searchableViews',
+    WebInspector.searchableViews_);
+
+
+(function() {
+  var oldShow = WebInspector.ScriptsPanel.prototype.show;
+  WebInspector.ScriptsPanel.prototype.show =  function() {
+    devtools.tools.getDebuggerAgent().initUI();
+    this.enableToggleButton.visible = false;
+    oldShow.call(this);
+  };
+})();
+
+
+(function InterceptProfilesPanelEvents() {
+  var oldShow = WebInspector.ProfilesPanel.prototype.show;
+  WebInspector.ProfilesPanel.prototype.show = function() {
+    devtools.tools.getDebuggerAgent().initializeProfiling();
+    this.enableToggleButton.visible = false;
+    oldShow.call(this);
+    // Show is called on every show event of a panel, so
+    // we only need to intercept it once.
+    WebInspector.ProfilesPanel.prototype.show = oldShow;
+  };
+})();
+
+
+/*
+ * @override
+ * TODO(mnaganov): Restore l10n when it will be agreed that it is needed.
+ */
+WebInspector.UIString = function(string) {
+  return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
+};
+
+
+// There is no clear way of setting frame title yet. So sniffing main resource
+// load.
+(function OverrideUpdateResource() {
+  var originalUpdateResource = WebInspector.updateResource;
+  WebInspector.updateResource = function(identifier, payload) {
+    originalUpdateResource.call(this, identifier, payload);
+    var resource = this.resources[identifier];
+    if (resource && resource.mainResource && resource.finished) {
+      document.title =
+          WebInspector.UIString('Developer Tools - %s', resource.url);
+    }
+  };
+})();
+
+
+// Highlight extension content scripts in the scripts list.
+(function () {
+  var original = WebInspector.ScriptsPanel.prototype._addScriptToFilesMenu;
+  WebInspector.ScriptsPanel.prototype._addScriptToFilesMenu = function(script) {
+    var result = original.apply(this, arguments);
+    var debuggerAgent = devtools.tools.getDebuggerAgent();
+    var type = debuggerAgent.getScriptContextType(script.sourceID);
+    var option = script.filesSelectOption;
+    if (type == 'injected' && option) {
+      option.addStyleClass('injected');
+    }
+    return result;
+  };
+})();
+
+
+/** Pending WebKit upstream by apavlov). Fixes iframe vs drag problem. */
+(function() {
+  var originalDragStart = WebInspector.elementDragStart;
+  WebInspector.elementDragStart = function(element) {
+    var glassPane = document.createElement("div");
+    glassPane.style.cssText =
+        'position:absolute;width:100%;height:100%;opacity:0;z-index:1';
+    glassPane.id = 'glass-pane-for-drag';
+    element.parentElement.appendChild(glassPane);
+
+    originalDragStart.apply(this, arguments);
+  };
+
+  var originalDragEnd = WebInspector.elementDragEnd;
+  WebInspector.elementDragEnd = function() {
+    originalDragEnd.apply(this, arguments);
+
+    var glassPane = document.getElementById('glass-pane-for-drag');
+    glassPane.parentElement.removeChild(glassPane);
+  };
+})();
+
+
+(function() {
+  var originalCreatePanels = WebInspector._createPanels;
+  WebInspector._createPanels = function() {
+    originalCreatePanels.apply(this, arguments);
+    this.panels.heap = new WebInspector.HeapProfilerPanel();
+  };
+})();
+
+
+(function () {
+var orig = InjectedScriptAccess.getProperties;
+InjectedScriptAccess.getProperties = function(
+    objectProxy, ignoreHasOwnProperty, callback) {
+  if (objectProxy.isScope) {
+    devtools.tools.getDebuggerAgent().resolveScope(objectProxy.objectId,
+        callback);
+  } else if (objectProxy.isV8Ref) {
+    devtools.tools.getDebuggerAgent().resolveChildren(objectProxy.objectId,
+        callback, true);
+  } else {
+    orig.apply(this, arguments);
+  }
+};
+})()
+
+
+WebInspector.resourceTrackingWasEnabled = function()
+{
+    InspectorController.resourceTrackingEnabled_ = true;
+    this.panels.resources.resourceTrackingWasEnabled();
+};
+
+WebInspector.resourceTrackingWasDisabled = function()
+{
+    InspectorController.resourceTrackingEnabled_ = false;
+    this.panels.resources.resourceTrackingWasDisabled();
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_callback.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_callback.js
new file mode 100644
index 0000000..f252861333
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_callback.js
@@ -0,0 +1,57 @@
+// Copyright (c) 2009 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 Generic callback manager.
+ */
+goog.provide('devtools.Callback');
+
+
+/**
+ * Generic callback support as a singleton object.
+ * @constructor
+ */
+devtools.Callback = function() {
+  this.lastCallbackId_ = 1;
+  this.callbacks_ = {};
+};
+
+
+/**
+ * Assigns id to a callback.
+ * @param {Function} callback Callback to assign id to.
+ * @return {number} Callback id.
+ */
+devtools.Callback.prototype.wrap = function(callback) {
+  var callbackId = this.lastCallbackId_++;
+  this.callbacks_[callbackId] = callback || function() {};
+  return callbackId;
+};
+
+
+/**
+ * Executes callback with the given id.
+ * @param {callbackId} callbackId Id of a callback to call.
+ */
+devtools.Callback.prototype.processCallback = function(callbackId,
+    opt_vararg) {
+  var args = Array.prototype.slice.call(arguments, 1);
+  var callback = this.callbacks_[callbackId];
+  callback.apply(null, args);
+  delete this.callbacks_[callbackId];
+};
+
+
+/**
+ * @type {devtools.Callback} Callback support singleton.
+ * @private
+ */
+devtools.Callback.INSTANCE_ = new devtools.Callback();
+
+devtools.Callback.wrap = goog.bind(
+    devtools.Callback.INSTANCE_.wrap,
+    devtools.Callback.INSTANCE_);
+devtools.Callback.processCallback = goog.bind(
+    devtools.Callback.INSTANCE_.processCallback,
+    devtools.Callback.INSTANCE_);
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_host_stub.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_host_stub.js
new file mode 100644
index 0000000..2f3da60
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_host_stub.js
@@ -0,0 +1,335 @@
+// Copyright (c) 2009 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 These stubs emulate backend functionality and allows
+ * DevTools frontend to function as a standalone web app.
+ */
+
+/**
+ * @constructor
+ */
+RemoteDebuggerAgentStub = function() {
+  this.activeProfilerModules_ =
+      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE;
+  this.profileLogPos_ = 0;
+  this.heapProfSample_ = 0;
+  this.heapProfLog_ = '';
+};
+
+
+RemoteDebuggerAgentStub.prototype.DebugBreak = function() {
+};
+
+
+RemoteDebuggerAgentStub.prototype.GetContextId = function() {
+  RemoteDebuggerAgent.SetContextId(3);
+};
+
+
+RemoteDebuggerAgentStub.prototype.StopProfiling = function(modules) {
+  this.activeProfilerModules_ &= ~modules;
+};
+
+
+RemoteDebuggerAgentStub.prototype.StartProfiling = function(modules) {
+  var profModules = devtools.DebuggerAgent.ProfilerModules;
+  if (modules & profModules.PROFILER_MODULE_HEAP_SNAPSHOT) {
+    if (modules & profModules.PROFILER_MODULE_HEAP_STATS) {
+      this.heapProfLog_ +=
+          'heap-sample-begin,"Heap","allocated",' +
+              (new Date()).getTime() + '\n' +
+          'heap-sample-stats,"Heap","allocated",10000,1000\n';
+      this.heapProfLog_ +=
+          'heap-sample-item,STRING_TYPE,100,1000\n' +
+          'heap-sample-item,CODE_TYPE,10,200\n' +
+          'heap-sample-item,MAP_TYPE,20,350\n';
+      var sample = RemoteDebuggerAgentStub.HeapSamples[this.heapProfSample_];
+      if (++this.heapProfSample_ == RemoteDebuggerAgentStub.HeapSamples.length)
+          this.heapProfSample_ = 0;
+      for (var obj in sample) {
+        this.heapProfLog_ +=
+            'heap-js-cons-item,"' + obj + '",' + sample[obj][0] +
+            ',' + sample[obj][1] + '\n';
+      }
+      this.heapProfLog_ +=
+          'heap-sample-end,"Heap","allocated"\n';
+    }
+  } else {
+    this.activeProfilerModules_ |= modules;
+  }
+};
+
+
+RemoteDebuggerAgentStub.prototype.GetActiveProfilerModules = function() {
+  var self = this;
+  setTimeout(function() {
+      RemoteDebuggerAgent.DidGetActiveProfilerModules(
+          self.activeProfilerModules_);
+  }, 100);
+};
+
+
+RemoteDebuggerAgentStub.prototype.GetNextLogLines = function() {
+  var profModules = devtools.DebuggerAgent.ProfilerModules;
+  var logLines = '';
+  if (this.activeProfilerModules_ & profModules.PROFILER_MODULE_CPU) {
+    if (this.profileLogPos_ < RemoteDebuggerAgentStub.ProfilerLogBuffer.length) {
+      this.profileLogPos_ += RemoteDebuggerAgentStub.ProfilerLogBuffer.length;
+      logLines += RemoteDebuggerAgentStub.ProfilerLogBuffer;
+    }
+  }
+  if (this.heapProfLog_) {
+    logLines += this.heapProfLog_;
+    this.heapProfLog_ = '';
+  }
+  setTimeout(function() {
+    RemoteDebuggerAgent.DidGetNextLogLines(logLines);
+  }, 100);
+};
+
+
+/**
+ * @constructor
+ */
+RemoteToolsAgentStub = function() {
+};
+
+
+RemoteToolsAgentStub.prototype.HideDOMNodeHighlight = function() {
+};
+
+
+RemoteToolsAgentStub.prototype.HighlightDOMNode = function() {
+};
+
+
+RemoteToolsAgentStub.prototype.evaluate = function(expr) {
+  window.eval(expr);
+};
+
+RemoteToolsAgentStub.prototype.EvaluateJavaScript = function(callId, script) {
+  setTimeout(function() {
+    var result = eval(script);
+    RemoteToolsAgent.DidEvaluateJavaScript(callId, result);
+  }, 0);
+};
+
+
+RemoteToolsAgentStub.prototype.ExecuteUtilityFunction = function(callId,
+    functionName, args) {
+  setTimeout(function() {
+    var result = [];
+    if (functionName == 'getProperties') {
+      result = [
+        'undefined', 'undefined_key', undefined,
+        'string', 'string_key', 'value',
+        'function', 'func', undefined,
+        'array', 'array_key', [10],
+        'object', 'object_key', undefined,
+        'boolean', 'boolean_key', true,
+        'number', 'num_key', 911,
+        'date', 'date_key', new Date() ];
+    } else if (functionName == 'getPrototypes') {
+      result = ['Proto1', 'Proto2', 'Proto3'];
+    } else if (functionName == 'getStyles') {
+      result = {
+        'computedStyle' : [0, '0px', '0px', null, null, null, ['display', false, false, '', 'none']],
+        'inlineStyle' : [1, '0px', '0px', null, null, null, ['display', false, false, '', 'none']],
+        'styleAttributes' : {
+           attr: [2, '0px', '0px', null, null, null, ['display', false, false, '', 'none']]
+        },
+        'matchedCSSRules' : [
+          { 'selector' : 'S',
+            'style' : [3, '0px', '0px', null, null, null, ['display', false, false, '', 'none']],
+            'parentStyleSheet' : { 'href' : 'http://localhost',
+                                   'ownerNodeName' : 'DIV' }
+          }
+        ]
+      };
+    } else if (functionName == 'toggleNodeStyle' ||
+        functionName == 'applyStyleText' ||
+        functionName == 'setStyleProperty') {
+      alert(functionName + '(' + args + ')');
+    } else if (functionName == 'evaluate') {
+      try {
+        result = [ window.eval(JSON.parse(args)[0]), false ];
+      } catch (e) {
+        result = [ e.toString(), true ];
+      }
+    } else if (functionName == 'InspectorController' ||
+        functionName == 'InjectedScript') {
+      // do nothing;
+    } else {
+      alert('Unexpected utility function:' + functionName);
+    }
+    RemoteToolsAgent.DidExecuteUtilityFunction(callId,
+        JSON.stringify(result), '');
+  }, 0);
+};
+
+
+RemoteToolsAgentStub.prototype.ExecuteVoidJavaScript = function() {
+};
+
+
+RemoteToolsAgentStub.prototype.SetResourceTrackingEnabled = function(enabled, always) {
+  RemoteToolsAgent.SetResourcesPanelEnabled(enabled);
+  if (enabled) {
+    WebInspector.resourceTrackingWasEnabled();
+  } else {
+    WebInspector.resourceTrackingWasDisabled();
+  }
+  addDummyResource();
+};
+
+
+RemoteDebuggerAgentStub.ProfilerLogBuffer =
+  'profiler,begin,1\n' +
+  'profiler,resume\n' +
+  'code-creation,LazyCompile,0x1000,256,"test1 http://aaa.js:1"\n' +
+  'code-creation,LazyCompile,0x2000,256,"test2 http://bbb.js:2"\n' +
+  'code-creation,LazyCompile,0x3000,256,"test3 http://ccc.js:3"\n' +
+  'tick,0x1010,0x0,3\n' +
+  'tick,0x2020,0x0,3,0x1010\n' +
+  'tick,0x2020,0x0,3,0x1010\n' +
+  'tick,0x3010,0x0,3,0x2020, 0x1010\n' +
+  'tick,0x2020,0x0,3,0x1010\n' +
+  'tick,0x2030,0x0,3,0x2020, 0x1010\n' +
+  'tick,0x2020,0x0,3,0x1010\n' +
+  'tick,0x1010,0x0,3\n' +
+  'profiler,pause\n';
+
+
+RemoteDebuggerAgentStub.HeapSamples = [
+    {foo: [1, 100], bar: [20, 2000]},
+    {foo: [2000, 200000], bar: [10, 1000]},
+    {foo: [15, 1500], bar: [15, 1500]},
+    {bar: [20, 2000]},
+    {foo: [15, 1500], bar: [15, 1500]},
+    {bar: [20, 2000], baz: [15, 1500]}
+];
+
+
+/**
+ * @constructor
+ */
+RemoteDebuggerCommandExecutorStub = function() {
+};
+
+
+RemoteDebuggerCommandExecutorStub.prototype.DebuggerCommand = function(cmd) {
+  if ('{"seq":2,"type":"request","command":"scripts","arguments":{"' +
+      'includeSource":false}}' == cmd) {
+    var response1 =
+        '{"seq":5,"request_seq":2,"type":"response","command":"scripts","' +
+        'success":true,"body":[{"handle":61,"type":"script","name":"' +
+        'http://www/~test/t.js","id":59,"lineOffset":0,"columnOffset":0,' +
+        '"lineCount":1,"sourceStart":"function fib(n) {","sourceLength":300,' +
+        '"scriptType":2,"compilationType":0,"context":{"ref":60}}],"refs":[{' +
+        '"handle":60,"type":"context","data":{"type":"page","value":3}}],' +
+        '"running":false}';
+    this.sendResponse_(response1);
+  } else if ('{"seq":3,"type":"request","command":"scripts","arguments":{' +
+             '"ids":[59],"includeSource":true}}' == cmd) {
+    this.sendResponse_(
+        '{"seq":8,"request_seq":3,"type":"response","command":"scripts",' +
+        '"success":true,"body":[{"handle":1,"type":"script","name":' +
+        '"http://www/~test/t.js","id":59,"lineOffset":0,"columnOffset":0,' +
+        '"lineCount":1,"source":"function fib(n) {return n+1;}",' +
+        '"sourceLength":244,"scriptType":2,"compilationType":0,"context":{' +
+        '"ref":0}}],"refs":[{"handle":0,"type":"context","data":{"type":' +
+        '"page","value":3}}],"running":false}');
+  } else {
+    debugPrint('Unexpected command: ' + cmd);
+  }
+};
+
+
+RemoteDebuggerCommandExecutorStub.prototype.sendResponse_ = function(response) {
+  setTimeout(function() {
+    RemoteDebuggerAgent.DebuggerOutput(response);
+  }, 0);
+};
+
+
+/**
+ * @constructor
+ */
+DevToolsHostStub = function() {
+  this.isStub = true;
+  window.domAutomationController = {
+    send: function(text) {
+        debugPrint(text);
+    }
+  };
+};
+
+
+function addDummyResource() {
+  var payload = {
+    requestHeaders : {},
+    requestURL: 'http://google.com/simple_page.html',
+    host: 'google.com',
+    path: 'simple_page.html',
+    lastPathComponent: 'simple_page.html',
+    isMainResource: true,
+    cached: false,
+    mimeType: 'text/html',
+    suggestedFilename: 'simple_page.html',
+    expectedContentLength: 10000,
+    statusCode: 200,
+    contentLength: 10000,
+    responseHeaders: {},
+    type: WebInspector.Resource.Type.Document,
+    finished: true,
+    startTime: new Date(),
+
+    didResponseChange: true,
+    didCompletionChange: true,
+    didTypeChange: true
+  };
+
+  WebInspector.addResource(1, payload);
+  WebInspector.updateResource(1, payload);
+}
+
+
+DevToolsHostStub.prototype.loaded = function() {
+  addDummyResource();
+  uiTests.runAllTests();
+};
+
+
+DevToolsHostStub.prototype.reset = function() {
+};
+
+
+DevToolsHostStub.prototype.getPlatform = function() {
+  return "windows";
+};
+
+
+DevToolsHostStub.prototype.addResourceSourceToFrame = function(
+    identifier, mimeType, element) {
+};
+
+
+DevToolsHostStub.prototype.addSourceToFrame = function(mimeType, source,
+    element) {
+};
+
+
+DevToolsHostStub.prototype.getApplicationLocale = function() {
+  return "en-US";
+};
+
+
+if (!window['DevToolsHost']) {
+  window['RemoteDebuggerAgent'] = new RemoteDebuggerAgentStub();
+  window['RemoteDebuggerCommandExecutor'] =
+      new RemoteDebuggerCommandExecutorStub();
+  window['RemoteToolsAgent'] = new RemoteToolsAgentStub();
+  window['DevToolsHost'] = new DevToolsHostStub();
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/heap_profiler_panel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/heap_profiler_panel.js
new file mode 100644
index 0000000..eb1dffad
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/heap_profiler_panel.js
@@ -0,0 +1,680 @@
+// Copyright (c) 2009 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 Heap profiler panel implementation.
+ */
+
+WebInspector.HeapProfilerPanel = function() {
+    WebInspector.Panel.call(this);
+
+    this.element.addStyleClass("heap-profiler");
+
+    this.sidebarElement = document.createElement("div");
+    this.sidebarElement.id = "heap-snapshot-sidebar";
+    this.sidebarElement.className = "sidebar";
+    this.element.appendChild(this.sidebarElement);
+
+    this.sidebarResizeElement = document.createElement("div");
+    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
+    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
+    this.element.appendChild(this.sidebarResizeElement);
+
+    this.sidebarTreeElement = document.createElement("ol");
+    this.sidebarTreeElement.className = "sidebar-tree";
+    this.sidebarElement.appendChild(this.sidebarTreeElement);
+
+    this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
+
+    this.snapshotViews = document.createElement("div");
+    this.snapshotViews.id = "heap-snapshot-views";
+    this.element.appendChild(this.snapshotViews);
+
+    this.snapshotButton = new WebInspector.StatusBarButton(WebInspector.UIString("Take heap snapshot."), "heap-snapshot-status-bar-item");
+    this.snapshotButton.addEventListener("click", this._snapshotClicked.bind(this), false);
+
+    this.snapshotViewStatusBarItemsContainer = document.createElement("div");
+    this.snapshotViewStatusBarItemsContainer.id = "heap-snapshot-status-bar-items";
+
+    this.reset();
+};
+
+WebInspector.HeapProfilerPanel.prototype = {
+    toolbarItemClass: "heap-profiler",
+
+    get toolbarItemLabel() {
+        return WebInspector.UIString("Heap");
+    },
+
+    get statusBarItems() {
+        return [this.snapshotButton.element, this.snapshotViewStatusBarItemsContainer];
+    },
+
+    show: function() {
+        WebInspector.Panel.prototype.show.call(this);
+        this._updateSidebarWidth();
+    },
+
+    reset: function() {
+        if (this._snapshots) {
+            var snapshotsLength = this._snapshots.length;
+            for (var i = 0; i < snapshotsLength; ++i) {
+                var snapshot = this._snapshots[i];
+                delete snapshot._snapshotView;
+            }
+        }
+
+        this._snapshots = [];
+
+        this.sidebarTreeElement.removeStyleClass("some-expandable");
+
+        this.sidebarTree.removeChildren();
+        this.snapshotViews.removeChildren();
+
+        this.snapshotViewStatusBarItemsContainer.removeChildren();
+    },
+
+    handleKeyEvent: function(event) {
+        this.sidebarTree.handleKeyEvent(event);
+    },
+
+    addSnapshot: function(snapshot) {
+        this._snapshots.push(snapshot);
+        snapshot.list = this._snapshots;
+        snapshot.listIndex = this._snapshots.length - 1;
+
+        var snapshotsTreeElement = new WebInspector.HeapSnapshotSidebarTreeElement(snapshot);
+        snapshotsTreeElement.small = false;
+        snapshot._snapshotsTreeElement = snapshotsTreeElement;
+
+        this.sidebarTree.appendChild(snapshotsTreeElement);
+
+        this.dispatchEventToListeners("snapshot added");
+    },
+
+    showSnapshot: function(snapshot) {
+        if (!snapshot)
+            return;
+
+        if (this.visibleView)
+            this.visibleView.hide();
+        var view = this.snapshotViewForSnapshot(snapshot);
+        view.show(this.snapshotViews);
+        this.visibleView = view;
+
+        this.snapshotViewStatusBarItemsContainer.removeChildren();
+        var statusBarItems = view.statusBarItems;
+        for (var i = 0; i < statusBarItems.length; ++i)
+            this.snapshotViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
+    },
+
+    showView: function(view)
+    {
+        this.showSnapshot(view.snapshot);
+    },
+
+    snapshotViewForSnapshot: function(snapshot)
+    {
+        if (!snapshot)
+            return null;
+        if (!snapshot._snapshotView)
+            snapshot._snapshotView = new WebInspector.HeapSnapshotView(this, snapshot);
+        return snapshot._snapshotView;
+    },
+
+    closeVisibleView: function()
+    {
+        if (this.visibleView)
+            this.visibleView.hide();
+        delete this.visibleView;
+    },
+
+    _snapshotClicked: function() {
+        devtools.tools.getDebuggerAgent().startProfiling(
+            devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT |
+                devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_STATS |
+                devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_JS_CONSTRUCTORS);
+    },
+
+    _startSidebarDragging: function(event)
+    {
+        WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
+    },
+
+    _sidebarDragging: function(event)
+    {
+        this._updateSidebarWidth(event.pageX);
+
+        event.preventDefault();
+    },
+
+    _endSidebarDragging: function(event)
+    {
+        WebInspector.elementDragEnd(event);
+    },
+
+    _updateSidebarWidth: function(width)
+    {
+        if (this.sidebarElement.offsetWidth <= 0) {
+            // The stylesheet hasn"t loaded yet or the window is closed,
+            // so we can"t calculate what is need. Return early.
+            return;
+        }
+
+        if (!("_currentSidebarWidth" in this))
+            this._currentSidebarWidth = this.sidebarElement.offsetWidth;
+        if (typeof width === "undefined")
+            width = this._currentSidebarWidth;
+
+        width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
+        this._currentSidebarWidth = width;
+        this.sidebarElement.style.width = width + "px";
+        this.snapshotViews.style.left = width + "px";
+        this.snapshotViewStatusBarItemsContainer.style.left = width + "px";
+        this.sidebarResizeElement.style.left = (width - 3) + "px";
+    }
+};
+
+WebInspector.HeapProfilerPanel.prototype.__proto__ = WebInspector.Panel.prototype;
+
+WebInspector.HeapSnapshotView = function(parent, snapshot)
+{
+    WebInspector.View.call(this);
+
+    this.element.addStyleClass("heap-snapshot-view");
+
+    this.parent = parent;
+    this.parent.addEventListener("snapshot added", this._updateBaseOptions, this);
+
+    this.showCountAsPercent = true;
+    this.showSizeAsPercent = true;
+    this.showCountDeltaAsPercent = true;
+    this.showSizeDeltaAsPercent = true;
+
+    this.summaryBar = new WebInspector.SummaryBar(this.categories);
+    this.summaryBar.element.id = "heap-snapshot-summary";
+    this.summaryBar.calculator = new WebInspector.HeapSummaryCalculator(snapshot.used);
+    this.element.appendChild(this.summaryBar.element);
+
+    var columns = { "cons": { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
+                    "count": { title: WebInspector.UIString("Count"), width: "54px", sortable: true },
+                    "size": { title: WebInspector.UIString("Size"), width: "72px", sort: "descending", sortable: true },
+                    "countDelta": { title: WebInspector.UIString("\xb1 Count"), width: "72px", sortable: true },
+                    "sizeDelta": { title: WebInspector.UIString("\xb1 Size"), width: "72px", sortable: true } };
+
+    this.dataGrid = new WebInspector.DataGrid(columns);
+    this.dataGrid.addEventListener("sorting changed", this._sortData, this);
+    this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
+    this.element.appendChild(this.dataGrid.element);
+
+    this.snapshot = snapshot;
+
+    this.baseSelectElement = document.createElement("select");
+    this.baseSelectElement.className = "status-bar-item";
+    this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
+    this._updateBaseOptions();
+    if (this.snapshot.listIndex > 0)
+        this.baseSelectElement.selectedIndex = this.snapshot.listIndex - 1;
+    else
+        this.baseSelectElement.selectedIndex = this.snapshot.listIndex;
+    this._resetDataGridList();
+
+    this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
+    this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
+
+    this.refresh();
+
+    this._updatePercentButton();
+};
+
+WebInspector.HeapSnapshotView.prototype = {
+
+    get categories()
+    {
+        return {code: {title: WebInspector.UIString("Code"), color: {r: 255, g: 121, b: 0}}, data: {title: WebInspector.UIString("Objects and Data"), color: {r: 47, g: 102, b: 236}}, other: {title: WebInspector.UIString("Other"), color: {r: 186, g: 186, b: 186}}};
+    },
+
+    get statusBarItems()
+    {
+        return [this.baseSelectElement, this.percentButton.element];
+    },
+
+    get snapshot()
+    {
+        return this._snapshot;
+    },
+
+    set snapshot(snapshot)
+    {
+        this._snapshot = snapshot;
+    },
+
+    show: function(parentElement)
+    {
+        WebInspector.View.prototype.show.call(this, parentElement);
+        this.dataGrid.updateWidths();
+    },
+
+    resize: function()
+    {
+        if (this.dataGrid)
+            this.dataGrid.updateWidths();
+    },
+
+    refresh: function()
+    {
+        this.dataGrid.removeChildren();
+
+        var children = this.snapshotDataGridList.children;
+        var count = children.length;
+        for (var index = 0; index < count; ++index)
+            this.dataGrid.appendChild(children[index]);
+
+        this._updateSummaryGraph();
+    },
+
+    refreshShowAsPercents: function()
+    {
+        this._updatePercentButton();
+        this.refreshVisibleData();
+    },
+
+    refreshVisibleData: function()
+    {
+        var child = this.dataGrid.children[0];
+        while (child) {
+            child.refresh();
+            child = child.traverseNextNode(false, null, true);
+        }
+        this._updateSummaryGraph();
+    },
+
+    _changeBase: function() {
+        if (this.baseSnapshot === this.snapshot.list[this.baseSelectElement.selectedIndex])
+            return;
+
+      this._resetDataGridList();
+      this.refresh();
+    },
+
+    _createSnapshotDataGridList: function()
+    {
+        if (this._snapshotDataGridList)
+          delete this._snapshotDataGridList;
+
+        this._snapshotDataGridList = new WebInspector.HeapSnapshotDataGridList(this, this.baseSnapshot.entries, this.snapshot.entries);
+        return this._snapshotDataGridList;
+    },
+
+    _mouseDownInDataGrid: function(event)
+    {
+        if (event.detail < 2)
+            return;
+
+        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
+        if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("size-column") && !cell.hasStyleClass("countDelta-column") && !cell.hasStyleClass("sizeDelta-column")))
+            return;
+
+        if (cell.hasStyleClass("count-column"))
+            this.showCountAsPercent = !this.showCountAsPercent;
+        else if (cell.hasStyleClass("size-column"))
+            this.showSizeAsPercent = !this.showSizeAsPercent;
+        else if (cell.hasStyleClass("countDelta-column"))
+            this.showCountDeltaAsPercent = !this.showCountDeltaAsPercent;
+        else if (cell.hasStyleClass("sizeDelta-column"))
+            this.showSizeDeltaAsPercent = !this.showSizeDeltaAsPercent;
+
+        this.refreshShowAsPercents();
+
+        event.preventDefault();
+        event.stopPropagation();
+    },
+
+    get _isShowingAsPercent()
+    {
+        return this.showCountAsPercent && this.showSizeAsPercent && this.showCountDeltaAsPercent && this.showSizeDeltaAsPercent;
+    },
+
+    _percentClicked: function(event)
+    {
+        var currentState = this._isShowingAsPercent;
+        this.showCountAsPercent = !currentState;
+        this.showSizeAsPercent = !currentState;
+        this.showCountDeltaAsPercent = !currentState;
+        this.showSizeDeltaAsPercent = !currentState;
+        this.refreshShowAsPercents();
+    },
+
+    _resetDataGridList: function()
+    {
+        this.baseSnapshot = this.snapshot.list[this.baseSelectElement.selectedIndex];
+        var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("objectsSize", false);
+        if (this.snapshotDataGridList) {
+            lastComparator = this.snapshotDataGridList.lastComparator;
+        }
+        this.snapshotDataGridList = this._createSnapshotDataGridList();
+        this.snapshotDataGridList.sort(lastComparator, true);
+    },
+
+    _sortData: function()
+    {
+        var sortAscending = this.dataGrid.sortOrder === "ascending";
+        var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
+        var sortProperty = {
+                "cons": "constructorName",
+                "count": "objectsCount",
+                "size": "objectsSize",
+                "countDelta": this.showCountDeltaAsPercent ? "objectsCountDeltaPercent" : "objectsCountDelta",
+                "sizeDelta": this.showSizeDeltaAsPercent ? "objectsSizeDeltaPercent" : "objectsSizeDelta"
+        }[sortColumnIdentifier];
+
+        this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty, sortAscending));
+
+        this.refresh();
+    },
+
+    _updateBaseOptions: function()
+    {
+        // We're assuming that snapshots can only be added.
+        if (this.baseSelectElement.length == this.snapshot.list.length)
+            return;
+
+        for (var i = this.baseSelectElement.length, n = this.snapshot.list.length; i < n; ++i) {
+            var baseOption = document.createElement("option");
+            baseOption.label = WebInspector.UIString("Compared to %s", this.snapshot.list[i].title);
+            this.baseSelectElement.appendChild(baseOption);
+        }
+    },
+
+    _updatePercentButton: function()
+    {
+        if (this._isShowingAsPercent) {
+            this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
+            this.percentButton.toggled = true;
+        } else {
+            this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
+            this.percentButton.toggled = false;
+        }
+    },
+
+    _updateSummaryGraph: function()
+    {
+        this.summaryBar.calculator.showAsPercent = this._isShowingAsPercent;
+        this.summaryBar.update(this.snapshot.lowlevels);
+    }
+};
+
+WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype;
+
+WebInspector.HeapSummaryCalculator = function(total)
+{
+    this.total = total;
+}
+
+WebInspector.HeapSummaryCalculator.prototype = {
+    computeSummaryValues: function(lowLevels)
+    {
+        function highFromLow(type) {
+            if (type == "CODE_TYPE" || type == "SHARED_FUNCTION_INFO_TYPE" || type == "SCRIPT_TYPE") return "code";
+            if (type == "STRING_TYPE" || type == "HEAP_NUMBER_TYPE" || type.match(/^JS_/) || type.match(/_ARRAY_TYPE$/)) return "data";
+            return "other";
+        }
+        var highLevels = {data: 0, code: 0, other: 0};
+        for (var item in lowLevels) {
+            var highItem = highFromLow(item);
+            highLevels[highItem] += lowLevels[item].size;
+        }
+        return {categoryValues: highLevels};
+    },
+
+    formatValue: function(value)
+    {
+        if (this.showAsPercent)
+            return WebInspector.UIString("%.2f%%", value / this.total * 100.0);
+        else
+            return Number.bytesToString(value);
+    },
+
+    get showAsPercent()
+    {
+        return this._showAsPercent;
+    },
+
+    set showAsPercent(x)
+    {
+        this._showAsPercent = x;
+    }
+}
+
+WebInspector.HeapSnapshotSidebarTreeElement = function(snapshot)
+{
+    this.snapshot = snapshot;
+    this.snapshot.title = WebInspector.UIString("Snapshot %d", this.snapshot.number);
+
+    WebInspector.SidebarTreeElement.call(this, "heap-snapshot-sidebar-tree-item", "", "", snapshot, false);
+
+    this.refreshTitles();
+};
+
+WebInspector.HeapSnapshotSidebarTreeElement.prototype = {
+    onselect: function()
+    {
+        WebInspector.panels.heap.showSnapshot(this.snapshot);
+    },
+
+    get mainTitle()
+    {
+        if (this._mainTitle)
+            return this._mainTitle;
+        return this.snapshot.title;
+    },
+
+    set mainTitle(x)
+    {
+        this._mainTitle = x;
+        this.refreshTitles();
+    },
+
+    get subtitle()
+    {
+        if (this._subTitle)
+            return this._subTitle;
+        return WebInspector.UIString("Used %s of %s (%.0f%%)", Number.bytesToString(this.snapshot.used, null, false), Number.bytesToString(this.snapshot.capacity, null, false), this.snapshot.used / this.snapshot.capacity * 100.0);
+    },
+
+    set subtitle(x)
+    {
+        this._subTitle = x;
+        this.refreshTitles();
+    }
+};
+
+WebInspector.HeapSnapshotSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
+
+WebInspector.HeapSnapshotDataGridNode = function(snapshotView, baseEntry, snapshotEntry, owningList)
+{
+    WebInspector.DataGridNode.call(this, null, false);
+
+    this.snapshotView = snapshotView;
+    this.list = owningList;
+
+    if (!snapshotEntry)
+        snapshotEntry = { cons: baseEntry.cons, count: 0, size: 0 };
+    this.constructorName = snapshotEntry.cons;
+    this.objectsCount = snapshotEntry.count;
+    this.objectsSize = snapshotEntry.size;
+
+    if (!baseEntry)
+        baseEntry = { count: 0, size: 0 };
+    this.baseObjectsCount = baseEntry.count;
+    this.objectsCountDelta = this.objectsCount - this.baseObjectsCount;
+    this.baseObjectsSize = baseEntry.size;
+    this.objectsSizeDelta = this.objectsSize - this.baseObjectsSize;
+};
+
+WebInspector.HeapSnapshotDataGridNode.prototype = {
+    get data()
+    {
+        var data = {};
+
+        data["cons"] = this.constructorName;
+
+        if (this.snapshotView.showCountAsPercent)
+            data["count"] = WebInspector.UIString("%.2f%%", this.objectsCountPercent);
+        else
+            data["count"] = this.objectsCount;
+
+        if (this.snapshotView.showSizeAsPercent)
+            data["size"] = WebInspector.UIString("%.2f%%", this.objectsSizePercent);
+        else
+            data["size"] = Number.bytesToString(this.objectsSize);
+
+        function signForDelta(delta) {
+            if (delta == 0)
+                return "";
+            if (delta > 0)
+                return "+";
+            else
+                // Math minus sign, same width as plus.
+                return "\u2212";
+        }
+
+        function showDeltaAsPercent(value) {
+            if (value === Number.POSITIVE_INFINITY)
+                return WebInspector.UIString("new");
+            else if (value === Number.NEGATIVE_INFINITY)
+                return WebInspector.UIString("deleted");
+            if (value > 1000.0)
+                return WebInspector.UIString("%s >1000%%", signForDelta(value));
+            return WebInspector.UIString("%s%.2f%%", signForDelta(value), Math.abs(value));
+        }
+
+        if (this.snapshotView.showCountDeltaAsPercent)
+            data["countDelta"] = showDeltaAsPercent(this.objectsCountDeltaPercent);
+        else
+            data["countDelta"] = WebInspector.UIString("%s%d", signForDelta(this.objectsCountDelta), Math.abs(this.objectsCountDelta));
+
+        if (this.snapshotView.showSizeDeltaAsPercent)
+            data["sizeDelta"] = showDeltaAsPercent(this.objectsSizeDeltaPercent);
+        else
+            data["sizeDelta"] = WebInspector.UIString("%s%s", signForDelta(this.objectsSizeDelta), Number.bytesToString(Math.abs(this.objectsSizeDelta)));
+
+        return data;
+    },
+
+    get objectsCountPercent()
+    {
+        return this.objectsCount / this.list.objectsCount * 100.0;
+    },
+
+    get objectsSizePercent()
+    {
+        return this.objectsSize / this.list.objectsSize * 100.0;
+    },
+
+    get objectsCountDeltaPercent()
+    {
+        if (this.baseObjectsCount > 0) {
+            if (this.objectsCount > 0)
+                return this.objectsCountDelta / this.baseObjectsCount * 100.0;
+            else
+                return Number.NEGATIVE_INFINITY;
+        } else
+            return Number.POSITIVE_INFINITY;
+    },
+
+    get objectsSizeDeltaPercent()
+    {
+        if (this.baseObjectsSize > 0) {
+            if (this.objectsSize > 0)
+                return this.objectsSizeDelta / this.baseObjectsSize * 100.0;
+            else
+                return Number.NEGATIVE_INFINITY;
+        } else
+            return Number.POSITIVE_INFINITY;
+    }
+};
+
+WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
+
+WebInspector.HeapSnapshotDataGridList = function(snapshotView, baseEntries, snapshotEntries)
+{
+    this.list = this;
+    this.snapshotView = snapshotView;
+    this.children = [];
+    this.lastComparator = null;
+    this.populateChildren(baseEntries, snapshotEntries);
+};
+
+WebInspector.HeapSnapshotDataGridList.prototype = {
+    appendChild: function(child)
+    {
+        this.insertChild(child, this.children.length);
+    },
+
+    insertChild: function(child, index)
+    {
+        this.children.splice(index, 0, child);
+    },
+
+    removeChildren: function()
+    {
+        this.children = [];
+    },
+
+    populateChildren: function(baseEntries, snapshotEntries)
+    {
+        for (var item in snapshotEntries)
+            this.appendChild(new WebInspector.HeapSnapshotDataGridNode(this.snapshotView, baseEntries[item], snapshotEntries[item], this));
+
+        for (item in baseEntries) {
+            if (!(item in snapshotEntries))
+                this.appendChild(new WebInspector.HeapSnapshotDataGridNode(this.snapshotView, baseEntries[item], null, this));
+        }
+    },
+
+    sort: function(comparator, force) {
+        if (!force && this.lastComparator === comparator)
+            return;
+
+        this.children.sort(comparator);
+        this.lastComparator = comparator;
+    },
+
+    get objectsCount() {
+        if (!this._objectsCount) {
+            this._objectsCount = 0;
+            for (var i = 0, n = this.children.length; i < n; ++i) {
+                this._objectsCount += this.children[i].objectsCount;
+            }
+        }
+        return this._objectsCount;
+    },
+
+    get objectsSize() {
+        if (!this._objectsSize) {
+            this._objectsSize = 0;
+            for (var i = 0, n = this.children.length; i < n; ++i) {
+                this._objectsSize += this.children[i].objectsSize;
+            }
+        }
+        return this._objectsSize;
+    }
+};
+
+WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}];
+
+WebInspector.HeapSnapshotDataGridList.propertyComparator = function(property, isAscending)
+{
+    var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property];
+    if (!comparator) {
+        comparator = function(lhs, rhs) {
+            var l = lhs[property], r = rhs[property];
+            var result = l < r ? -1 : (l > r ? 1 : 0);
+            return isAscending ? result : -result;
+        };
+        this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator;
+    }
+    return comparator;
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/back.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/back.png
new file mode 100644
index 0000000..9363960b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/back.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/checker.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/checker.png
new file mode 100644
index 0000000..83499084
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/checker.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/clearConsoleButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/clearConsoleButtonGlyph.png
new file mode 100644
index 0000000..b1f9465
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/clearConsoleButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/closeButtons.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/closeButtons.png
new file mode 100644
index 0000000..28158a4
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/closeButtons.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/consoleButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/consoleButtonGlyph.png
new file mode 100644
index 0000000..d10d43c
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/consoleButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/cookie.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/cookie.png
new file mode 100644
index 0000000..90c3c15
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/cookie.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/database.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/database.png
new file mode 100644
index 0000000..339efa6
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/database.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/databaseTable.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/databaseTable.png
new file mode 100644
index 0000000..3718708
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/databaseTable.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerContinue.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerContinue.png
new file mode 100644
index 0000000..d90a855
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerContinue.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerPause.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerPause.png
new file mode 100644
index 0000000..97f958ab
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerPause.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepInto.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepInto.png
new file mode 100644
index 0000000..277f126
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepInto.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOut.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOut.png
new file mode 100644
index 0000000..3032e32
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOut.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOver.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOver.png
new file mode 100644
index 0000000..7d47245
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOver.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDown.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDown.png
new file mode 100644
index 0000000..cffc835
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDown.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownBlack.png
new file mode 100644
index 0000000..4b49c13e
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownBlack.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownWhite.png
new file mode 100644
index 0000000..aebae12
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownWhite.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRight.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRight.png
new file mode 100644
index 0000000..a3102ea
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRight.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightBlack.png
new file mode 100644
index 0000000..2c458594
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightBlack.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDown.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDown.png
new file mode 100644
index 0000000..035c0692
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDown.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownBlack.png
new file mode 100644
index 0000000..86f67bd
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownBlack.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownWhite.png
new file mode 100644
index 0000000..972d7947
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownWhite.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightWhite.png
new file mode 100644
index 0000000..a10168f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightWhite.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/dockButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/dockButtonGlyph.png
new file mode 100644
index 0000000..7052f4bd
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/dockButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/elementsIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/elementsIcon.png
new file mode 100644
index 0000000..fde3db98f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/elementsIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableOutlineButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableOutlineButtonGlyph.png
new file mode 100644
index 0000000..85e0bd6
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableOutlineButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableSolidButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableSolidButtonGlyph.png
new file mode 100644
index 0000000..25b2e96a
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableSolidButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorIcon.png
new file mode 100644
index 0000000..c697263
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorMediumIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorMediumIcon.png
new file mode 100644
index 0000000..6ca32bb
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorMediumIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/excludeButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/excludeButtonGlyph.png
new file mode 100644
index 0000000..5128576
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/excludeButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/focusButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/focusButtonGlyph.png
new file mode 100644
index 0000000..b71807c6
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/focusButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/forward.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/forward.png
new file mode 100644
index 0000000..ad70f3e0
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/forward.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeader.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeader.png
new file mode 100644
index 0000000..6cbefb7
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeader.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderPressed.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderPressed.png
new file mode 100644
index 0000000..1153506
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderPressed.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelected.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelected.png
new file mode 100644
index 0000000..71d5af6
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelected.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelectedPressed.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelectedPressed.png
new file mode 100644
index 0000000..7047dbe
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelectedPressed.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/goArrow.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/goArrow.png
new file mode 100644
index 0000000..f318a56
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/goArrow.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutLeft.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutLeft.png
new file mode 100644
index 0000000..6426dbd
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutLeft.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutRight.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutRight.png
new file mode 100644
index 0000000..8c87eae
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutRight.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/largerResourcesButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/largerResourcesButtonGlyph.png
new file mode 100644
index 0000000..71256d646
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/largerResourcesButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/localStorage.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/localStorage.png
new file mode 100644
index 0000000..44a3019
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/localStorage.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/nodeSearchButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/nodeSearchButtonGlyph.png
new file mode 100644
index 0000000..faf5df2
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/nodeSearchButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrow.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrow.png
new file mode 100644
index 0000000..d55b865
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrow.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrowActive.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrowActive.png
new file mode 100644
index 0000000..ef3f259
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrowActive.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneGrowHandleLine.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneGrowHandleLine.png
new file mode 100644
index 0000000..4eaf61b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneGrowHandleLine.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/pauseOnExceptionButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/pauseOnExceptionButtonGlyph.png
new file mode 100644
index 0000000..c3cec5f8
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/pauseOnExceptionButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/percentButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/percentButtonGlyph.png
new file mode 100644
index 0000000..0ace3b7
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/percentButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileGroupIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileGroupIcon.png
new file mode 100644
index 0000000..44616d4
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileGroupIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileIcon.png
new file mode 100644
index 0000000..8008f9b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileSmallIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileSmallIcon.png
new file mode 100644
index 0000000..7935520
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileSmallIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesIcon.png
new file mode 100644
index 0000000..ecd5b04
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesSilhouette.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesSilhouette.png
new file mode 100644
index 0000000..42bb9668
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesSilhouette.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/radioDot.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/radioDot.png
new file mode 100644
index 0000000..609878f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/radioDot.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordButtonGlyph.png
new file mode 100644
index 0000000..bfdad1a
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordToggledButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordToggledButtonGlyph.png
new file mode 100644
index 0000000..2c22f87
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordToggledButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/reloadButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/reloadButtonGlyph.png
new file mode 100644
index 0000000..28e047a
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/reloadButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceCSSIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceCSSIcon.png
new file mode 100644
index 0000000..aead6a7
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceCSSIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIcon.png
new file mode 100644
index 0000000..1683a09
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIconSmall.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIconSmall.png
new file mode 100644
index 0000000..468ced9
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIconSmall.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceJSIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceJSIcon.png
new file mode 100644
index 0000000..9ef6ed0
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceJSIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIcon.png
new file mode 100644
index 0000000..0ed37b6
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIconSmall.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIconSmall.png
new file mode 100644
index 0000000..0fa967d
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIconSmall.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesIcon.png
new file mode 100644
index 0000000..982424d
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSilhouette.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSilhouette.png
new file mode 100644
index 0000000..9c8bb53b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSilhouette.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSizeGraphIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSizeGraphIcon.png
new file mode 100644
index 0000000..e60dbe5
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSizeGraphIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesTimeGraphIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesTimeGraphIcon.png
new file mode 100644
index 0000000..c6953e9
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesTimeGraphIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsIcon.png
new file mode 100644
index 0000000..213b31e
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsSilhouette.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsSilhouette.png
new file mode 100644
index 0000000..206396f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsSilhouette.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBlue.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBlue.png
new file mode 100644
index 0000000..9c990f4
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBlue.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBrightBlue.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBrightBlue.png
new file mode 100644
index 0000000..b1d8055
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBrightBlue.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallGray.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallGray.png
new file mode 100644
index 0000000..4f3c068
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallGray.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallWhite.png
new file mode 100644
index 0000000..85f430df
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallWhite.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segment.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segment.png
new file mode 100644
index 0000000..759266e1c
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segment.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentEnd.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentEnd.png
new file mode 100644
index 0000000..72672ff
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentEnd.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHover.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHover.png
new file mode 100644
index 0000000..c5017f4
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHover.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHoverEnd.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHoverEnd.png
new file mode 100644
index 0000000..d51363d
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHoverEnd.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelected.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelected.png
new file mode 100644
index 0000000..c92f5843
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelected.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelectedEnd.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelectedEnd.png
new file mode 100644
index 0000000..be5e0852
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelectedEnd.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/sessionStorage.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/sessionStorage.png
new file mode 100644
index 0000000..4d50e35
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/sessionStorage.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDimple.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDimple.png
new file mode 100644
index 0000000..584ffd45
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDimple.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDividerBackground.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDividerBackground.png
new file mode 100644
index 0000000..1120a7f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDividerBackground.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBackground.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBackground.png
new file mode 100644
index 0000000..b466a49
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBackground.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBottomBackground.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBottomBackground.png
new file mode 100644
index 0000000..fb5c9e4
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBottomBackground.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarButtons.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarButtons.png
new file mode 100644
index 0000000..e8090cb9
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarButtons.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButton.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButton.png
new file mode 100644
index 0000000..9b3abdd
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButton.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButtonSelected.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButtonSelected.png
new file mode 100644
index 0000000..8189c43
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButtonSelected.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerHorizontal.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerHorizontal.png
new file mode 100644
index 0000000..56deeab
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerHorizontal.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerVertical.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerVertical.png
new file mode 100644
index 0000000..7fc145277
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerVertical.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/storageIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/storageIcon.png
new file mode 100644
index 0000000..79c7bb3
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/storageIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillBlue.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillBlue.png
new file mode 100644
index 0000000..c7c273b8
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillBlue.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGray.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGray.png
new file mode 100644
index 0000000..9ff37ef
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGray.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGreen.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGreen.png
new file mode 100644
index 0000000..cc5a8f3
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGreen.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillOrange.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillOrange.png
new file mode 100644
index 0000000..08a81e4
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillOrange.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillPurple.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillPurple.png
new file mode 100644
index 0000000..565a05c
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillPurple.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillRed.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillRed.png
new file mode 100644
index 0000000..c3a1b9b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillRed.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillYellow.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillYellow.png
new file mode 100644
index 0000000..780045b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillYellow.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillBlue.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillBlue.png
new file mode 100644
index 0000000..c897faa
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillBlue.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGray.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGray.png
new file mode 100644
index 0000000..2128896
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGray.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGreen.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGreen.png
new file mode 100644
index 0000000..9b66125cd
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGreen.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillOrange.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillOrange.png
new file mode 100644
index 0000000..dd944fb
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillOrange.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillPurple.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillPurple.png
new file mode 100644
index 0000000..21b96f7
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillPurple.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillRed.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillRed.png
new file mode 100644
index 0000000..f5e213b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillRed.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillYellow.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillYellow.png
new file mode 100644
index 0000000..ae2a5a23
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillYellow.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloon.png
new file mode 100644
index 0000000..4cdf738
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloonBottom.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloonBottom.png
new file mode 100644
index 0000000..3317a5a5
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloonBottom.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIcon.png
new file mode 100644
index 0000000..8ca6124f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIconPressed.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIconPressed.png
new file mode 100644
index 0000000..443e410
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIconPressed.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/toolbarItemSelected.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/toolbarItemSelected.png
new file mode 100644
index 0000000..bd681f18
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/toolbarItemSelected.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleBlack.png
new file mode 100644
index 0000000..0821112
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleBlack.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleWhite.png
new file mode 100644
index 0000000..1667b51
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleWhite.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleBlack.png
new file mode 100644
index 0000000..90de820
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleBlack.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleWhite.png
new file mode 100644
index 0000000..2b6a82fb
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleWhite.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleBlack.png
new file mode 100644
index 0000000..ef69dbc2
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleBlack.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleWhite.png
new file mode 100644
index 0000000..43ce4be
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleWhite.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/undockButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/undockButtonGlyph.png
new file mode 100644
index 0000000..eed2b65
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/undockButtonGlyph.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputIcon.png
new file mode 100644
index 0000000..325023f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputPreviousIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputPreviousIcon.png
new file mode 100644
index 0000000..068d572
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputPreviousIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputResultIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputResultIcon.png
new file mode 100644
index 0000000..794a5ca
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputResultIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningIcon.png
new file mode 100644
index 0000000..d5e4c825
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningMediumIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningMediumIcon.png
new file mode 100644
index 0000000..291e111
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningMediumIcon.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningsErrors.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningsErrors.png
new file mode 100644
index 0000000..878b593
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningsErrors.png
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inject.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inject.js
new file mode 100644
index 0000000..8a9b199
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inject.js
@@ -0,0 +1,50 @@
+// Copyright (c) 2009 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 Javascript that is being injected into the inspectable page
+ * while debugging.
+ */
+goog.provide('devtools.Injected');
+
+
+/**
+ * Main injected object.
+ * @constructor.
+ */
+devtools.Injected = function() {
+};
+
+
+/**
+ * Dispatches given method with given args on the host object.
+ * @param {string} method Method name.
+ */
+devtools.Injected.prototype.InspectorController = function(method, var_args) {
+  var args = Array.prototype.slice.call(arguments, 1);
+  return InspectorController[method].apply(InspectorController, args);
+};
+
+
+/**
+ * Dispatches given method with given args on the InjectedScript.
+ * @param {string} method Method name.
+ */
+devtools.Injected.prototype.InjectedScript = function(method, var_args) {
+  var args = Array.prototype.slice.call(arguments, 1);
+  var result = InjectedScript[method].apply(InjectedScript, args);
+  return result;
+};
+
+
+// Plugging into upstreamed support.
+InjectedScript._window = function() {
+  return contentWindow;
+};
+
+
+// Plugging into upstreamed support.
+Object.className = function(obj) {
+  return (obj == null) ? "null" : obj.constructor.name;
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.css b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.css
new file mode 100644
index 0000000..ea6f661
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.css
@@ -0,0 +1,3302 @@
+/*
+ * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2009 Anthony Ricaud <[email protected]>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+body {
+    cursor: default;
+    height: 100%;
+    width: 100%;
+    overflow: hidden;
+    font-family: Lucida Grande, sans-serif;
+    font-size: 10px;
+    margin: 0;
+    -webkit-text-size-adjust: none;
+    -webkit-user-select: none;
+}
+
+* {
+    -webkit-box-sizing: border-box;
+}
+
+:focus {
+    outline: none;
+}
+
+input[type="search"]:focus, input[type="text"]:focus {
+    outline: auto 5px -webkit-focus-ring-color;
+}
+
+iframe, a img {
+    border: none;
+}
+
+img {
+    -webkit-user-drag: none;
+}
+
+.hidden {
+    display: none !important;
+}
+
+#toolbar {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 56px;
+    display: -webkit-box;
+    padding: 0 5px;
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(191, 191, 191)), to(rgb(151, 151, 151)));
+    border-bottom: 1px solid rgb(80, 80, 80);
+    -webkit-box-orient: horizontal;
+    -webkit-background-origin: padding;
+    -webkit-background-clip: padding;
+}
+
+body.inactive #toolbar {
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(233, 233, 233)), to(rgb(207, 207, 207)));
+    border-bottom: 1px solid rgb(64%, 64%, 64%);
+}
+
+body.detached.platform-mac-leopard #toolbar {
+    background: transparent !important;
+}
+
+body.attached #toolbar {
+    height: 34px;
+    border-top: 1px solid rgb(100, 100, 100);
+    cursor: row-resize;
+    padding-left: 0;
+}
+
+body.attached.inactive #toolbar {
+    border-top: 1px solid rgb(64%, 64%, 64%);
+}
+
+.toolbar-item {
+    display: -webkit-box;
+    padding: 4px 6px;
+    margin: 0;
+    background-color: transparent;
+    border-style: none;
+    border-color: transparent;
+    -webkit-box-orient: vertical;
+    -webkit-box-align: center;
+    -webkit-box-pack: end;
+}
+
+.toolbar-item.toggleable.toggled-on {
+    border-width: 0 2px 0 2px;
+    padding: 4px 4px;
+    -webkit-border-image: url(Images/toolbarItemSelected.png) 0 2 0 2;
+}
+
+.toolbar-item.flexable-space {
+    -webkit-box-flex: 1;
+    visibility: hidden;
+}
+
+.toolbar-item input {
+    margin-bottom: 8px;
+}
+
+.toolbar-icon {
+    display: inline-block;
+    width: 32px;
+    height: 32px;
+    -webkit-background-size: 100% auto;
+}
+
+body.attached .toolbar-icon {
+    width: 24px;
+    height: 24px;
+    vertical-align: middle;
+}
+
+.toolbar-item:active .toolbar-icon {
+    background-position: 0 32px;
+}
+
+body.attached .toolbar-item:active .toolbar-icon {
+    background-position: 0 24px;
+}
+
+.toolbar-label {
+    font-size: 11px;
+    font-family: Lucida Grande, sans-serif;
+    text-shadow: rgba(255, 255, 255, 0.5) 0 1px 0;
+}
+
+.toolbar-item.toggleable:active .toolbar-label {
+    text-shadow: none;
+}
+
+body.attached .toolbar-label {
+    display: inline-block;
+    vertical-align: middle;
+    margin-left: 3px;
+}
+
+body.attached #search-toolbar-label {
+    display: none;
+}
+
+#search {
+    width: 205px;
+    font-size: 16px;
+    margin-bottom: 5px;
+}
+
+body.attached #search {
+    font-size: 11px;
+    margin-bottom: 8px;
+}
+
+#search-results-matches {
+    font-size: 11px;
+    text-shadow: rgba(255, 255, 255, 0.5) 0 1px 0;
+    margin-bottom: 22px;
+}
+
+body.attached #search-results-matches {
+    margin-bottom: 6px;
+}
+
+.toolbar-item.elements .toolbar-icon {
+    background-image: url(Images/elementsIcon.png);
+}
+
+.toolbar-item.resources .toolbar-icon {
+    background-image: url(Images/resourcesIcon.png);
+}
+
+.toolbar-item.scripts .toolbar-icon {
+    background-image: url(Images/scriptsIcon.png);
+}
+
+.toolbar-item.storage .toolbar-icon {
+    background-image: url(Images/storageIcon.png);
+}
+
+.toolbar-item.profiles .toolbar-icon {
+    background-image: url(Images/profilesIcon.png);
+}
+
+#close-button {
+    width: 14px;
+    height: 14px;
+    background-image: url(Images/closeButtons.png);
+    background-position: 0 0;
+    background-color: transparent;
+    border: 0 none transparent;
+    margin: 5px 0;
+}
+
+#close-button:hover {
+    background-position: 14px 0;
+}
+
+#close-button:active {
+    background-position: 28px 0;
+}
+
+body.detached .toolbar-item.close {
+    display: none;
+}
+
+#main {
+    position: absolute;
+    z-index: 1;
+    top: 56px;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    overflow: hidden;
+    background-color: white;
+}
+
+body.attached #main {
+    top: 34px;
+}
+
+#main-panels {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 23px;
+    overflow: hidden;
+}
+
+#main-status-bar {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+}
+
+body.drawer-visible #main-status-bar {
+    height: 24px;
+    background-image: url(Images/statusbarResizerVertical.png), url(Images/statusbarBackground.png);
+    background-repeat: no-repeat, repeat-x;
+    background-position: right center, center;
+    cursor: row-resize;
+}
+
+body.drawer-visible #main-status-bar * {
+    cursor: default;
+}
+
+body.drawer-visible #main-panels {
+    bottom: 24px;
+}
+
+.status-bar {
+    background-color: rgb(235, 235, 235);
+    background-image: url(Images/statusbarBackground.png);
+    background-repeat: repeat-x;
+    white-space: nowrap;
+    height: 23px;
+    overflow: hidden;
+    z-index: 12;
+}
+
+.status-bar > div {
+    display: inline-block;
+    vertical-align: top;
+}
+
+.status-bar-item {
+    display: inline-block;
+    height: 24px;
+    padding: 0;
+    margin-left: -1px;
+    margin-right: 0;
+    vertical-align: top;
+    border: 0 transparent none;
+    background-color: transparent;
+}
+
+.status-bar-item:active {
+    position: relative;
+    z-index: 200;
+}
+
+.glyph {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.75);
+    z-index: 1;
+}
+
+.glyph.shadow {
+    top: 1px;
+    background-color: white !important;
+    z-index: 0;
+}
+
+button.status-bar-item {
+    position: relative;
+    width: 32px;
+    background-image: url(Images/statusbarButtons.png);
+    background-position: 0 0;
+}
+
+button.status-bar-item:active {
+    background-position: 32px 0 !important;
+}
+
+button.status-bar-item .glyph.shadow {
+    background-color: rgba(255, 255, 255, 0.33) !important;
+}
+
+button.status-bar-item.toggled-on .glyph {
+    background-color: rgb(66, 129, 235);
+}
+
+button.status-bar-item:disabled {
+    opacity: 0.5;
+    background-position: 0 0 !important;
+}
+
+select.status-bar-item {
+    min-width: 48px;
+    border-width: 0 17px 0 2px;
+    padding: 0 2px 0 6px;
+    font-weight: bold;
+    color: rgb(48, 48, 48);
+    text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0;
+    -webkit-border-image: url(Images/statusbarMenuButton.png) 0 17 0 2;
+    -webkit-border-radius: 0;
+    -webkit-appearance: none;
+}
+
+select.status-bar-item:active {
+    color: black;
+    -webkit-border-image: url(Images/statusbarMenuButtonSelected.png) 0 17 0 2;
+}
+
+#dock-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/undockButtonGlyph.png);
+}
+
+body.detached #dock-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/dockButtonGlyph.png);
+}
+
+#console-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/consoleButtonGlyph.png);
+}
+
+#clear-console-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/clearConsoleButtonGlyph.png);
+}
+
+#changes-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/consoleButtonGlyph.png); /* TODO: Needs Image for Changes Toggle Button */
+}
+
+#clear-changes-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/clearConsoleButtonGlyph.png);
+}
+
+#count-items {
+    position: absolute;
+    right: 16px;
+    top: 0;
+    cursor: pointer;
+    padding: 6px 2px;
+    font-size: 10px;
+    height: 19px;
+}
+
+#changes-count, #error-warning-count {
+    display: inline;
+}
+
+#error-warning-count:hover, #changes-count:hover {
+    border-bottom: 1px solid rgb(96, 96, 96);
+}
+
+#style-changes-count::before {
+    content: url(Images/styleIcon.png); /* TODO: Needs Image for Style Changes Icon */
+    width: 10px;
+    height: 10px;
+    vertical-align: -1px;
+    margin-right: 2px;
+}
+
+#error-count::before {
+    content: url(Images/errorIcon.png);
+    width: 10px;
+    height: 10px;
+    vertical-align: -1px;
+    margin-right: 2px;
+}
+    
+#changes-count + #error-warning-count, #error-count + #warning-count {
+    margin-left: 6px;
+}
+
+#warning-count::before {
+    content: url(Images/warningIcon.png);
+    width: 10px;
+    height: 10px;
+    vertical-align: -1px;
+    margin-right: 2px;
+}
+
+#drawer {
+    display: none;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    height: 200px;
+    background-color: white;
+    background-image: url(Images/statusbarBottomBackground.png);
+    background-repeat: repeat-x;
+    background-position: bottom;
+}
+
+body.drawer-visible #drawer {
+    display: block;
+}
+
+#drawer-status-bar {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    background: none;
+}
+
+#console-messages {
+    position: absolute;
+    z-index: 0;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 23px;
+    font-size: initial;
+    font-family: monospace;
+    padding: 2px 0;
+    overflow-y: overlay;
+    -webkit-user-select: text;
+    -webkit-text-size-adjust: auto;
+}
+
+#console-prompt {
+    position: relative;
+    padding: 1px 22px 1px 24px;
+    min-height: 16px; 
+    white-space: pre-wrap;
+    -webkit-user-modify: read-write-plaintext-only;
+}
+
+#console-prompt::before {
+    background-image: url(Images/userInputIcon.png);
+}
+
+.console-user-command-result.console-log-level::before {
+    background-image: url(Images/userInputResultIcon.png);
+}
+
+.console-message, .console-user-command {
+    position: relative;
+    border-bottom: 1px solid rgb(240, 240, 240);
+    padding: 1px 22px 1px 24px;
+    min-height: 16px;
+}
+
+.console-adjacent-user-command-result {
+    border-bottom: none;
+}
+
+.console-adjacent-user-command-result + .console-user-command-result.console-log-level::before {
+    background-image: none;
+}
+
+.console-message::before, .console-user-command::before, #console-prompt::before, .console-group-title::before {
+    position: absolute;
+    display: block;
+    content: "";
+    left: 7px;
+    top: 0.8em;
+    width: 10px;
+    height: 10px;
+    margin-top: -5px;
+    -webkit-user-select: none;
+}
+
+.console-message .bubble {
+    display: inline-block;
+    height: 14px;
+    background-color: rgb(128, 151, 189);
+    vertical-align: middle;
+    white-space: nowrap;
+    padding: 1px 4px;
+    margin-top: -2px;
+    margin-right: 4px;
+    text-align: left;
+    font-size: 11px;
+    font-family: Helvetia, Arial, sans-serif;
+    font-weight: bold;
+    text-shadow: none;
+    color: white;
+    -webkit-border-radius: 7px;
+}
+
+.console-message-text {
+    white-space: pre-wrap;
+}
+
+.repeated-message {
+    padding-left: 6px;
+}
+
+.repeated-message.console-error-level::before, .repeated-message.console-warning-level:before, .repeated-message.console-debug-level:before {
+    visibility: hidden;
+}
+
+.console-group .console-group > .console-group-messages {
+    margin-left: 16px;
+}
+
+.console-group-title {
+    font-weight: bold;
+}
+
+.console-group-title::before {
+    background-image: url(Images/disclosureTriangleSmallDown.png);
+    top: 0.6em;
+    width: 11px;
+    height: 12px;
+}
+
+.console-group.collapsed .console-group-title::before {
+    background-image: url(Images/disclosureTriangleSmallRight.png);
+}
+
+.console-group.collapsed > .console-group-messages {
+    display: none;
+}
+
+.console-error-level .console-message-text {
+    color: red;
+}
+
+.console-debug-level .console-message-text {
+    color: blue;
+}
+
+.console-debug-level::before {
+    background-image: url(Images/searchSmallBrightBlue.png);
+}
+
+.console-error-level::before {
+    background-image: url(Images/errorIcon.png);
+}
+
+.console-warning-level::before {
+    background-image: url(Images/warningIcon.png);
+}
+
+.console-user-command .console-message {
+    margin-left: -24px;
+    padding-right: 0;
+    border-bottom: none;
+}
+
+.console-user-command::before {
+    background-image: url(Images/userInputPreviousIcon.png);
+}
+
+.console-user-command > .console-message-text {
+    color: rgb(0, 128, 255);
+}
+
+#console-messages a {
+    color: rgb(33%, 33%, 33%);
+    cursor: pointer;
+}
+
+#console-messages a:hover {
+    color: rgb(15%, 15%, 15%);
+}
+
+.console-message-url {
+    float: right;
+}
+
+.console-group-messages .section {
+    margin: 0 0 0 12px !important;
+}
+
+.console-group-messages .section .header {
+    padding: 0 8px 0 0;
+    background-image: none;
+    border: none;
+    min-height: 0;
+}
+
+.console-group-messages .section .header::before {
+    position: absolute;
+    top: 1px;
+    left: 1px;
+    width: 8px;
+    height: 8px;
+    content: url(Images/treeRightTriangleBlack.png);
+}
+
+.console-group-messages .section.expanded .header::before {
+    content: url(Images/treeDownTriangleBlack.png);
+}
+
+.console-group-messages .section .header .title {
+    color: black;
+    font-weight: normal;
+}
+
+.console-group-messages .section .properties li .info {
+    padding-top: 0;
+    padding-bottom: 0;
+    color: rgb(60%, 60%, 60%);
+}
+
+.console-group-messages .outline-disclosure {
+    padding-left: 0;
+}
+
+.console-group-messages .outline-disclosure > ol {
+    padding: 0 0 0 12px !important;
+}
+
+.console-group-messages .outline-disclosure, .console-group-messages .outline-disclosure ol {
+    font-size: inherit;
+    line-height: 1em;
+}
+
+.console-group-messages .outline-disclosure.single-node li {
+    padding-left: 2px;
+}
+
+.console-group-messages .outline-disclosure li .selection {
+    margin-left: -6px;
+    margin-right: -6px;
+}
+
+.console-group-messages .add-attribute {
+    display: none;
+}
+
+.console-formatted-object, .console-formatted-node {
+    position: relative;
+    display: inline-block;
+    vertical-align: top;
+}
+
+.console-formatted-object .section, .console-formatted-node .section {
+    position: static;
+}
+
+.console-formatted-object .properties, .console-formatted-node .properties {
+    padding-left: 0 !important;
+}
+
+.error-message {
+    color: red;
+}
+
+.auto-complete-text {
+    color: rgb(128, 128, 128);
+    -webkit-user-select: none;
+    -webkit-user-modify: read-only;
+}
+
+.panel {
+    display: none;
+    overflow: hidden;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+
+.panel.visible {
+    display: block;
+}
+
+.resource-view {
+    display: none;
+    overflow: hidden;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    overflow: hidden;
+}
+
+.resource-view.visible {
+    display: block;
+}
+
+.resource-view.headers-visible {
+    overflow-y: auto;
+    overflow-x: hidden;
+}
+
+.resource-view-headers {
+    display: none;
+    padding: 6px;
+    border-bottom: 1px solid rgb(64%, 64%, 64%);
+    background-color: white;
+    -webkit-user-select: text;
+}
+
+.resource-view-headers .outline-disclosure .parent {
+    -webkit-user-select: none;
+    font-weight: bold;
+}
+
+.resource-view.headers-visible .resource-view-headers {
+    display: block;
+}
+
+.resource-view-headers .outline-disclosure .children li {
+    white-space: nowrap;
+}
+
+.resource-view-headers .outline-disclosure li.expanded .header-count {
+    display: none;
+}
+
+.resource-view-headers .outline-disclosure .header-name {
+    color: rgb(33%, 33%, 33%);
+    display: inline-block;
+    width: 105px;
+    text-align: right;
+    margin-right: 0.5em;
+    font-weight: bold;
+    vertical-align: top;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.resource-view-headers .outline-disclosure .header-value {
+    display: inline-block;
+    white-space: normal;
+    word-break: break-word;
+    vertical-align: top;
+    margin-right: 100px;
+}
+
+.resource-view-headers .outline-disclosure .raw-form-data {
+    white-space:pre-wrap;
+}
+
+.resource-view .resource-view-content {
+    position: absolute;
+    top: 0;
+    right: 0;
+    left: 0;
+    bottom: 0;
+}
+
+.resource-view.headers-visible .resource-view-content {
+    position: relative;
+    top: auto;
+    right: auto;
+    left: auto;
+    bottom: auto;
+}
+
+.resource-view.headers-visible .source-view-frame {
+    height: auto;
+    vertical-align: top;
+}
+
+.invisible {
+    color: inherit;
+    text-decoration: none;
+}
+
+.webkit-line-gutter-backdrop {
+    /* Keep this in sync with view-source.css (.webkit-line-gutter-backdrop) */
+    width: 31px;
+    background-color: rgb(240, 240, 240);
+    border-right: 1px solid rgb(187, 187, 187);
+    position: absolute;
+    z-index: -1;
+    left: 0;
+    top: 0;
+    height: 100%
+}
+
+.resource-view.font .resource-view-content {
+    font-size: 60px;
+    white-space: pre-wrap;
+    word-wrap: break-word;
+    text-align: center;
+    padding: 15px;
+}
+
+.resource-view.image .resource-view-content > .image {
+    padding: 20px 20px 10px 20px;
+    text-align: center;
+}
+
+.resource-view.image .resource-view-content > .info {
+    padding-bottom: 10px;
+    font-size: 11px;
+    -webkit-user-select: text;
+}
+
+.resource-view.image img {
+    max-width: 100%;
+    max-height: 1000px;
+    background-image: url(Images/checker.png);
+    -webkit-box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5);
+    -webkit-user-select: text;
+    -webkit-user-drag: auto;
+}
+
+.resource-view.image .title {
+    text-align: center;
+    font-size: 13px;
+}
+
+.resource-view.image .infoList {
+    margin: 0;
+}
+
+.resource-view.image .infoList dt {
+    font-weight: bold;
+    display: inline-block;
+    width: 50%;
+    text-align: right;
+    color: rgb(76, 76, 76);
+}
+
+.resource-view.image .infoList dd {
+    display: inline-block;
+    padding-left: 8px;
+    width: 50%;
+    text-align: left;
+    margin: 0;
+}
+
+.resource-view.image .infoList dd::after {
+    white-space: pre;
+    content: "\A";
+}
+
+#elements-content {
+    display: block;
+    overflow: auto;
+    padding: 0;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 225px;
+    bottom: 0;
+}
+
+#elements-sidebar {
+    position: absolute;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    width: 225px;
+    background-color: rgb(245, 245, 245);
+    border-left: 1px solid rgb(64%, 64%, 64%);
+    cursor: default;
+    overflow: auto;
+}
+
+.crumbs {
+    display: inline-block;
+    font-size: 11px;
+    line-height: 19px;
+    text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0;
+    color: rgb(20, 20, 20);
+    margin-left: -1px;
+    padding-right: 12px;
+}
+
+.crumbs .crumb {
+    height: 24px;
+    border-width: 0 12px 0 2px;
+    -webkit-border-image: url(Images/segment.png) 0 12 0 2;
+    margin-right: -12px;
+    padding-left: 18px;
+    padding-right: 2px;
+    white-space: nowrap;
+    line-height: 23px;
+    float: right;
+}
+
+.crumbs .crumb.collapsed > * {
+    display: none;
+}
+
+.crumbs .crumb.collapsed::before {
+    content: "\2026";
+    font-weight: bold;
+}
+
+.crumbs .crumb.compact .extra {
+    display: none;
+}
+
+.crumbs .crumb.dimmed {
+    color: rgba(0, 0, 0, 0.45);
+}
+
+.crumbs .crumb.start {
+    padding-left: 7px;
+}
+
+.crumbs .crumb.end {
+    border-width: 0 2px 0 2px;
+    padding-right: 6px;
+    -webkit-border-image: url(Images/segmentEnd.png) 0 2 0 2;
+}
+
+.crumbs .crumb.selected {
+    -webkit-border-image: url(Images/segmentSelected.png) 0 12 0 2;
+    color: black;
+    text-shadow: rgba(255, 255, 255, 0.5) 0 1px 0;
+}
+
+.crumbs .crumb.selected:hover {
+    -webkit-border-image: url(Images/segmentSelected.png) 0 12 0 2;
+}
+
+.crumbs .crumb.selected.end, .crumbs .crumb.selected.end:hover {
+    -webkit-border-image: url(Images/segmentSelectedEnd.png) 0 2 0 2;
+}
+
+.crumbs .crumb:hover {
+    -webkit-border-image: url(Images/segmentHover.png) 0 12 0 2;
+    color: black;
+}
+
+.crumbs .crumb.dimmed:hover {
+    -webkit-border-image: url(Images/segmentHover.png) 0 12 0 2;
+    color: rgba(0, 0, 0, 0.75);
+}
+
+.crumbs .crumb.end:hover {
+    -webkit-border-image: url(Images/segmentHoverEnd.png) 0 2 0 2;
+}
+
+.outline-disclosure li.hovered:not(.selected) .selection {
+    display: block;
+    left: 3px;
+    right: 3px;
+    background-color: rgba(56, 121, 217, 0.1);
+    -webkit-border-radius: 5px;
+}
+
+.outline-disclosure li.highlighted .highlight {
+    background-color: rgb(255, 230, 179);
+    -webkit-border-radius: 4px;
+    padding-bottom: 2px;
+    margin-bottom: -2px;
+}
+
+.outline-disclosure li.selected.highlighted .highlight {
+    background-color: transparent;
+    padding-bottom: 0;
+    margin-bottom: 0;
+}
+
+.outline-disclosure li .selection {
+    display: none;
+    position: absolute;
+    left: 0;
+    right: 0;
+    height: 15px;
+    z-index: -1;
+}
+
+.outline-disclosure li.selected .selection {
+    display: block;
+    background-color: rgb(212, 212, 212);
+}
+
+:focus .outline-disclosure li.selected .selection {
+    background-color: rgb(56, 121, 217);
+}
+
+.outline-disclosure > ol {
+    position: relative;
+    padding: 2px 6px !important;
+    margin: 0;
+    color: black;
+    cursor: default;
+    min-width: 100%;
+}
+
+.outline-disclosure, .outline-disclosure ol {
+    list-style-type: none;
+    font-size: 11px;
+    -webkit-padding-start: 12px;
+    margin: 0;
+}
+
+.outline-disclosure li {
+    padding: 0 0 2px 14px;
+    margin-top: 1px;
+    margin-bottom: 1px;
+    word-wrap: break-word;
+    text-indent: -2px
+}
+
+:focus .outline-disclosure li.selected {
+    color: white;
+}
+
+:focus .outline-disclosure li.selected * {
+    color: inherit;
+}
+
+.outline-disclosure li.parent {
+    text-indent: -12px
+}
+
+.outline-disclosure li .webkit-html-tag.close {
+    margin-left: -12px;
+}
+
+.outline-disclosure li.parent::before {
+    content: url(Images/treeRightTriangleBlack.png);
+    float: left;
+    width: 8px;
+    height: 8px;
+    margin-top: 1px;
+    padding-right: 2px;
+}
+
+.outline-disclosure li.parent::before {
+    content: url(Images/treeRightTriangleBlack.png);
+}
+
+:focus .outline-disclosure li.parent.selected::before {
+    content: url(Images/treeRightTriangleWhite.png);
+}
+
+.outline-disclosure li.parent.expanded::before {
+    content: url(Images/treeDownTriangleBlack.png);
+}
+
+:focus .outline-disclosure li.parent.expanded.selected::before {
+    content: url(Images/treeDownTriangleWhite.png);
+}
+
+.outline-disclosure ol.children {
+    display: none;
+}
+
+.outline-disclosure ol.children.expanded {
+    display: block;
+}
+
+.webkit-html-comment {
+    /* Keep this in sync with view-source.css (.webkit-html-comment) */
+    color: rgb(35, 110, 37);
+}
+
+.webkit-html-tag {
+    /* Keep this in sync with view-source.css (.webkit-html-tag) */
+    color: rgb(136, 18, 128);
+}
+
+.webkit-html-doctype {
+    /* Keep this in sync with view-source.css (.webkit-html-doctype) */
+    color: rgb(192, 192, 192);
+}
+
+.webkit-html-attribute-name {
+    /* Keep this in sync with view-source.css (.webkit-html-attribute-name) */
+    color: rgb(153, 69, 0);
+}
+
+.webkit-html-attribute-value {
+    /* Keep this in sync with view-source.css (.webkit-html-attribute-value) */
+    color: rgb(26, 26, 166);
+}
+
+.webkit-html-external-link, .webkit-html-resource-link {
+    /* Keep this in sync with view-source.css (.webkit-html-external-link, .webkit-html-resource-link) */
+    color: #00e;
+}
+
+.webkit-html-external-link {
+    /* Keep this in sync with view-source.css (.webkit-html-external-link) */
+    text-decoration: none;
+}
+
+.webkit-html-external-link:hover {
+    /* Keep this in sync with view-source.css (.webkit-html-external-link:hover) */
+    text-decoration: underline;
+}
+
+.add-attribute {
+    margin-left: 1px;
+    margin-right: 1px;
+}
+
+.placard {
+    position: relative;
+    margin-top: 1px;
+    padding: 3px 8px 4px 18px;
+    min-height: 18px;
+    white-space: nowrap;
+}
+
+.placard:nth-of-type(2n) {
+    background-color: rgb(234, 243, 255);
+}
+
+.placard.selected {
+    border-top: 1px solid rgb(145, 160, 192);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(162, 177, 207)), to(rgb(120, 138, 177)));
+    -webkit-background-origin: padding;
+    -webkit-background-clip: padding;
+}
+
+:focus .placard.selected {
+    border-top: 1px solid rgb(68, 128, 200);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(92, 147, 213)), to(rgb(21, 83, 170)));
+}
+
+body.inactive .placard.selected {
+    border-top: 1px solid rgb(151, 151, 151);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(180, 180, 180)), to(rgb(138, 138, 138)));
+}
+
+.placard .title {
+    color: black;
+    font-weight: normal;
+    word-wrap: break-word;
+    white-space: normal;
+}
+
+.placard.selected .title {
+    color: white;
+    font-weight: bold;
+}
+
+.placard .subtitle {
+    float: right;
+    font-size: 10px;
+    margin-left: 5px;
+    max-width: 55%;
+    color: rgba(0, 0, 0, 0.7);
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+.placard.selected .subtitle {
+    color: rgba(255, 255, 255, 0.7);
+}
+
+.placard .subtitle a {
+    color: inherit;
+}
+
+.section {
+    position: relative;
+    margin-top: 1px;
+}
+
+.section:nth-last-of-type(1) {
+    margin-bottom: 1px;
+}
+
+.watch-expressions-buttons-container {
+    text-align: center;
+}
+
+.section .header {
+    padding: 2px 8px 4px 18px;
+    border-top: 1px solid rgb(145, 160, 192);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(162, 177, 207)), to(rgb(120, 138, 177)));
+    min-height: 18px;
+    white-space: nowrap;
+    -webkit-background-origin: padding;
+    -webkit-background-clip: padding;
+}
+
+.section.no-affect .header {
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(167, 167, 167)), to(rgb(123, 123, 123)))
+}
+
+.section .header::before {
+    position: absolute;
+    top: 4px;
+    left: 7px;
+    width: 8px;
+    height: 8px;
+    content: url(Images/treeRightTriangleWhite.png);
+}
+
+.section.blank-section .header::before {
+    display: none;
+}
+
+.section.expanded .header::before {
+    content: url(Images/treeDownTriangleWhite.png);
+}
+
+.section .header .title {
+    color: white;
+    font-weight: bold;
+    word-wrap: break-word;
+    white-space: normal;
+}
+
+.section .header .title.blank-title {
+    font-style: italic;
+}
+
+.section .header label {
+    display: none;
+}
+
+.section.expanded .header label {
+    display: inline;
+}
+
+.section .header input[type=checkbox] {
+    height: 10px;
+    width: 10px;
+    margin-left: 0;
+    margin-top: 0;
+    margin-bottom: 0;
+    vertical-align: 2px;
+}
+
+.section .header .subtitle {
+    float: right;
+    font-size: 10px;
+    margin-left: 5px;
+    max-width: 55%;
+    color: rgba(255, 255, 255, 0.7);
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+.section .header .subtitle a {
+    color: inherit;
+}
+
+.section .properties {
+    display: none;
+    margin: 0;
+    padding: 2px 6px 3px;
+    list-style: none;
+    background-color: white;
+    min-height: 18px;
+}
+
+.section.no-affect .properties li {
+    opacity: 0.5;
+}
+
+.section.no-affect .properties li.editing {
+    opacity: 1.0;
+}
+
+.section.expanded .properties {
+    display: block;
+}
+
+.section .properties li {
+    margin-left: 12px;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    -webkit-user-select: text;
+    cursor: auto;
+}
+
+.section .properties li.parent {
+    margin-left: 1px;
+}
+
+.section .properties ol {
+    display: none;
+    margin: 0;
+    -webkit-padding-start: 12px;
+    list-style: none;
+}
+
+.section .properties ol.expanded {
+    display: block;
+}
+
+.section .properties li.parent::before {
+    content: url(Images/treeRightTriangleBlack.png);
+    opacity: 0.75;
+    float: left;
+    width: 8px;
+    height: 8px;
+    margin-top: 0;
+    padding-right: 3px;
+    -webkit-user-select: none;
+    cursor: default;
+}
+
+.section .properties li.parent.expanded::before {
+    content: url(Images/treeDownTriangleBlack.png);
+    margin-top: 1px;
+}
+
+.section .properties li .info {
+    padding-top: 4px;
+    padding-bottom: 3px;
+}
+
+.editing {
+    -webkit-user-select: text;
+    -webkit-box-shadow: rgba(0, 0, 0, .5) 3px 3px 4px;
+    outline: 1px solid rgb(66%, 66%, 66%) !important;
+    background-color: white;
+    -webkit-user-modify: read-write-plaintext-only;
+    text-overflow: clip;
+    padding-left: 2px;
+    margin-left: -2px;
+    padding-right: 2px;
+    margin-right: -2px;
+    margin-bottom: -1px;
+    padding-bottom: 1px;
+    opacity: 1.0 !important;
+}
+
+.editing, .editing * {
+    color: black !important;
+    text-decoration: none !important;
+}
+
+.section .properties li.editing {
+    margin-left: 10px;
+    text-overflow: clip;
+}
+
+li.editing .swatch, li.editing .enabled-button,  li.editing-sub-part .delete-button {
+    display: none !important;
+}
+
+.watch-expressions > li.editing-sub-part .name {
+    display: block; 
+    width: 100%;
+}
+
+.watch-expressions > li.editing-sub-part .value, .watch-expressions > li.editing-sub-part .separator  {
+    display: none;
+}
+
+.section .properties li.editing-sub-part {
+    padding: 3px 6px 8px 18px;
+    margin: -3px -6px -8px -6px;
+    text-overflow: clip;
+}
+
+.section .properties .overloaded, .section .properties .disabled {
+    text-decoration: line-through;
+}
+
+.section.computed-style .properties .disabled {
+    text-decoration: none;
+    opacity: 0.5;
+}
+
+.section .properties .implicit, .section .properties .inherited {
+    opacity: 0.5;
+}
+
+.section:not(.show-inherited) .properties .inherited {
+    display: none;
+}
+
+.section .properties .enabled-button {
+    display: none;
+    float: right;
+    font-size: 10px;
+    margin: 0 0 0 4px;
+    vertical-align: top;
+    position: relative;
+    z-index: 1;
+}
+
+/* FIXME: need a better icon (comment in bug 27514) */
+.section .properties .delete-button {
+    width: 10px;
+    height: 10px;
+    background-image: url(Images/errorIcon.png);
+    background-position: 0 0;
+    background-color: transparent;
+    background-repeat: no-repeat;
+    border: 0 none transparent;
+}
+
+.section:hover .properties .enabled-button {
+    display: block;
+}
+
+.section .properties .name {
+    color: rgb(136, 19, 145);
+}
+
+.section .properties .value.dimmed {
+    color: rgb(100, 100, 100);
+}
+
+.section .properties .number {
+    color: blue;
+}
+
+.section .properties .priority {
+    color: rgb(128, 0, 0);
+}
+
+.section .properties .keyword {
+    color: rgb(136, 19, 79);
+}
+
+.section .properties .color {
+    color: rgb(118, 15, 21);
+}
+
+.swatch {
+    display: inline-block;
+    vertical-align: baseline;
+    margin-left: 1px;
+    margin-right: 2px;
+    margin-bottom: -1px;
+    width: 1em;
+    height: 1em;
+    border: 1px solid rgba(128, 128, 128, 0.6);
+}
+
+.swatch:hover {
+    border: 1px solid rgba(64, 64, 64, 0.8);
+}
+
+.pane:not(.expanded) + .pane, .pane:first-of-type {
+    margin-top: -1px;
+}
+
+.pane > .title {
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(243, 243, 243)), color-stop(0.05, rgb(243, 243, 243)), color-stop(0.05, rgb(230, 230, 230)), to(rgb(209, 209, 209)));
+    height: 20px;
+    padding: 0 5px;
+    border-top: 1px solid rgb(189, 189, 189);
+    border-bottom: 1px solid rgb(189, 189, 189);
+    font-weight: bold;
+    font-size: 12px;
+    line-height: 18px;
+    color: rgb(110, 110, 110);
+    text-shadow: white 0 1px 0;
+    -webkit-background-origin: padding;
+    -webkit-background-clip: padding;
+}
+
+.pane > .title:active {
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(231, 231, 231)), color-stop(0.05, rgb(231, 231, 231)), color-stop(0.05, rgb(207, 207, 207)), to(rgb(186, 186, 186)));
+    border-top: 1px solid rgb(178, 178, 178);
+    border-bottom: 1px solid rgb(178, 178, 178);
+}
+
+.pane > .title::before {
+    content: url(Images/disclosureTriangleSmallRightBlack.png);
+    float: left;
+    width: 11px;
+    height: 12px;
+    margin-right: 2px;
+    margin-top: 1px;
+}
+
+.pane.expanded > .title::before {
+    content: url(Images/disclosureTriangleSmallDownBlack.png);
+}
+
+.pane > .title > select {
+    display: none;
+    float: right;
+    width: 23px;
+    height: 17px;
+    color: transparent;
+    background-color: transparent;
+    border: none;
+    background-image: url(Images/paneSettingsButtons.png);
+    background-repeat: no-repeat;
+    margin: 1px 0 0 0;
+    padding: 0;
+    -webkit-border-radius: 0;
+    -webkit-appearance: none;
+}
+
+.pane.expanded:hover > .title > select {
+    display: inline-block;
+}
+
+.pane > .title > select:hover {
+    background-position: -23px 0px;
+}
+
+.pane > .title > select:active {
+    background-position: -46px 0px;
+}
+
+.pane > .body {
+    position: relative;
+    display: none;
+    background-color: white;
+    overflow-y: auto;
+    overflow-x: hidden;
+}
+
+.pane > .body .info {
+    text-align: center;
+    font-style: italic;
+    font-size: 10px;
+    padding: 6px;
+    color: gray;
+}
+
+.pane.expanded > .body, .pane.expanded > .growbar {
+    display: block;
+}
+
+.pane.expanded:nth-last-of-type(1) {
+    border-bottom: 1px solid rgb(189, 189, 189);
+}
+
+.pane > .growbar {
+    display: none;
+    background-image: url(Images/paneGrowHandleLine.png), url(Images/paneBottomGrow.png);
+    background-repeat: no-repeat, repeat-x;
+    background-position: center center, bottom;
+    height: 5px;
+}
+
+.metrics {
+    padding: 8px;
+    font-size: 10px;
+    text-align: center;
+    white-space: nowrap;
+}
+
+.metrics .label {
+    position: absolute;
+    margin-top: -10px;
+    font-size: 9px;
+    color: grey;
+    background-color: white;
+    margin-left: 3px;
+    padding-left: 2px;
+    padding-right: 2px;
+}
+
+.metrics .position {
+    border: 1px rgb(66%, 66%, 66%) dotted;
+    display: inline-block;
+    text-align: center;
+    padding: 3px;
+    margin: 3px;
+}
+
+.metrics .margin {
+    border: 1px dashed;
+    display: inline-block;
+    text-align: center;
+    vertical-align: middle;
+    padding: 3px;
+    margin: 3px;
+}
+
+.metrics .border {
+    border: 1px black solid;
+    display: inline-block;
+    text-align: center;
+    vertical-align: middle;
+    padding: 3px;
+    margin: 3px;
+}
+
+.metrics .padding {
+    border: 1px grey dashed;
+    display: inline-block;
+    text-align: center;
+    vertical-align: middle;
+    padding: 3px;
+    margin: 3px;
+}
+
+.metrics .content {
+    position: static;
+    border: 1px grey solid;
+    display: inline-block;
+    text-align: center;
+    vertical-align: middle;
+    padding: 3px;
+    margin: 3px;
+    min-width: 80px;
+    text-align: center;
+    overflow: visible;
+}
+
+.metrics .content span {
+    display: inline-block;
+}
+
+.metrics .editing {
+    position: relative;
+    z-index: 100;
+}
+
+.metrics .left {
+    display: inline-block;
+    vertical-align: middle;
+}
+
+.metrics .right {
+    display: inline-block;
+    vertical-align: middle;
+}
+
+.metrics .top {
+    display: inline-block;
+}
+
+.metrics .bottom {
+    display: inline-block;
+}
+
+.sidebar {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    width: 200px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    background-color: rgb(214, 221, 229);
+    border-right: 1px solid rgb(64%, 64%, 64%);
+}
+
+body.inactive .sidebar {
+    background-color: rgb(232, 232, 232);
+}
+
+.database-sidebar-tree-item .icon {
+    content: url(Images/database.png);
+}
+
+.database-table-sidebar-tree-item .icon {
+    content: url(Images/databaseTable.png);
+}
+
+.domstorage-sidebar-tree-item.local-storage .icon {
+    content: url(Images/localStorage.png);
+}
+
+.domstorage-sidebar-tree-item.session-storage .icon {
+    content: url(Images/sessionStorage.png);
+}
+
+.cookie-sidebar-tree-item .icon {
+    content: url(Images/cookie.png);
+}
+
+#storage-views {
+    position: absolute;
+    top: 0;
+    right: 0;
+    left: 200px;
+    bottom: 0;
+}
+
+.storage-view {
+    display: none;
+    overflow: hidden;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+
+.storage-view.visible {
+    display: block;
+}
+
+.storage-view.table {
+    overflow: hidden;
+}
+
+.storage-view.table .data-grid {
+    border: none;
+    height: 100%;
+}
+
+.storage-view.table .storage-table-empty, .storage-view.table .storage-table-error {
+    position: absolute;
+    top: 0;
+    bottom: 25%;
+    left: 0;
+    right: 0;
+    font-size: 24px;
+    color: rgb(75%, 75%, 75%);
+    margin-top: auto;
+    margin-bottom: auto;
+    height: 50px;
+    line-height: 26px;
+    text-align: center;
+    font-weight: bold;
+    padding: 10px;
+    white-space: pre-wrap;
+}
+
+.storage-view.table .storage-table-error {
+    color: rgb(66%, 33%, 33%);
+}
+
+.data-grid {
+    position: relative;
+    border: 1px solid #aaa;
+}
+
+.data-grid .highlight {
+    background-color: rgb(255, 230, 179);
+}
+
+.data-grid tr.selected .highlight {
+    background-color: transparent;
+}
+
+.data-grid table {
+    table-layout: fixed;
+    border-spacing: 0;
+    border-collapse: collapse;
+    width: 100%;
+    font-size: 10px;
+    font-family: Lucida Grande, sans-serif;
+}
+
+.data-grid .data-container {
+    position: absolute;
+    top: 16px;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    padding-right: 14px;
+    overflow-x: hidden;
+    overflow-y: overlay;
+    background-image: -webkit-gradient(linear, left top, left bottom, from(white), color-stop(0.5, white), color-stop(0.5, rgb(234, 243, 255)), to(rgb(234, 243, 255)));
+    -webkit-background-size: 1px 32px;
+}
+
+.data-grid.inline .data-container {
+    position: static;
+}
+
+.data-grid th {
+    text-align: left;
+    background-image: url(Images/glossyHeader.png);
+    background-repeat: repeat-x;
+    border-right: 1px solid rgb(179, 179, 179);
+    border-bottom: 1px solid rgb(179, 179, 179);
+    height: 15px;
+    font-weight: normal;
+    vertical-align: middle;
+    padding: 0 4px;
+    white-space: nowrap;
+}
+
+.data-grid th.corner {
+    width: 15px;
+    border-right: 0 none transparent;
+}
+
+.data-grid tr.filler {
+    display: table-row !important;
+    height: auto !important;
+}
+
+.data-grid tr.filler td {
+    height: auto !important;
+    padding: 0 !important;
+}
+
+.data-grid table.data {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 16px;
+    bottom: 0;
+    height: 100%;
+    border-top: 0 none transparent;
+    background-image: -webkit-gradient(linear, left top, left bottom, from(white), color-stop(0.5, white), color-stop(0.5, rgb(234, 243, 255)), to(rgb(234, 243, 255)));
+    -webkit-background-size: 1px 32px;
+}
+
+.data-grid.inline table.data {
+    position: static;
+}
+
+.data-grid table.data tr {
+    display: none;
+}
+
+.data-grid table.data tr.revealed {
+    display: table-row;
+}
+
+.data-grid td {
+    vertical-align: top;
+    height: 12px;
+    padding: 2px 4px;
+    white-space: nowrap;
+    border-right: 1px solid #aaa;
+    -webkit-user-select: text;
+}
+
+.data-grid td > div, .data-grid th > div {
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+.data-grid .centered div {
+    text-align: center;
+}
+
+.data-grid .right div {
+    text-align: right;
+}
+
+.data-grid th.sortable div {
+    position: relative;
+}
+
+.data-grid th.sortable:active {
+    background-image: url(Images/glossyHeaderPressed.png);
+}
+
+.data-grid th.sort-ascending, .data-grid th.sort-descending {
+    border-right: 1px solid rgb(107, 140, 196);
+    border-bottom: 1px solid rgb(107, 140, 196);
+    background-image: url(Images/glossyHeaderSelected.png);
+    background-repeat: repeat-x;
+}
+
+.data-grid th.sortable.sort-ascending:active, .data-grid th.sortable.sort-descending:active {
+    background-image: url(Images/glossyHeaderSelectedPressed.png);
+}
+
+.data-grid th.sort-ascending div::after {
+    position: absolute;
+    top: 0;
+    right: 0;
+    width: 8px;
+    height: 8px;
+    content: url(Images/treeUpTriangleBlack.png);
+}
+
+.data-grid th.sort-descending div::after {
+    position: absolute;
+    top: 0;
+    right: 0;
+    margin-top: 1px;
+    width: 8px;
+    height: 8px;
+    content: url(Images/treeDownTriangleBlack.png);
+}
+
+body.inactive .data-grid th.sort-ascending, body.inactive .data-grid th.sort-descending {
+    background-image: url(Images/glossyHeader.png);
+    border-right: 1px solid rgb(179, 179, 179);
+    border-bottom: 1px solid rgb(179, 179, 179);
+}
+
+.data-grid tr.parent td.disclosure::before {
+    float: left;
+    content: url(Images/treeRightTriangleBlack.png);
+    width: 8px;
+    height: 8px;
+    margin-right: 2px;
+    -webkit-user-select: none;
+}
+
+.data-grid tr.expanded td.disclosure::before {
+    content: url(Images/treeDownTriangleBlack.png);
+    width: 8px;
+    height: 8px;
+    margin-top: 1px;
+}
+
+.data-grid tr.selected {
+    background-color: rgb(212, 212, 212);
+    color: inherit;
+}
+
+.data-grid:focus tr.selected {
+    background-color: rgb(56, 121, 217);
+    color: white;
+}
+
+.data-grid:focus tr.parent.selected td.disclosure::before {
+    content: url(Images/treeRightTriangleWhite.png);
+}
+
+.data-grid:focus tr.expanded.selected td.disclosure::before {
+    content: url(Images/treeDownTriangleWhite.png);
+}
+
+.data-grid tr:not(.parent) td.disclosure {
+    text-indent: 10px;
+}
+
+.data-grid-resizer {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    width: 5px;
+    z-index: 500;
+    cursor: col-resize;
+}
+
+.storage-view.query {
+    font-size: initial;
+    font-family: monospace;
+    padding: 2px 0;
+    overflow-y: overlay;
+    overflow-x: hidden;
+    -webkit-text-size-adjust: auto;
+}
+
+.database-query-prompt {
+    position: relative;
+    padding: 1px 22px 1px 24px;
+    min-height: 16px; 
+    white-space: pre-wrap;
+    -webkit-user-modify: read-write-plaintext-only;
+    -webkit-user-select: text;
+}
+
+.database-user-query::before, .database-query-prompt::before, .database-query-result::before {
+    position: absolute;
+    display: block;
+    content: "";
+    left: 7px;
+    top: 0.8em;
+    width: 10px;
+    height: 10px;
+    margin-top: -5px;
+    -webkit-user-select: none;
+}
+
+.database-query-prompt::before {
+    background-image: url(Images/userInputIcon.png);
+}
+
+.database-user-query {
+    position: relative;
+    border-bottom: 1px solid rgb(245, 245, 245);
+    padding: 1px 22px 1px 24px;
+    min-height: 16px; 
+}
+
+.database-user-query::before {
+    background-image: url(Images/userInputPreviousIcon.png);
+}
+
+.database-query-text {
+    color: rgb(0, 128, 255);
+    -webkit-user-select: text;
+}
+
+.database-query-result {
+    position: relative;
+    padding: 1px 22px 1px 24px;
+    min-height: 16px;
+    margin-left: -24px;
+    padding-right: 0;
+}
+
+.database-query-result.error {
+    color: red;
+    -webkit-user-select: text;
+}
+
+.database-query-result.error::before {
+    background-image: url(Images/errorIcon.png);
+}
+
+.panel-enabler-view {
+    z-index: 1000;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: white;
+    font-size: 13px;
+    text-align: center;
+    overflow-x: hidden;
+    overflow-y: overlay;
+    display: none;
+}
+
+.panel-enabler-view.visible {
+    display: block;
+}
+
+.panel-enabler-view .panel-enabler-view-content {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    max-height: 390px;
+    margin: auto;
+    white-space: nowrap;
+}
+
+.panel-enabler-view h1 {
+    color: rgb(110, 116, 128);
+    font-size: 16px;
+    line-height: 20px;
+    font-weight: normal;
+    margin-top: 0;
+}
+
+.panel-enabler-disclaimer {
+    font-size: 10px;
+    color: rgb(110, 116, 128);
+    margin-bottom: 12px;
+    margin-left: 20px;
+}
+
+.panel-enabler-disclaimer:empty {
+    display: none;
+}
+
+.panel-enabler-view img {
+    height: 100%;
+    min-height: 200px;
+    max-width: 100%;
+    top: 0;
+    bottom: 0;
+    padding: 20px 0 20px 20px;
+    margin: auto;
+    vertical-align: middle;
+}
+
+.panel-enabler-view img.hidden {
+    display: initial !important;
+    width: 0;
+}
+
+.panel-enabler-view form {
+    display: inline-block;
+    vertical-align: middle;
+    width: 330px;
+    margin: 0;
+    padding: 15px;
+    white-space: normal;
+}
+
+.panel-enabler-view label {
+    position: relative;
+    display: block;
+    text-align: left;
+    word-break: break-word;
+    margin: 0 0 5px 20px;
+}
+
+.panel-enabler-view button {
+    font-size: 13px;
+    margin: 6px 0 0 0;
+    padding: 3px 20px;
+    color: rgb(6, 6, 6);
+    height: 24px;
+    background-color: transparent;
+    border: 1px solid rgb(165, 165, 165);
+    background-color: rgb(237, 237, 237);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(252, 252, 252)), to(rgb(223, 223, 223)));
+    -webkit-border-radius: 12px;
+    -webkit-appearance: none;
+}
+
+.panel-enabler-view button:active {
+    background-color: rgb(215, 215, 215);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(194, 194, 194)), to(rgb(239, 239, 239)));
+}
+
+body.inactive .panel-enabler-view button, .panel-enabler-view button:disabled {
+    color: rgb(130, 130, 130);
+    border-color: rgb(212, 212, 212);
+    background-color: rgb(239, 239, 239);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(250, 250, 250)), to(rgb(235, 235, 235)));
+}
+
+.panel-enabler-view input {
+    height: 17px;
+    width: 17px;
+    border: 1px solid rgb(165, 165, 165);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(252, 252, 252)), to(rgb(223, 223, 223)));
+    -webkit-border-radius: 8px;
+    -webkit-appearance: none;
+    vertical-align: middle;
+    margin: 0 5px 5px 0;
+}
+
+.panel-enabler-view input:active {
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(194, 194, 194)), to(rgb(239, 239, 239)));
+}
+
+.panel-enabler-view input:checked {
+    background: url(Images/radioDot.png) center no-repeat,
+                -webkit-gradient(linear, left top, left bottom, from(rgb(252, 252, 252)), to(rgb(223, 223, 223)));
+}
+
+.panel-enabler-view.resources img {
+    content: url(Images/resourcesSilhouette.png);
+}
+
+.panel-enabler-view.scripts img {
+    content: url(Images/scriptsSilhouette.png);
+}
+
+.panel-enabler-view.profiles img {
+    content: url(Images/profilesSilhouette.png);
+}
+
+button.enable-toggle-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/enableOutlineButtonGlyph.png);
+}
+
+button.enable-toggle-status-bar-item.toggled-on .glyph {
+    -webkit-mask-image: url(Images/enableSolidButtonGlyph.png);
+}
+
+.scripts-pause-on-exceptions-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/pauseOnExceptionButtonGlyph.png);
+}
+
+#scripts-status-bar {
+    position: absolute;
+    top: -1px;
+    left: 0;
+    right: 0;
+    height: 24px;
+}
+
+#scripts-files {
+    max-width: 250px;
+}
+
+#scripts-functions {
+    max-width: 150px;
+}
+
+#scripts-status-bar .status-bar-item img {
+    margin-top: 2px;
+}
+
+#scripts-back img {
+    content: url(Images/back.png);
+}
+
+#scripts-forward img {
+    content: url(Images/forward.png);
+}
+
+#scripts-pause img {
+    content: url(Images/debuggerPause.png);
+}
+
+#scripts-pause.paused img {
+    content: url(Images/debuggerContinue.png);
+}
+
+#scripts-step-over img {
+    content: url(Images/debuggerStepOver.png);
+}
+
+#scripts-step-into img {
+    content: url(Images/debuggerStepInto.png);
+}
+
+#scripts-step-out img {
+    content: url(Images/debuggerStepOut.png);
+}
+
+#scripts-debugger-status {
+    position: absolute;
+    line-height: 24px;
+    top: 0;
+    right: 8px;
+}
+
+#scripts-sidebar-resizer-widget {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    right: 225px;
+    width: 16px;
+    cursor: col-resize;
+    background-image: url(Images/statusbarResizerHorizontal.png);
+    background-repeat: no-repeat;
+    background-position: center;
+}
+
+#scripts-sidebar-buttons {
+    position: absolute;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    width: 225px;
+    overflow: hidden;
+    border-left: 1px solid rgb(64%, 64%, 64%);
+}
+
+#script-resource-views {
+    display: block;
+    overflow: auto;
+    padding: 0;
+    position: absolute;
+    top: 23px;
+    left: 0;
+    right: 225px;
+    bottom: 0;
+}
+
+.script-view {
+    display: none;
+    overflow: hidden;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+
+.script-view.visible {
+    display: block;
+}
+
+#scripts-sidebar {
+    position: absolute;
+    top: 23px;
+    right: 0;
+    bottom: 0;
+    width: 225px;
+    background-color: rgb(245, 245, 245);
+    border-left: 1px solid rgb(64%, 64%, 64%);
+    cursor: default;
+    overflow: auto;
+}
+
+.resources-larger-resources-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/largerResourcesButtonGlyph.png);
+}
+
+#resources-filter {
+    height: 24px;
+    padding: 2px 10px 0;
+    background: -webkit-gradient(linear, left top, left bottom, from(rgb(233, 233, 233)), to(rgb(207, 207, 207)));
+    border-bottom: 1px solid rgb(177, 177, 177);
+    overflow: hidden;
+}
+
+#console-filter {
+    height: 24px;
+    padding: 2px 10px 0;
+    overflow: hidden;
+}
+
+#resources-filter li, #console-filter li {
+    display: inline-block;
+    margin: 1px 1px 0 0;
+    padding: 0 6px 3px;
+    font-size: 12px;
+    line-height: 12px;
+    font-weight: bold;
+    color: rgb(40, 40, 40);
+    border: 1px solid transparent;
+    border-bottom: 0;
+    background: transparent;
+    -webkit-border-radius: 8px;
+    text-shadow: rgba(255, 255, 255, 0.5) 1px 1px 0;
+}
+
+#resources-filter li.selected, #resources-filter li:hover, #resources-filter li:active,
+#console-filter li.selected, #console-filter li:hover, #console-filter li:active {
+    color: white;
+    text-shadow: rgb(80, 80, 80) 1px 1px 1px;
+    background: rgba(20, 20, 20, 0.4);
+    border-color: rgba(20, 20, 20, 0.2);
+    -webkit-box-shadow: 0 1px 0px rgba(255, 255, 255, 0.5);
+}
+
+#resources-filter li:hover,
+#console-filter li:hover {
+    background: rgba(20, 20, 20, 0.4);
+    border-color: transparent;
+    -webkit-box-shadow: none;
+}
+
+#resources-filter li:active,
+#console-filter li:active {
+    background: rgba(20, 20, 20, 0.6);
+}
+
+#resources-container {
+    position: absolute;
+    top: 24px;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    border-right: 0 none transparent;
+    overflow-y: auto;
+    overflow-x: hidden;
+}
+
+#resources-container.viewing-resource {
+    right: auto;
+    width: 200px;
+    border-right: 1px solid rgb(64%, 64%, 64%);
+}
+
+#resources-container.viewing-resource #resources-sidebar {
+    width: 100%;
+    border-right: 0 none transparent;
+}
+
+#resources-sidebar {
+    min-height: 100%;
+    bottom: auto;
+    overflow: visible;
+}
+
+#resources-container-content {
+    position: absolute;
+    top: 0;
+    right: 0;
+    left: 200px;
+    min-height: 100%;
+}
+
+#resources-container.viewing-resource #resources-container-content {
+    display: none;
+}
+
+#resources-summary {
+    position: absolute;
+    padding-top: 20px;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 93px;
+    margin-left: -1px;
+    border-left: 1px solid rgb(102, 102, 102);
+    background-color: rgb(101, 111, 130);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.5)));
+    background-repeat: repeat-x;
+    background-position: bottom;
+    text-align: center;
+    text-shadow: black 0 1px 1px;
+    white-space: nowrap;
+    color: white;
+    -webkit-background-size: 1px 6px;
+    -webkit-background-origin: padding;
+    -webkit-background-clip: padding;
+}
+
+.summary-graph-legend {
+    margin-top: -10px;
+    padding-left: 15px;
+}
+
+.summary-graph-legend-item {
+    display: inline-block;
+    font-weight: bold;
+    margin-right: 15px;
+    vertical-align: top;
+}
+
+.summary-graph-legend-item.total {
+    margin-left: 10px;
+}
+
+.summary-graph-legend-label {
+    display: inline-block;
+    text-align: left;
+}
+
+.summary-graph-legend-header {
+    font-size: 12px;
+}
+
+.summary-graph-legend-value {
+    font-size: 10px;
+}
+
+.summary-graph-legend-swatch {
+    vertical-align: top;
+    margin-top: 1px;
+    margin-right: 3px;
+}
+
+#resources-dividers {
+    position: absolute;
+    left: 0;
+    right: 0;
+    height: 100%;
+    top: 0;
+    z-index: -100;
+}
+
+#resources-dividers-label-bar {
+    position: absolute;
+    top: 93px;
+    left: 0px;
+    right: 0;
+    background-color: rgba(255, 255, 255, 0.8);
+    background-clip: padding;
+    border-bottom: 1px solid rgba(0, 0, 0, 0.3);
+    height: 20px;
+    z-index: 200;
+}
+
+.resources-divider {
+    position: absolute;
+    width: 1px;
+    top: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.1);
+}
+
+.resources-divider.last {
+    background-color: transparent;
+}
+
+.resources-divider-label {
+    position: absolute;
+    top: 4px;
+    right: 3px;
+    font-size: 9px;
+    color: rgb(50%, 50%, 50%);
+    white-space: nowrap;
+}
+
+.resources-graph-label {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    margin: auto -7px;
+    height: 13px;
+    line-height: 13px;
+    font-size: 9px;
+    color: rgba(0, 0, 0, 0.75);
+    text-shadow: rgba(255, 255, 255, 0.25) 1px 0 0, rgba(255, 255, 255, 0.25) -1px 0 0, rgba(255, 255, 255, 0.333) 0 1px 0, rgba(255, 255, 255, 0.25) 0 -1px 0;
+    z-index: 150;
+    overflow: hidden;
+    text-align: center;
+    font-weight: bold;
+    opacity: 0;
+    -webkit-transition: opacity 250ms ease-in-out;
+}
+
+.resources-graph-side:hover .resources-graph-label {
+    opacity: 1;
+}
+
+.resources-graph-label:empty {
+    display: none;
+}
+
+.resources-graph-label.waiting {
+    margin-right: 5px;
+}
+
+.resources-graph-label.before {
+    color: rgba(0, 0, 0, 0.7);
+    text-shadow: none;
+    text-align: right;
+    margin-right: 2px;
+}
+
+.resources-graph-label.before::after {
+    padding-left: 2px;
+    height: 6px;
+    content: url(Images/graphLabelCalloutLeft.png);
+}
+
+.resources-graph-label.after {
+    color: rgba(0, 0, 0, 0.7);
+    text-shadow: none;
+    text-align: left;
+    margin-left: 2px;
+}
+
+.resources-graph-label.after::before {
+    padding-right: 2px;
+    height: 6px;
+    content: url(Images/graphLabelCalloutRight.png);
+}
+
+.resources-graph-bar {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    margin: auto -7px;
+    border-width: 6px 7px;
+    height: 13px;
+    min-width: 14px;
+    opacity: 0.65;
+    -webkit-border-image: url(Images/timelinePillGray.png) 6 7 6 7;
+}
+
+.resources-category-documents, .resources-category-stylesheets, .resources-category-images,
+.resources-category-scripts, .resources-category-xhr, .resources-category-fonts, .resources-category-other {
+    display: none;
+}
+
+.filter-all .resources-category-documents, .filter-documents .resources-category-documents,
+.filter-all .resources-category-stylesheets, .filter-stylesheets .resources-category-stylesheets,
+.filter-all .resources-category-images, .filter-images .resources-category-images,
+.filter-all .resources-category-scripts, .filter-scripts .resources-category-scripts,
+.filter-all .resources-category-xhr, .filter-xhr .resources-category-xhr,
+.filter-all .resources-category-fonts, .filter-fonts .resources-category-fonts,
+.filter-all .resources-category-other, .filter-other .resources-category-other,
+.resource-sidebar-tree-item.selected {
+    display: list-item;
+}
+
+.console-warning-level, .console-error-level, .console-log-level {
+    display: none;
+}
+
+.filter-all .console-warning-level, .filter-warnings .console-warning-level,
+.filter-all .console-error-level, .filter-errors .console-error-level,
+.filter-all .console-log-level, .filter-logs .console-log-level {
+    display: block;
+}
+
+.resources-graph-bar.waiting {
+    opacity: 0.35;
+}
+
+.resource-cached .resources-graph-bar {
+    -webkit-border-image: url(Images/timelineHollowPillGray.png) 6 7 6 7;
+}
+
+.resources-category-documents .resources-graph-bar {
+    -webkit-border-image: url(Images/timelinePillBlue.png) 6 7 6 7;
+}
+
+.resources-category-documents.resource-cached .resources-graph-bar {
+    -webkit-border-image: url(Images/timelineHollowPillBlue.png) 6 7 6 7;
+}
+
+.resources-category-stylesheets .resources-graph-bar {
+    -webkit-border-image: url(Images/timelinePillGreen.png) 6 7 6 7;
+}
+
+.resources-category-stylesheets.resource-cached .resources-graph-bar {
+    -webkit-border-image: url(Images/timelineHollowPillGreen.png) 6 7 6 7;
+}
+
+.resources-category-images .resources-graph-bar {
+    -webkit-border-image: url(Images/timelinePillPurple.png) 6 7 6 7;
+}
+
+.resources-category-images.resource-cached .resources-graph-bar {
+    -webkit-border-image: url(Images/timelineHollowPillPurple.png) 6 7 6 7;
+}
+
+.resources-category-fonts .resources-graph-bar {
+    -webkit-border-image: url(Images/timelinePillRed.png) 6 7 6 7;
+}
+
+.resources-category-fonts.resource-cached .resources-graph-bar {
+    -webkit-border-image: url(Images/timelineHollowPillRed.png) 6 7 6 7;
+}
+
+.resources-category-scripts .resources-graph-bar {
+    -webkit-border-image: url(Images/timelinePillOrange.png) 6 7 6 7;
+}
+
+.resources-category-scripts.resource-cached .resources-graph-bar {
+    -webkit-border-image: url(Images/timelineHollowPillOrange.png) 6 7 6 7;
+}
+
+.resources-category-xhr .resources-graph-bar {
+    -webkit-border-image: url(Images/timelinePillYellow.png) 6 7 6 7;
+}
+
+.resources-category-xhr.resource-cached .resources-graph-bar {
+    -webkit-border-image: url(Images/timelineHollowPillYellow.png) 6 7 6 7;
+}
+
+.tip-button {
+    background-image: url(Images/tipIcon.png);
+    border: none;
+    width: 16px;
+    height: 16px;
+    float: right;
+    background-color: transparent;
+    margin-top: 1px;
+}
+
+.tip-button:active {
+    background-image: url(Images/tipIconPressed.png);
+}
+
+.tip-balloon {
+    position: absolute;
+    left: 145px;
+    top: -5px;
+    z-index: 1000;
+    border-width: 51px 15px 18px 37px;
+    -webkit-border-image: url(Images/tipBalloon.png) 51 15 18 37;
+    width: 265px;
+}
+
+.tip-balloon.bottom {
+    position: absolute;
+    left: 145px;
+    top: auto;
+    bottom: -7px;
+    z-index: 1000;
+    border-width: 18px 15px 51px 37px;
+    -webkit-border-image: url(Images/tipBalloonBottom.png) 18 15 51 37;
+}
+
+.tip-balloon-content {
+    margin-top: -40px;
+    margin-bottom: -2px;
+    margin-left: 2px;
+}
+
+.tip-balloon.bottom .tip-balloon-content {
+    margin-top: -10px;
+    margin-bottom: -35px;
+}
+
+#resource-views {
+    position: absolute;
+    top: 24px;
+    right: 0;
+    left: 200px;
+    bottom: 0;
+}
+
+.source-view-frame {
+    width: 100%;
+    height: 100%;
+}
+
+.sidebar-resizer-vertical {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    width: 5px;
+    z-index: 500;
+    cursor: col-resize;
+}
+
+.resources .sidebar-resizer-vertical {
+    top: 24px;
+}
+
+.sidebar-tree, .sidebar-tree .children {
+    position: relative;
+    padding: 0;
+    margin: 0;
+    list-style: none;
+    font-size: 11px;
+}
+
+.sidebar-tree-section {
+    position: relative;
+    height: 18px;
+    padding: 4px 10px 6px 10px;
+    white-space: nowrap;
+    margin-top: 1px;
+    color: rgb(92, 110, 129);
+    font-weight: bold;
+    text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0;
+}
+
+.sidebar-tree-item {
+    position: relative;
+    height: 36px;
+    padding: 0 5px 0 5px;
+    white-space: nowrap;
+    margin-top: 1px;
+    line-height: 34px;
+    border-top: 1px solid transparent;
+}
+
+.sidebar-tree .children {
+    display: none;
+}
+
+.sidebar-tree .children.expanded {
+    display: block;
+}
+
+.sidebar-tree-section + .children > .sidebar-tree-item {
+    padding-left: 10px !important;
+}
+
+.sidebar-tree-section + .children.small > .sidebar-tree-item {
+    padding-left: 17px !important;
+}
+
+.sidebar-tree > .children > .sidebar-tree-item {
+    padding-left: 37px;
+}
+
+.sidebar-tree > .children > .children > .sidebar-tree-item {
+    padding-left: 37px;
+}
+
+.sidebar-tree.hide-disclosure-buttons > .children {
+    display: none;
+}
+
+.sidebar-tree > .children.hide-disclosure-buttons > .children {
+    display: none;
+}
+
+.sidebar-tree.some-expandable:not(.hide-disclosure-buttons) > .sidebar-tree-item:not(.parent) .icon {
+    margin-left: 16px;
+}
+
+.sidebar-tree-item .disclosure-button {
+    float: left;
+    width: 16px;
+    height: 100%;
+    border: 0;
+    background-color: transparent;
+    background-image: url(Images/disclosureTriangleSmallRight.png);
+    background-repeat: no-repeat;
+    background-position: center;
+    -webkit-apearance: none;
+}
+
+.sidebar-tree.hide-disclosure-buttons .sidebar-tree-item .disclosure-button {
+    display: none;
+}
+
+body.inactive .sidebar-tree-item .disclosure-button {
+    background-image: url(Images/disclosureTriangleSmallRightBlack.png);
+}
+
+body.inactive .sidebar-tree-item.expanded .disclosure-button {
+    background-image: url(Images/disclosureTriangleSmallDownBlack.png);
+}
+
+body.inactive .sidebar-tree-item .disclosure-button:active {
+    background-image: url(Images/disclosureTriangleSmallRightDownBlack.png);
+}
+
+.sidebar-tree-item.selected .disclosure-button {
+    background-image: url(Images/disclosureTriangleSmallRightWhite.png) !important;
+}
+
+.sidebar-tree-item.expanded .disclosure-button {
+    background-image: url(Images/disclosureTriangleSmallDown.png);
+}
+
+.sidebar-tree-item.selected.expanded .disclosure-button {
+    background-image: url(Images/disclosureTriangleSmallDownWhite.png) !important;
+}
+
+.sidebar-tree-item.selected .disclosure-button:active {
+    background-image: url(Images/disclosureTriangleSmallRightDownWhite.png) !important;
+}
+
+.sidebar-tree-item .disclosure-button:active {
+    background-image: url(Images/disclosureTriangleSmallRightDown.png);
+}
+
+.sidebar-tree-item .icon {
+    float: left;
+    width: 32px;
+    height: 32px;
+    margin-top: 1px;
+    margin-right: 3px;
+}
+
+.sidebar-tree-item .status {
+    float: right;
+    height: 16px;
+    margin-top: 9px;
+    margin-left: 4px;
+    line-height: 1em;
+}
+
+.sidebar-tree-item .status:empty {
+    display: none;
+}
+
+.sidebar-tree-item .status .bubble {
+    display: inline-block;
+    height: 14px;
+    min-width: 16px;
+    margin-top: 1px;
+    background-color: rgb(128, 151, 189);
+    vertical-align: middle;
+    white-space: nowrap;
+    padding: 1px 4px;
+    text-align: center;
+    font-size: 11px;
+    font-family: Helvetia, Arial, sans-serif;
+    font-weight: bold;
+    text-shadow: none;
+    color: white;
+    -webkit-border-radius: 7px;
+}
+
+.sidebar-tree-item .status .bubble:empty {
+    display: none;
+}
+
+.sidebar-tree-item.selected .status .bubble {
+    background-color: white !important;
+    color: rgb(132, 154, 190) !important;
+}
+
+:focus .sidebar-tree-item.selected .status .bubble {
+    color: rgb(36, 98, 172) !important;
+}
+
+body.inactive .sidebar-tree-item.selected .status .bubble {
+    color: rgb(159, 159, 159) !important;
+}
+
+.sidebar-tree.small .sidebar-tree-item, .sidebar-tree .children.small .sidebar-tree-item, .sidebar-tree-item.small, .small .resources-graph-side {
+    height: 20px;
+}
+
+.sidebar-tree.small .sidebar-tree-item .icon, .sidebar-tree .children.small .sidebar-tree-item .icon, .sidebar-tree-item.small .icon {
+    width: 16px;
+    height: 16px;
+}
+
+.sidebar-tree.small .sidebar-tree-item .status, .sidebar-tree .children.small .sidebar-tree-item .status, .sidebar-tree-item.small .status {
+    margin-top: 1px;
+}
+
+.sidebar-tree-item.selected {
+    color: white;
+    border-top: 1px solid rgb(145, 160, 192);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(162, 177, 207)), to(rgb(120, 138, 177)));
+    text-shadow: rgba(0, 0, 0, 0.33) 0 1px 0;
+    font-weight: bold;
+    -webkit-background-origin: padding;
+    -webkit-background-clip: padding;
+}
+
+:focus .sidebar-tree-item.selected {
+    border-top: 1px solid rgb(68, 128, 200);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(92, 147, 213)), to(rgb(21, 83, 170)));
+}
+
+body.inactive .sidebar-tree-item.selected {
+    border-top: 1px solid rgb(151, 151, 151);
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(180, 180, 180)), to(rgb(138, 138, 138)));
+}
+
+.sidebar-tree-item .titles {
+    position: relative;
+    top: 5px;
+    line-height: 11px;
+    padding-bottom: 1px;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    white-space: nowrap;
+}
+
+.sidebar-tree-item .titles.no-subtitle {
+    top: 10px;
+}
+
+.sidebar-tree.small .sidebar-tree-item .titles, .sidebar-tree .children.small .sidebar-tree-item .titles, .sidebar-tree-item.small .titles {
+    top: 2px;
+    line-height: normal;
+}
+
+.sidebar-tree:not(.small) .sidebar-tree-item:not(.small) .title::after, .sidebar-tree .children:not(.small) .sidebar-tree-item .title::after {
+    content: "\A";
+    white-space: pre;
+}
+
+.sidebar-tree-item .subtitle {
+    font-size: 9px;
+    color: rgba(0, 0, 0, 0.7);
+}
+
+.sidebar-tree.small .sidebar-tree-item .subtitle, .sidebar-tree .children.small .sidebar-tree-item .subtitle, .sidebar-tree-item.small .subtitle {
+    display: none;
+}
+
+.sidebar-tree-item.selected .subtitle {
+    color: rgba(255, 255, 255, 0.9);
+}
+
+#resources-graphs {
+    position: absolute;
+    left: 0;
+    right: 0;
+    max-height: 100%;
+    top: 112px;
+}
+
+.resources-graph-side {
+    position: relative;
+    height: 36px;
+    padding: 0 5px;
+    white-space: nowrap;
+    margin-top: 1px;
+    border-top: 1px solid transparent;
+    overflow: hidden;
+}
+
+.resources-graph-bar-area {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    right: 8px;
+    left: 9px;
+}
+
+#resources-container:not(.viewing-resource) .resource-sidebar-tree-item:nth-of-type(2n) {
+    background-color: rgba(0, 0, 0, 0.05);
+}
+
+#resources-container:not(.viewing-resource) .resources-graph-side:nth-of-type(2n) {
+    background-color: rgba(0, 0, 0, 0.05);
+}
+
+.resources-time-graph-sidebar-item .icon {
+    content: url(Images/resourcesTimeGraphIcon.png);
+}
+
+.resources-size-graph-sidebar-item .icon {
+    content: url(Images/resourcesSizeGraphIcon.png);
+}
+
+.resources-size-graph-sidebar-item .icon {
+    content: url(Images/resourcesSizeGraphIcon.png);
+}
+
+.resource-sidebar-tree-item .icon {
+    content: url(Images/resourcePlainIcon.png);
+}
+
+.children.small .resource-sidebar-tree-item .icon {
+    content: url(Images/resourcePlainIconSmall.png);
+}
+
+.resource-sidebar-tree-item.resources-category-documents .icon {
+    content: url(Images/resourceDocumentIcon.png);
+}
+
+.children.small .resource-sidebar-tree-item.resources-category-documents .icon {
+    content: url(Images/resourceDocumentIconSmall.png);
+}
+
+.resource-sidebar-tree-item.resources-category-stylesheets .icon {
+    content: url(Images/resourceCSSIcon.png);
+}
+
+.children.small .resource-sidebar-tree-item.resources-category-stylesheets .icon {
+    content: url(Images/resourceDocumentIconSmall.png);
+}
+
+.resource-sidebar-tree-item.resources-category-images .icon {
+    position: relative;
+    background-image: url(Images/resourcePlainIcon.png);
+    background-repeat: no-repeat;
+    content: "";
+}
+
+.resource-sidebar-tree-item.resources-category-images .image-resource-icon-preview {
+    position: absolute;
+    margin: auto;
+    top: 3px;
+    bottom: 4px;
+    left: 5px;
+    right: 5px;
+    max-width: 18px;
+    max-height: 21px;
+    min-width: 1px;
+    min-height: 1px;
+}
+
+.children.small .resource-sidebar-tree-item.resources-category-images .icon {
+    background-image: url(Images/resourcePlainIconSmall.png);
+    content: "";
+}
+
+.children.small .resource-sidebar-tree-item.resources-category-images .image-resource-icon-preview {
+    top: 2px;
+    bottom: 1px;
+    left: 3px;
+    right: 3px;
+    max-width: 8px;
+    max-height: 11px;
+}
+
+.resource-sidebar-tree-item.resources-category-fonts .icon {
+    content: url(Images/resourcePlainIcon.png);
+}
+
+.children.small .resource-sidebar-tree-item.resources-category-fonts .icon {
+    content: url(Images/resourcePlainIconSmall.png);
+}
+
+.resource-sidebar-tree-item.resources-category-scripts .icon {
+    content: url(Images/resourceJSIcon.png);
+}
+
+.children.small .resource-sidebar-tree-item.resources-category-scripts .icon {
+    content: url(Images/resourceDocumentIconSmall.png);
+}
+
+.resource-sidebar-tree-item.resources-category-xhr .icon {
+    content: url(Images/resourcePlainIcon.png);
+}
+
+.children.small .resource-sidebar-tree-item.resources-category-xhr .icon {
+    content: url(Images/resourceDocumentIconSmall.png);
+}
+
+.bubble.debug, .console-debug-level .bubble {
+    background-color: rgb(0, 0, 255) !important;
+}
+
+.bubble.warning, .console-warning-level .bubble {
+    background-color: rgb(232, 164, 0) !important;
+}
+
+.bubble.error, .console-error-level .bubble {
+    background-color: rgb(216, 35, 35) !important;
+}
+
+.bubble.search-matches {
+    background-image: url(Images/searchSmallWhite.png);
+    background-repeat: no-repeat;
+    background-position: 3px 2px;
+    padding-left: 13px !important;
+}
+
+.sidebar-tree-item.selected .bubble.search-matches {
+    background-image: url(Images/searchSmallBlue.png);
+}
+
+:focus .sidebar-tree-item.selected .bubble.search-matches {
+    background-image: url(Images/searchSmallBrightBlue.png);
+}
+
+body.inactive .sidebar-tree-item.selected .bubble.search-matches {
+    background-image: url(Images/searchSmallGray.png);
+}
+
+/* Profiler Style */
+
+#profile-views {
+    position: absolute;
+    top: 0;
+    right: 0;
+    left: 200px;
+    bottom: 0;
+}
+
+#profile-view-status-bar-items {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 200px;
+    overflow: hidden;
+    border-left: 1px solid rgb(184, 184, 184);
+    margin-left: -1px;
+}
+
+.profile-sidebar-tree-item .icon {
+    content: url(Images/profileIcon.png);
+}
+
+.profile-sidebar-tree-item.small .icon {
+    content: url(Images/profileSmallIcon.png);
+}
+
+.profile-group-sidebar-tree-item .icon {
+    content: url(Images/profileGroupIcon.png);
+}
+
+.profile-view {
+    display: none;
+    overflow: hidden;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+
+.profile-view.visible {
+    display: block;
+}
+
+.profile-view .data-grid {
+    border: none;
+    height: 100%;
+}
+
+.profile-view .data-grid th.average-column {
+    text-align: center;
+}
+
+.profile-view .data-grid td.average-column {
+    text-align: right;
+}
+
+.profile-view .data-grid th.self-column {
+    text-align: center;
+}
+
+.profile-view .data-grid td.self-column {
+    text-align: right;
+}
+
+.profile-view .data-grid th.total-column {
+    text-align: center;
+}
+
+.profile-view .data-grid td.total-column {
+    text-align: right;
+}
+
+.profile-view .data-grid .calls-column {
+    text-align: center;
+}
+
+.profile-node-file {
+    float: right;
+    color: gray;
+    margin-top: -1px;
+}
+
+.data-grid tr.selected .profile-node-file {
+    color: rgb(33%, 33%, 33%);
+}
+
+.data-grid:focus tr.selected .profile-node-file {
+    color: white;
+}
+
+button.enable-toggle-status-bar-item .glyph {
+}
+
+.record-profile-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/recordButtonGlyph.png);
+}
+
+.record-profile-status-bar-item.toggled-on .glyph {
+    -webkit-mask-image: url(Images/recordToggledButtonGlyph.png);
+    background-color: rgb(216, 0, 0) !important;
+}
+
+/* FIXME: should have its own glyph. */
+.heap-snapshot-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/focusButtonGlyph.png);
+}
+
+.node-search-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/nodeSearchButtonGlyph.png);
+}
+
+.percent-time-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/percentButtonGlyph.png);
+}
+
+.focus-profile-node-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/focusButtonGlyph.png);
+}
+
+.exclude-profile-node-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/excludeButtonGlyph.png);
+}
+
+.reset-profile-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/reloadButtonGlyph.png);
+}
+
+.delete-storage-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/excludeButtonGlyph.png);
+}
+
+#storage-view-status-bar-items {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 200px;
+    overflow: hidden;
+    border-left: 1px solid rgb(184, 184, 184);
+    margin-left: -1px;
+}
+
+.refresh-storage-status-bar-item .glyph {
+    -webkit-mask-image: url(Images/reloadButtonGlyph.png);
+}
+
+#storage-view-status-bar-items {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 200px;
+    overflow: hidden;
+    border-left: 1px solid rgb(184, 184, 184);
+    margin-left: -1px;
+}
+
+ol.breakpoint-list {
+    -webkit-padding-start: 2px;
+    list-style: none;
+    margin: 0;
+}
+
+.breakpoint-list li {
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    margin: 4px 0;
+}
+
+.breakpoint-list .checkbox-elem {
+    font-size: 10px;
+    margin: 0 4px;
+    vertical-align: top;
+    position: relative;
+    z-index: 1;
+}
+
+.breakpoint-list .source-text {
+    font-family: monospace;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    margin: 2px 0 0px 20px;
+}
+
+.breakpoint-list a {
+    color: rgb(33%, 33%, 33%);
+    cursor: pointer;
+}
+
+.breakpoint-list a:hover {
+    color: rgb(15%, 15%, 15%);
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.html b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.html
new file mode 100644
index 0000000..7f544fe2
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.html
@@ -0,0 +1,112 @@
+<!--
+Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1.  Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+2.  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.
+3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+-->
+<!DOCTYPE html>
+<html>
+<head>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8">
+    <link rel="stylesheet" type="text/css" href="inspector.css">
+    <script type="text/javascript" src="utilities.js"></script>
+    <script type="text/javascript" src="treeoutline.js"></script>
+    <script type="text/javascript" src="inspector.js"></script>
+    <script type="text/javascript" src="Object.js"></script>
+    <script type="text/javascript" src="KeyboardShortcut.js"></script>
+    <script type="text/javascript" src="TextPrompt.js"></script>
+    <script type="text/javascript" src="Popup.js"></script>
+    <script type="text/javascript" src="Placard.js"></script>
+    <script type="text/javascript" src="View.js"></script>
+    <script type="text/javascript" src="Callback.js"></script>
+    <script type="text/javascript" src="Drawer.js"></script>
+    <script type="text/javascript" src="ChangesView.js"></script>
+    <script type="text/javascript" src="ConsoleView.js"></script>
+    <script type="text/javascript" src="Resource.js"></script>
+    <script type="text/javascript" src="ResourceCategory.js"></script>
+    <script type="text/javascript" src="Database.js"></script>
+    <script type="text/javascript" src="DOMStorage.js"></script>
+    <script type="text/javascript" src="DOMStorageItemsView.js"></script>
+    <script type="text/javascript" src="DataGrid.js"></script>
+    <script type="text/javascript" src="DOMStorageDataGrid.js"></script>
+    <script type="text/javascript" src="CookieItemsView.js"></script>
+    <script type="text/javascript" src="Script.js"></script>
+    <script type="text/javascript" src="Breakpoint.js"></script>
+    <script type="text/javascript" src="SidebarPane.js"></script>
+    <script type="text/javascript" src="ElementsTreeOutline.js"></script>
+    <script type="text/javascript" src="SidebarTreeElement.js"></script>
+    <script type="text/javascript" src="PropertiesSection.js"></script>
+    <script type="text/javascript" src="ObjectProxy.js"></script>
+    <script type="text/javascript" src="ObjectPropertiesSection.js"></script>
+    <script type="text/javascript" src="BreakpointsSidebarPane.js"></script>
+    <script type="text/javascript" src="CallStackSidebarPane.js"></script>
+    <script type="text/javascript" src="ScopeChainSidebarPane.js"></script>
+    <script type="text/javascript" src="WatchExpressionsSidebarPane.js"></script>
+    <script type="text/javascript" src="MetricsSidebarPane.js"></script>
+    <script type="text/javascript" src="PropertiesSidebarPane.js"></script>
+    <script type="text/javascript" src="Color.js"></script>
+    <script type="text/javascript" src="StylesSidebarPane.js"></script>
+    <script type="text/javascript" src="Panel.js"></script>
+    <script type="text/javascript" src="PanelEnablerView.js"></script>
+    <script type="text/javascript" src="StatusBarButton.js"></script>
+    <script type="text/javascript" src="SummaryBar.js"></script>
+    <script type="text/javascript" src="ElementsPanel.js"></script>
+    <script type="text/javascript" src="ResourcesPanel.js"></script>
+    <script type="text/javascript" src="ScriptsPanel.js"></script>
+    <script type="text/javascript" src="StoragePanel.js"></script>
+    <script type="text/javascript" src="ProfilesPanel.js"></script>
+    <script type="text/javascript" src="ResourceView.js"></script>
+    <script type="text/javascript" src="SourceFrame.js"></script>
+    <script type="text/javascript" src="SourceView.js"></script>
+    <script type="text/javascript" src="FontView.js"></script>
+    <script type="text/javascript" src="ImageView.js"></script>
+    <script type="text/javascript" src="DatabaseTableView.js"></script>
+    <script type="text/javascript" src="DatabaseQueryView.js"></script>
+    <script type="text/javascript" src="ScriptView.js"></script>
+    <script type="text/javascript" src="ProfileDataGridTree.js"></script>
+    <script type="text/javascript" src="BottomUpProfileDataGridTree.js"></script>
+    <script type="text/javascript" src="TopDownProfileDataGridTree.js"></script>
+    <script type="text/javascript" src="ProfileView.js"></script>
+    <script type="text/javascript" src="DOMAgent.js"></script>
+    <script type="text/javascript" src="InjectedScript.js"></script>
+    <script type="text/javascript" src="InjectedScriptAccess.js"></script>
+    <script type="text/javascript" src="TimelineAgent.js"></script>
+</head>
+<body class="detached">
+    <div id="toolbar">
+        <div class="toolbar-item close"><button id="close-button"></button></div>
+        <div class="toolbar-item flexable-space"></div>
+        <div class="toolbar-item hidden" id="search-results-matches"></div>
+        <div class="toolbar-item"><input id="search" type="search" incremental results="0"><div id="search-toolbar-label" class="toolbar-label"></div></div>
+    </div>
+    <div id="main">
+        <div id="main-panels" tabindex="0" spellcheck="false"></div>
+        <div id="main-status-bar" class="status-bar"><div id="anchored-status-bar-items"><button id="dock-status-bar-item" class="status-bar-item toggled"><div class="glyph"></div><div class="glyph shadow"></div></button><button id="console-status-bar-item" class="status-bar-item"><div class="glyph"></div><div class="glyph shadow"></div></button><button id="changes-status-bar-item" class="status-bar-item hidden"></button><div id="count-items"><div id="changes-count" class="hidden"></div><div id="error-warning-count" class="hidden"></div></div></div></div>
+    </div>
+    <div id="drawer">
+        <div id="console-view"><div id="console-messages"><div id="console-prompt" spellcheck="false"><br></div></div></div>
+        <div id="drawer-status-bar" class="status-bar"><div id="other-drawer-status-bar-items"><button id="clear-console-status-bar-item" class="status-bar-item"><div class="glyph"></div><div class="glyph shadow"></div></button><div id="console-filter" class="status-bar-item"></div></div></div>
+    </div>
+</body>
+</html>
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.js
new file mode 100644
index 0000000..902dd94
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.js
@@ -0,0 +1,1553 @@
+/*
+ * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2007 Matt Lilek ([email protected]).
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  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.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+var Preferences = {
+    ignoreWhitespace: true,
+    showUserAgentStyles: true,
+    maxInlineTextChildLength: 80,
+    minConsoleHeight: 75,
+    minSidebarWidth: 100,
+    minElementsSidebarWidth: 200,
+    minScriptsSidebarWidth: 200,
+    showInheritedComputedStyleProperties: false,
+    styleRulesExpandedState: {},
+    showMissingLocalizedStrings: false,
+    heapProfilerPresent: false,
+    samplingCPUProfiler: false,
+    showColorNicknames: true,
+    colorFormat: "hex"
+}
+
+var WebInspector = {
+    resources: {},
+    resourceURLMap: {},
+    missingLocalizedStrings: {},
+
+    get previousFocusElement()
+    {
+        return this._previousFocusElement;
+    },
+
+    get currentFocusElement()
+    {
+        return this._currentFocusElement;
+    },
+
+    set currentFocusElement(x)
+    {
+        if (this._currentFocusElement !== x)
+            this._previousFocusElement = this._currentFocusElement;
+        this._currentFocusElement = x;
+
+        if (this._currentFocusElement) {
+            this._currentFocusElement.focus();
+
+            // Make a caret selection inside the new element if there isn't a range selection and
+            // there isn't already a caret selection inside.
+            var selection = window.getSelection();
+            if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) {
+                var selectionRange = this._currentFocusElement.ownerDocument.createRange();
+                selectionRange.setStart(this._currentFocusElement, 0);
+                selectionRange.setEnd(this._currentFocusElement, 0);
+
+                selection.removeAllRanges();
+                selection.addRange(selectionRange);
+            }
+        } else if (this._previousFocusElement)
+            this._previousFocusElement.blur();
+    },
+
+    get currentPanel()
+    {
+        return this._currentPanel;
+    },
+
+    set currentPanel(x)
+    {
+        if (this._currentPanel === x)
+            return;
+
+        if (this._currentPanel)
+            this._currentPanel.hide();
+
+        this._currentPanel = x;
+
+        this.updateSearchLabel();
+
+        if (x) {
+            x.show();
+
+            if (this.currentQuery) {
+                if (x.performSearch) {
+                    function performPanelSearch()
+                    {
+                        this.updateSearchMatchesCount();
+
+                        x.currentQuery = this.currentQuery;
+                        x.performSearch(this.currentQuery);
+                    }
+
+                    // Perform the search on a timeout so the panel switches fast.
+                    setTimeout(performPanelSearch.bind(this), 0);
+                } else {
+                    // Update to show Not found for panels that can't be searched.
+                    this.updateSearchMatchesCount();
+                }
+            }
+        }
+
+        for (var panelName in WebInspector.panels) {
+            if (WebInspector.panels[panelName] == x)
+                InspectorController.storeLastActivePanel(panelName);
+        }
+    },
+
+    _createPanels: function()
+    {
+        var hiddenPanels = (InspectorController.hiddenPanels() || "").split(',');
+        if (hiddenPanels.indexOf("elements") === -1)
+            this.panels.elements = new WebInspector.ElementsPanel();
+        if (hiddenPanels.indexOf("resources") === -1)
+            this.panels.resources = new WebInspector.ResourcesPanel();
+        if (hiddenPanels.indexOf("scripts") === -1)
+            this.panels.scripts = new WebInspector.ScriptsPanel();
+        if (hiddenPanels.indexOf("profiles") === -1)
+            this.panels.profiles = new WebInspector.ProfilesPanel();
+        if (hiddenPanels.indexOf("storage") === -1 && hiddenPanels.indexOf("databases") === -1)
+            this.panels.storage = new WebInspector.StoragePanel();      
+    },
+
+    get attached()
+    {
+        return this._attached;
+    },
+
+    set attached(x)
+    {
+        if (this._attached === x)
+            return;
+
+        this._attached = x;
+
+        this.updateSearchLabel();
+
+        var dockToggleButton = document.getElementById("dock-status-bar-item");
+        var body = document.body;
+
+        if (x) {
+            InspectorController.attach();
+            body.removeStyleClass("detached");
+            body.addStyleClass("attached");
+            dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
+        } else {
+            InspectorController.detach();
+            body.removeStyleClass("attached");
+            body.addStyleClass("detached");
+            dockToggleButton.title = WebInspector.UIString("Dock to main window.");
+        }
+    },
+
+    get errors()
+    {
+        return this._errors || 0;
+    },
+
+    set errors(x)
+    {
+        x = Math.max(x, 0);
+
+        if (this._errors === x)
+            return;
+        this._errors = x;
+        this._updateErrorAndWarningCounts();
+    },
+
+    get warnings()
+    {
+        return this._warnings || 0;
+    },
+
+    set warnings(x)
+    {
+        x = Math.max(x, 0);
+
+        if (this._warnings === x)
+            return;
+        this._warnings = x;
+        this._updateErrorAndWarningCounts();
+    },
+
+    _updateErrorAndWarningCounts: function()
+    {
+        var errorWarningElement = document.getElementById("error-warning-count");
+        if (!errorWarningElement)
+            return;
+
+        if (!this.errors && !this.warnings) {
+            errorWarningElement.addStyleClass("hidden");
+            return;
+        }
+
+        errorWarningElement.removeStyleClass("hidden");
+
+        errorWarningElement.removeChildren();
+
+        if (this.errors) {
+            var errorElement = document.createElement("span");
+            errorElement.id = "error-count";
+            errorElement.textContent = this.errors;
+            errorWarningElement.appendChild(errorElement);
+        }
+
+        if (this.warnings) {
+            var warningsElement = document.createElement("span");
+            warningsElement.id = "warning-count";
+            warningsElement.textContent = this.warnings;
+            errorWarningElement.appendChild(warningsElement);
+        }
+
+        if (this.errors) {
+            if (this.warnings) {
+                if (this.errors == 1) {
+                    if (this.warnings == 1)
+                        errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings);
+                    else
+                        errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings);
+                } else if (this.warnings == 1)
+                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings);
+                else
+                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings);
+            } else if (this.errors == 1)
+                errorWarningElement.title = WebInspector.UIString("%d error", this.errors);
+            else
+                errorWarningElement.title = WebInspector.UIString("%d errors", this.errors);
+        } else if (this.warnings == 1)
+            errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings);
+        else if (this.warnings)
+            errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings);
+        else
+            errorWarningElement.title = null;
+    },
+
+    get styleChanges()
+    {
+        return this._styleChanges;
+    },
+
+    set styleChanges(x)
+    {
+        x = Math.max(x, 0);
+
+        if (this._styleChanges === x)
+            return;
+        this._styleChanges = x;
+        this._updateChangesCount();
+    },
+
+    _updateChangesCount: function()
+    {
+        // TODO: Remove immediate return when enabling the Changes Panel
+        return;
+
+        var changesElement = document.getElementById("changes-count");
+        if (!changesElement)
+            return;
+
+        if (!this.styleChanges) {
+            changesElement.addStyleClass("hidden");
+            return;
+        }
+
+        changesElement.removeStyleClass("hidden");
+        changesElement.removeChildren();
+
+        if (this.styleChanges) {
+            var styleChangesElement = document.createElement("span");
+            styleChangesElement.id = "style-changes-count";
+            styleChangesElement.textContent = this.styleChanges;
+            changesElement.appendChild(styleChangesElement);
+        }
+
+        if (this.styleChanges) {
+            if (this.styleChanges === 1)
+                changesElement.title = WebInspector.UIString("%d style change", this.styleChanges);
+            else
+                changesElement.title = WebInspector.UIString("%d style changes", this.styleChanges);
+        }
+    },
+
+    get hoveredDOMNode()
+    {
+        return this._hoveredDOMNode;
+    },
+
+    set hoveredDOMNode(x)
+    {
+        if (this._hoveredDOMNode === x)
+            return;
+
+        this._hoveredDOMNode = x;
+
+        if (this._hoveredDOMNode)
+            this._updateHoverHighlightSoon(this.showingDOMNodeHighlight ? 50 : 500);
+        else
+            this._updateHoverHighlight();
+    },
+
+    _updateHoverHighlightSoon: function(delay)
+    {
+        if ("_updateHoverHighlightTimeout" in this)
+            clearTimeout(this._updateHoverHighlightTimeout);
+        this._updateHoverHighlightTimeout = setTimeout(this._updateHoverHighlight.bind(this), delay);
+    },
+
+    _updateHoverHighlight: function()
+    {
+        if ("_updateHoverHighlightTimeout" in this) {
+            clearTimeout(this._updateHoverHighlightTimeout);
+            delete this._updateHoverHighlightTimeout;
+        }
+
+        if (this._hoveredDOMNode) {
+            InspectorController.highlightDOMNode(this._hoveredDOMNode.id);
+            this.showingDOMNodeHighlight = true;
+        } else {
+            InspectorController.hideDOMNodeHighlight();
+            this.showingDOMNodeHighlight = false;
+        }
+    }
+}
+
+WebInspector.loaded = function()
+{
+    var platform = InspectorController.platform();
+    document.body.addStyleClass("platform-" + platform);
+
+    var colorFormat = InspectorController.setting("color-format");
+    if (colorFormat)
+        Preferences.colorFormat = colorFormat;
+
+    this.drawer = new WebInspector.Drawer();
+    this.console = new WebInspector.ConsoleView(this.drawer);
+    // TODO: Uncomment when enabling the Changes Panel
+    // this.changes = new WebInspector.ChangesView(this.drawer);
+    // TODO: Remove class="hidden" from inspector.html on button#changes-status-bar-item
+    this.drawer.visibleView = this.console;
+    this.domAgent = new WebInspector.DOMAgent();
+
+    this.resourceCategories = {
+        documents: new WebInspector.ResourceCategory(WebInspector.UIString("Documents"), "documents"),
+        stylesheets: new WebInspector.ResourceCategory(WebInspector.UIString("Stylesheets"), "stylesheets"),
+        images: new WebInspector.ResourceCategory(WebInspector.UIString("Images"), "images"),
+        scripts: new WebInspector.ResourceCategory(WebInspector.UIString("Scripts"), "scripts"),
+        xhr: new WebInspector.ResourceCategory(WebInspector.UIString("XHR"), "xhr"),
+        fonts: new WebInspector.ResourceCategory(WebInspector.UIString("Fonts"), "fonts"),
+        other: new WebInspector.ResourceCategory(WebInspector.UIString("Other"), "other")
+    };
+
+    this.panels = {};
+    this._createPanels();
+
+    var toolbarElement = document.getElementById("toolbar");
+    var previousToolbarItem = toolbarElement.children[0];
+
+    this.panelOrder = [];
+    for (var panelName in this.panels) {
+        var panel = this.panels[panelName];
+        var panelToolbarItem = panel.toolbarItem;
+        this.panelOrder.push(panel);
+        panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this));
+        if (previousToolbarItem)
+            toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling);
+        else
+            toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild);
+        previousToolbarItem = panelToolbarItem;
+    }
+
+    this.Tips = {
+        ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")}
+    };
+
+    this.Warnings = {
+        IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")}
+    };
+
+    this.addMainEventListeners(document);
+
+    window.addEventListener("unload", this.windowUnload.bind(this), true);
+    window.addEventListener("resize", this.windowResize.bind(this), true);
+
+    document.addEventListener("focus", this.focusChanged.bind(this), true);
+    document.addEventListener("keydown", this.documentKeyDown.bind(this), true);
+    document.addEventListener("keyup", this.documentKeyUp.bind(this), true);
+    document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true);
+    document.addEventListener("copy", this.documentCopy.bind(this), true);
+    document.addEventListener("contextmenu", this.contextMenu.bind(this), true);
+
+    var mainPanelsElement = document.getElementById("main-panels");
+    mainPanelsElement.handleKeyEvent = this.mainKeyDown.bind(this);
+    mainPanelsElement.handleKeyUpEvent = this.mainKeyUp.bind(this);
+    mainPanelsElement.handleCopyEvent = this.mainCopy.bind(this);
+
+    // Focus the mainPanelsElement in a timeout so it happens after the initial focus,
+    // so it doesn't get reset to the first toolbar button. This initial focus happens
+    // on Mac when the window is made key and the WebHTMLView becomes the first responder.
+    setTimeout(function() { WebInspector.currentFocusElement = mainPanelsElement }, 0);
+
+    var dockToggleButton = document.getElementById("dock-status-bar-item");
+    dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false);
+
+    if (this.attached)
+        dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
+    else
+        dockToggleButton.title = WebInspector.UIString("Dock to main window.");
+
+    var errorWarningCount = document.getElementById("error-warning-count");
+    errorWarningCount.addEventListener("click", this.showConsole.bind(this), false);
+    this._updateErrorAndWarningCounts();
+
+    this.styleChanges = 0;
+    // TODO: Uncomment when enabling the Changes Panel
+    // var changesElement = document.getElementById("changes-count");
+    // changesElement.addEventListener("click", this.showChanges.bind(this), false);
+    // this._updateErrorAndWarningCounts();
+
+    var searchField = document.getElementById("search");
+    searchField.addEventListener("keydown", this.searchKeyDown.bind(this), false);
+    searchField.addEventListener("keyup", this.searchKeyUp.bind(this), false);
+    searchField.addEventListener("search", this.performSearch.bind(this), false); // when the search is emptied
+
+    document.getElementById("toolbar").addEventListener("mousedown", this.toolbarDragStart, true);
+    document.getElementById("close-button").addEventListener("click", this.close, true);
+
+    InspectorController.loaded();
+}
+
+var windowLoaded = function()
+{
+    var localizedStringsURL = InspectorController.localizedStringsURL();
+    if (localizedStringsURL) {
+        var localizedStringsScriptElement = document.createElement("script");
+        localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false);
+        localizedStringsScriptElement.type = "text/javascript";
+        localizedStringsScriptElement.src = localizedStringsURL;
+        document.getElementsByTagName("head").item(0).appendChild(localizedStringsScriptElement);
+    } else
+        WebInspector.loaded();
+
+    window.removeEventListener("load", windowLoaded, false);
+    delete windowLoaded;
+};
+
+window.addEventListener("load", windowLoaded, false);
+
+WebInspector.dispatch = function() {
+    var methodName = arguments[0];
+    var parameters = Array.prototype.slice.call(arguments, 1);
+    WebInspector[methodName].apply(this, parameters);
+}
+
+WebInspector.windowUnload = function(event)
+{
+    InspectorController.windowUnloading();
+}
+
+WebInspector.windowResize = function(event)
+{
+    if (this.currentPanel && this.currentPanel.resize)
+        this.currentPanel.resize();
+}
+
+WebInspector.windowFocused = function(event)
+{
+    if (event.target.nodeType === Node.DOCUMENT_NODE)
+        document.body.removeStyleClass("inactive");
+}
+
+WebInspector.windowBlured = function(event)
+{
+    if (event.target.nodeType === Node.DOCUMENT_NODE)
+        document.body.addStyleClass("inactive");
+}
+
+WebInspector.focusChanged = function(event)
+{
+    this.currentFocusElement = event.target;
+}
+
+WebInspector.setAttachedWindow = function(attached)
+{
+    this.attached = attached;
+}
+
+WebInspector.close = function(event)
+{
+    InspectorController.closeWindow();
+}
+
+WebInspector.documentClick = function(event)
+{
+    var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
+    if (!anchor)
+        return;
+
+    // Prevent the link from navigating, since we don't do any navigation by following links normally.
+    event.preventDefault();
+
+    function followLink()
+    {
+        // FIXME: support webkit-html-external-link links here.
+        if (anchor.href in WebInspector.resourceURLMap) {
+            if (anchor.hasStyleClass("webkit-html-external-link")) {
+                anchor.removeStyleClass("webkit-html-external-link");
+                anchor.addStyleClass("webkit-html-resource-link");
+            }
+
+            WebInspector.showResourceForURL(anchor.href, anchor.lineNumber, anchor.preferredPanel);
+        } else {
+            var profileStringRegEx = new RegExp("webkit-profile://.+/([0-9]+)");
+            var profileString = profileStringRegEx.exec(anchor.href);
+            if (profileString)
+                WebInspector.showProfileById(profileString[1])
+        }
+    }
+
+    if (WebInspector.followLinkTimeout)
+        clearTimeout(WebInspector.followLinkTimeout);
+
+    if (anchor.preventFollowOnDoubleClick) {
+        // Start a timeout if this is the first click, if the timeout is canceled
+        // before it fires, then a double clicked happened or another link was clicked.
+        if (event.detail === 1)
+            WebInspector.followLinkTimeout = setTimeout(followLink, 333);
+        return;
+    }
+
+    followLink();
+}
+
+WebInspector.documentKeyDown = function(event)
+{
+    if (!this.currentFocusElement)
+        return;
+    if (this.currentFocusElement.handleKeyEvent)
+        this.currentFocusElement.handleKeyEvent(event);
+    else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "KeyDown"])
+        WebInspector[this.currentFocusElement.id + "KeyDown"](event);
+
+    if (!event.handled) {
+        var isMac = InspectorController.platform().indexOf("mac-") === 0;
+
+        switch (event.keyIdentifier) {
+            case "U+001B": // Escape key
+                this.drawer.visible = !this.drawer.visible;
+                event.preventDefault();
+                break;
+
+            case "U+0046": // F key
+                if (isMac)
+                    var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
+                else
+                    var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey;
+
+                if (isFindKey) {
+                    var searchField = document.getElementById("search");
+                    searchField.focus();
+                    searchField.select();
+                    event.preventDefault();
+                }
+
+                break;
+
+            case "U+0047": // G key
+                if (isMac)
+                    var isFindAgainKey = event.metaKey && !event.ctrlKey && !event.altKey;
+                else
+                    var isFindAgainKey = event.ctrlKey && !event.metaKey && !event.altKey;
+
+                if (isFindAgainKey) {
+                    if (event.shiftKey) {
+                        if (this.currentPanel.jumpToPreviousSearchResult)
+                            this.currentPanel.jumpToPreviousSearchResult();
+                    } else if (this.currentPanel.jumpToNextSearchResult)
+                        this.currentPanel.jumpToNextSearchResult();
+                    event.preventDefault();
+                }
+
+                break;
+
+            case "U+005B": // [ key
+                if (isMac)
+                    var isRotateLeft = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
+                else
+                    var isRotateLeft = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
+
+                if (isRotateLeft) {
+                    var index = this.panelOrder.indexOf(this.currentPanel);
+                    index = (index === 0) ? this.panelOrder.length - 1 : index - 1;
+                    this.panelOrder[index].toolbarItem.click();
+                    event.preventDefault();
+                }
+
+                break;
+
+            case "U+005D": // ] key
+                if (isMac)
+                    var isRotateRight = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
+                else
+                    var isRotateRight = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
+
+                if (isRotateRight) {
+                    var index = this.panelOrder.indexOf(this.currentPanel);
+                    index = (index + 1) % this.panelOrder.length;
+                    this.panelOrder[index].toolbarItem.click();
+                    event.preventDefault();
+                }
+
+                break;
+        }
+    }
+}
+
+WebInspector.documentKeyUp = function(event)
+{
+    if (!this.currentFocusElement || !this.currentFocusElement.handleKeyUpEvent)
+        return;
+    this.currentFocusElement.handleKeyUpEvent(event);
+}
+
+WebInspector.documentCanCopy = function(event)
+{
+    if (!this.currentFocusElement)
+        return;
+    // Calling preventDefault() will say "we support copying, so enable the Copy menu".
+    if (this.currentFocusElement.handleCopyEvent)
+        event.preventDefault();
+    else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
+        event.preventDefault();
+}
+
+WebInspector.documentCopy = function(event)
+{
+    if (!this.currentFocusElement)
+        return;
+    if (this.currentFocusElement.handleCopyEvent)
+        this.currentFocusElement.handleCopyEvent(event);
+    else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
+        WebInspector[this.currentFocusElement.id + "Copy"](event);
+}
+
+WebInspector.contextMenu = function(event)
+{
+    if (event.handled || event.target.hasStyleClass("popup-glasspane"))
+        event.preventDefault();
+}
+
+WebInspector.mainKeyDown = function(event)
+{
+    if (this.currentPanel && this.currentPanel.handleKeyEvent)
+        this.currentPanel.handleKeyEvent(event);
+}
+
+WebInspector.mainKeyUp = function(event)
+{
+    if (this.currentPanel && this.currentPanel.handleKeyUpEvent)
+        this.currentPanel.handleKeyUpEvent(event);
+}
+
+WebInspector.mainCopy = function(event)
+{
+    if (this.currentPanel && this.currentPanel.handleCopyEvent)
+        this.currentPanel.handleCopyEvent(event);
+}
+
+WebInspector.animateStyle = function(animations, duration, callback, complete)
+{
+    if (complete === undefined)
+        complete = 0;
+    var slice = (1000 / 30); // 30 frames per second
+
+    var defaultUnit = "px";
+    var propertyUnit = {opacity: ""};
+
+    for (var i = 0; i < animations.length; ++i) {
+        var animation = animations[i];
+        var element = null;
+        var start = null;
+        var current = null;
+        var end = null;
+        var key = null;
+        for (key in animation) {
+            if (key === "element")
+                element = animation[key];
+            else if (key === "start")
+                start = animation[key];
+            else if (key === "current")
+                current = animation[key];
+            else if (key === "end")
+                end = animation[key];
+        }
+
+        if (!element || !end)
+            continue;
+
+        var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
+        if (!start) {
+            start = {};
+            for (key in end)
+                start[key] = parseInt(computedStyle.getPropertyValue(key));
+            animation.start = start;
+        } else if (complete == 0)
+            for (key in start)
+                element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
+
+        if (!current) {
+            current = {};
+            for (key in start)
+                current[key] = start[key];
+            animation.current = current;
+        }
+
+        function cubicInOut(t, b, c, d)
+        {
+            if ((t/=d/2) < 1) return c/2*t*t*t + b;
+            return c/2*((t-=2)*t*t + 2) + b;
+        }
+
+        var style = element.style;
+        for (key in end) {
+            var startValue = start[key];
+            var currentValue = current[key];
+            var endValue = end[key];
+            if ((complete + slice) < duration) {
+                var delta = (endValue - startValue) / (duration / slice);
+                var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
+                style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
+                current[key] = newValue;
+            } else {
+                style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
+            }
+        }
+    }
+
+    if (complete < duration)
+        setTimeout(WebInspector.animateStyle, slice, animations, duration, callback, complete + slice);
+    else if (callback)
+        callback();
+}
+
+WebInspector.updateSearchLabel = function()
+{
+    if (!this.currentPanel)
+        return;
+
+    var newLabel = WebInspector.UIString("Search %s", this.currentPanel.toolbarItemLabel);
+    if (this.attached)
+        document.getElementById("search").setAttribute("placeholder", newLabel);
+    else {
+        document.getElementById("search").removeAttribute("placeholder");
+        document.getElementById("search-toolbar-label").textContent = newLabel;
+    }
+}
+
+WebInspector.toggleAttach = function()
+{
+    this.attached = !this.attached;
+}
+
+WebInspector.toolbarDragStart = function(event)
+{
+    if (!WebInspector.attached && InspectorController.platform() !== "mac-leopard")
+        return;
+
+    var target = event.target;
+    if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable"))
+        return;
+
+    var toolbar = document.getElementById("toolbar");
+    if (target !== toolbar && !target.hasStyleClass("toolbar-item"))
+        return;
+
+    toolbar.lastScreenX = event.screenX;
+    toolbar.lastScreenY = event.screenY;
+
+    WebInspector.elementDragStart(toolbar, WebInspector.toolbarDrag, WebInspector.toolbarDragEnd, event, (WebInspector.attached ? "row-resize" : "default"));
+}
+
+WebInspector.toolbarDragEnd = function(event)
+{
+    var toolbar = document.getElementById("toolbar");
+
+    WebInspector.elementDragEnd(event);
+
+    delete toolbar.lastScreenX;
+    delete toolbar.lastScreenY;
+}
+
+WebInspector.toolbarDrag = function(event)
+{
+    var toolbar = document.getElementById("toolbar");
+
+    if (WebInspector.attached) {
+        var height = window.innerHeight - (event.screenY - toolbar.lastScreenY);
+
+        InspectorController.setAttachedWindowHeight(height);
+    } else {
+        var x = event.screenX - toolbar.lastScreenX;
+        var y = event.screenY - toolbar.lastScreenY;
+
+        // We cannot call window.moveBy here because it restricts the movement
+        // of the window at the edges.
+        InspectorController.moveByUnrestricted(x, y);
+    }
+
+    toolbar.lastScreenX = event.screenX;
+    toolbar.lastScreenY = event.screenY;
+
+    event.preventDefault();
+}
+
+WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor)
+{
+    if (this._elementDraggingEventListener || this._elementEndDraggingEventListener)
+        this.elementDragEnd(event);
+
+    this._elementDraggingEventListener = dividerDrag;
+    this._elementEndDraggingEventListener = elementDragEnd;
+
+    document.addEventListener("mousemove", dividerDrag, true);
+    document.addEventListener("mouseup", elementDragEnd, true);
+
+    document.body.style.cursor = cursor;
+
+    event.preventDefault();
+}
+
+WebInspector.elementDragEnd = function(event)
+{
+    document.removeEventListener("mousemove", this._elementDraggingEventListener, true);
+    document.removeEventListener("mouseup", this._elementEndDraggingEventListener, true);
+
+    document.body.style.removeProperty("cursor");
+
+    delete this._elementDraggingEventListener;
+    delete this._elementEndDraggingEventListener;
+
+    event.preventDefault();
+}
+
+WebInspector.showConsole = function()
+{
+    this.drawer.showView(this.console);
+}
+
+WebInspector.showChanges = function()
+{
+    this.drawer.showView(this.changes);
+}
+
+WebInspector.showElementsPanel = function()
+{
+    this.currentPanel = this.panels.elements;
+}
+
+WebInspector.showResourcesPanel = function()
+{
+    this.currentPanel = this.panels.resources;
+}
+
+WebInspector.showScriptsPanel = function()
+{
+    this.currentPanel = this.panels.scripts;
+}
+
+WebInspector.showProfilesPanel = function()
+{
+    this.currentPanel = this.panels.profiles;
+}
+
+WebInspector.showStoragePanel = function()
+{
+    this.currentPanel = this.panels.storage;
+}
+
+WebInspector.addResource = function(identifier, payload)
+{
+    var resource = new WebInspector.Resource(
+        payload.requestHeaders,
+        payload.requestURL,
+        payload.host,
+        payload.path,
+        payload.lastPathComponent,
+        identifier,
+        payload.isMainResource,
+        payload.cached,
+        payload.requestMethod,
+        payload.requestFormData);
+    this.resources[identifier] = resource;
+    this.resourceURLMap[resource.url] = resource;
+
+    if (resource.mainResource) {
+        this.mainResource = resource;
+        this.panels.elements.reset();
+    }
+
+    if (this.panels.resources)
+        this.panels.resources.addResource(resource);
+}
+
+WebInspector.clearConsoleMessages = function()
+{
+    WebInspector.console.clearMessages(false);
+}
+
+WebInspector.selectDatabase = function(o)
+{
+    WebInspector.showStoragePanel();
+    WebInspector.panels.storage.selectDatabase(o);
+}
+
+WebInspector.selectDOMStorage = function(o)
+{
+    WebInspector.showStoragePanel();
+    WebInspector.panels.storage.selectDOMStorage(o);
+}
+
+WebInspector.updateResource = function(identifier, payload)
+{
+    var resource = this.resources[identifier];
+    if (!resource)
+        return;
+
+    if (payload.didRequestChange) {
+        resource.url = payload.url;
+        resource.domain = payload.domain;
+        resource.path = payload.path;
+        resource.lastPathComponent = payload.lastPathComponent;
+        resource.requestHeaders = payload.requestHeaders;
+        resource.mainResource = payload.mainResource;
+        resource.requestMethod = payload.requestMethod;
+        resource.requestFormData = payload.requestFormData;
+    }
+
+    if (payload.didResponseChange) {
+        resource.mimeType = payload.mimeType;
+        resource.suggestedFilename = payload.suggestedFilename;
+        resource.expectedContentLength = payload.expectedContentLength;
+        resource.statusCode = payload.statusCode;
+        resource.suggestedFilename = payload.suggestedFilename;
+        resource.responseHeaders = payload.responseHeaders;
+    }
+
+    if (payload.didTypeChange) {
+        resource.type = payload.type;
+    }
+
+    if (payload.didLengthChange) {
+        resource.contentLength = payload.contentLength;
+    }
+
+    if (payload.didCompletionChange) {
+        resource.failed = payload.failed;
+        resource.finished = payload.finished;
+    }
+
+    if (payload.didTimingChange) {
+        if (payload.startTime)
+            resource.startTime = payload.startTime;
+        if (payload.responseReceivedTime)
+            resource.responseReceivedTime = payload.responseReceivedTime;
+        if (payload.endTime)
+            resource.endTime = payload.endTime;
+    }
+}
+
+WebInspector.removeResource = function(identifier)
+{
+    var resource = this.resources[identifier];
+    if (!resource)
+        return;
+
+    resource.category.removeResource(resource);
+    delete this.resourceURLMap[resource.url];
+    delete this.resources[identifier];
+
+    if (this.panels.resources)
+        this.panels.resources.removeResource(resource);
+}
+
+WebInspector.addDatabase = function(payload)
+{
+    var database = new WebInspector.Database(
+        payload.database,
+        payload.domain,
+        payload.name,
+        payload.version);
+    this.panels.storage.addDatabase(database);
+}
+
+WebInspector.addDOMStorage = function(payload)
+{
+    var domStorage = new WebInspector.DOMStorage(
+        payload.domStorage,
+        payload.host,
+        payload.isLocalStorage);
+    this.panels.storage.addDOMStorage(domStorage);
+}
+
+WebInspector.resourceTrackingWasEnabled = function()
+{
+    this.panels.resources.resourceTrackingWasEnabled();
+}
+
+WebInspector.resourceTrackingWasDisabled = function()
+{
+    this.panels.resources.resourceTrackingWasDisabled();
+}
+
+WebInspector.attachDebuggerWhenShown = function()
+{
+    this.panels.scripts.attachDebuggerWhenShown();
+}
+
+WebInspector.debuggerWasEnabled = function()
+{
+    this.panels.scripts.debuggerWasEnabled();
+}
+
+WebInspector.debuggerWasDisabled = function()
+{
+    this.panels.scripts.debuggerWasDisabled();
+}
+
+WebInspector.profilerWasEnabled = function()
+{
+    this.panels.profiles.profilerWasEnabled();
+}
+
+WebInspector.profilerWasDisabled = function()
+{
+    this.panels.profiles.profilerWasDisabled();
+}
+
+WebInspector.parsedScriptSource = function(sourceID, sourceURL, source, startingLine)
+{
+    this.panels.scripts.addScript(sourceID, sourceURL, source, startingLine);
+}
+
+WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLine, errorLine, errorMessage)
+{
+    this.panels.scripts.addScript(null, sourceURL, source, startingLine, errorLine, errorMessage);
+}
+
+WebInspector.pausedScript = function(callFrames)
+{
+    this.panels.scripts.debuggerPaused(callFrames);
+}
+
+WebInspector.resumedScript = function()
+{
+    this.panels.scripts.debuggerResumed();
+}
+
+WebInspector.populateInterface = function()
+{
+    for (var panelName in this.panels) {
+        var panel = this.panels[panelName];
+        if ("populateInterface" in panel)
+            panel.populateInterface();
+    }
+}
+
+WebInspector.reset = function()
+{
+    for (var panelName in this.panels) {
+        var panel = this.panels[panelName];
+        if ("reset" in panel)
+            panel.reset();
+    }
+
+    for (var category in this.resourceCategories)
+        this.resourceCategories[category].removeAllResources();
+
+    this.resources = {};
+    this.resourceURLMap = {};
+    this.hoveredDOMNode = null;
+
+    delete this.mainResource;
+
+    this.console.clearMessages();
+}
+
+WebInspector.resourceURLChanged = function(resource, oldURL)
+{
+    delete this.resourceURLMap[oldURL];
+    this.resourceURLMap[resource.url] = resource;
+}
+
+WebInspector.addMessageToConsole = function(payload)
+{
+    var consoleMessage = new WebInspector.ConsoleMessage(
+        payload.source,
+        payload.type,
+        payload.level,
+        payload.line,
+        payload.url,
+        payload.groupLevel,
+        payload.repeatCount);
+    consoleMessage.setMessageBody(Array.prototype.slice.call(arguments, 1));
+    this.console.addMessage(consoleMessage);
+}
+
+WebInspector.log = function(message)
+{
+    var msg = new WebInspector.ConsoleMessage(
+        WebInspector.ConsoleMessage.MessageSource.Other,
+        WebInspector.ConsoleMessage.MessageType.Log,
+        WebInspector.ConsoleMessage.MessageLevel.Debug,
+        -1,
+        null,
+        null,
+        1,
+        message);
+    this.console.addMessage(msg);
+}
+
+WebInspector.addProfile = function(profile)
+{
+    this.panels.profiles.addProfile(profile);
+}
+
+WebInspector.setRecordingProfile = function(isProfiling)
+{
+    this.panels.profiles.setRecordingProfile(isProfiling);
+}
+
+WebInspector.drawLoadingPieChart = function(canvas, percent) {
+    var g = canvas.getContext("2d");
+    var darkColor = "rgb(122, 168, 218)";
+    var lightColor = "rgb(228, 241, 251)";
+    var cx = 8;
+    var cy = 8;
+    var r = 7;
+
+    g.beginPath();
+    g.arc(cx, cy, r, 0, Math.PI * 2, false);
+    g.closePath();
+
+    g.lineWidth = 1;
+    g.strokeStyle = darkColor;
+    g.fillStyle = lightColor;
+    g.fill();
+    g.stroke();
+
+    var startangle = -Math.PI / 2;
+    var endangle = startangle + (percent * Math.PI * 2);
+
+    g.beginPath();
+    g.moveTo(cx, cy);
+    g.arc(cx, cy, r, startangle, endangle, false);
+    g.closePath();
+
+    g.fillStyle = darkColor;
+    g.fill();
+}
+
+WebInspector.updateFocusedNode = function(nodeId)
+{
+    var node = WebInspector.domAgent.nodeForId(nodeId);
+    if (!node)
+        // FIXME: Should we deselect if null is passed in?
+        return;
+
+    this.currentPanel = this.panels.elements;
+    this.panels.elements.focusedDOMNode = node;
+}
+
+WebInspector.displayNameForURL = function(url)
+{
+    if (!url)
+        return "";
+    var resource = this.resourceURLMap[url];
+    if (resource)
+        return resource.displayName;
+    return url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : "");
+}
+
+WebInspector.resourceForURL = function(url)
+{
+    if (url in this.resourceURLMap)
+        return this.resourceURLMap[url];
+
+    // No direct match found. Search for resources that contain
+    // a substring of the URL.
+    for (var resourceURL in this.resourceURLMap) {
+        if (resourceURL.hasSubstring(url))
+            return this.resourceURLMap[resourceURL];
+    }
+
+    return null;
+}
+
+WebInspector.showResourceForURL = function(url, line, preferredPanel)
+{
+    var resource = this.resourceForURL(url);
+    if (!resource)
+        return false;
+
+    if (preferredPanel && preferredPanel in WebInspector.panels) {
+        var panel = this.panels[preferredPanel];
+        if (!("showResource" in panel))
+            panel = null;
+        else if ("canShowResource" in panel && !panel.canShowResource(resource))
+            panel = null;
+    }
+
+    this.currentPanel = panel || this.panels.resources;
+    if (!this.currentPanel)
+        return false;
+    this.currentPanel.showResource(resource, line);
+    return true;
+}
+
+WebInspector.linkifyStringAsFragment = function(string)
+{
+    var container = document.createDocumentFragment();
+    var linkStringRegEx = new RegExp("(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}://|www\\.)[\\w$\\-_+*'=\\|/\\\\(){}[\\]%@&#~,:;.!?]{2,}[\\w$\\-_+*=\\|/\\\\({%@&#~]");
+
+    while (string) {
+        var linkString = linkStringRegEx.exec(string);
+        if (!linkString)
+            break;
+
+        linkString = linkString[0];
+        var title = linkString;
+        var linkIndex = string.indexOf(linkString);
+        var nonLink = string.substring(0, linkIndex);
+        container.appendChild(document.createTextNode(nonLink));
+
+        var profileStringRegEx = new RegExp("webkit-profile://(.+)/[0-9]+");
+        var profileStringMatches = profileStringRegEx.exec(title);
+        var profileTitle;
+        if (profileStringMatches)
+            profileTitle = profileStringMatches[1];
+        if (profileTitle)
+            title = WebInspector.panels.profiles.displayTitleForProfileLink(profileTitle);
+
+        var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString);
+        container.appendChild(WebInspector.linkifyURLAsNode(realURL, title, null, (realURL in WebInspector.resourceURLMap)));
+        string = string.substring(linkIndex + linkString.length, string.length);
+    }
+
+    if (string)
+        container.appendChild(document.createTextNode(string));
+
+    return container;
+}
+
+WebInspector.showProfileById = function(uid) {
+    WebInspector.showProfilesPanel();
+    WebInspector.panels.profiles.showProfileById(uid);
+}
+
+WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal)
+{
+    if (!linkText)
+        linkText = url;
+    classes = (classes ? classes + " " : "");
+    classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";
+
+    var a = document.createElement("a");
+    a.href = url;
+    a.className = classes;
+    a.title = url;
+    a.target = "_blank";
+    a.textContent = linkText;
+
+    return a;
+}
+
+WebInspector.linkifyURL = function(url, linkText, classes, isExternal)
+{
+    // Use the DOM version of this function so as to avoid needing to escape attributes.
+    // FIXME:  Get rid of linkifyURL entirely.
+    return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal).outerHTML;
+}
+
+WebInspector.addMainEventListeners = function(doc)
+{
+    doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), true);
+    doc.defaultView.addEventListener("blur", this.windowBlured.bind(this), true);
+    doc.addEventListener("click", this.documentClick.bind(this), true);
+}
+
+WebInspector.searchKeyDown = function(event)
+{
+    if (event.keyIdentifier !== "Enter")
+        return;
+
+    // Call preventDefault since this was the Enter key. This prevents a "search" event
+    // from firing for key down. We handle the Enter key on key up in searchKeyUp. This
+    // stops performSearch from being called twice in a row.
+    event.preventDefault();
+}
+
+WebInspector.searchKeyUp = function(event)
+{
+    if (event.keyIdentifier !== "Enter")
+        return;
+
+    // Select all of the text so the user can easily type an entirely new query.
+    event.target.select();
+
+    // Only call performSearch if the Enter key was pressed. Otherwise the search
+    // performance is poor because of searching on every key. The search field has
+    // the incremental attribute set, so we still get incremental searches.
+    this.performSearch(event);
+}
+
+WebInspector.performSearch = function(event)
+{
+    var query = event.target.value;
+    var forceSearch = event.keyIdentifier === "Enter";
+
+    if (!query || !query.length || (!forceSearch && query.length < 3)) {
+        delete this.currentQuery;
+
+        for (var panelName in this.panels) {
+            var panel = this.panels[panelName];
+            if (panel.currentQuery && panel.searchCanceled)
+                panel.searchCanceled();
+            delete panel.currentQuery;
+        }
+
+        this.updateSearchMatchesCount();
+
+        return;
+    }
+
+    if (query === this.currentPanel.currentQuery && this.currentPanel.currentQuery === this.currentQuery) {
+        // When this is the same query and a forced search, jump to the next
+        // search result for a good user experience.
+        if (forceSearch && this.currentPanel.jumpToNextSearchResult)
+            this.currentPanel.jumpToNextSearchResult();
+        return;
+    }
+
+    this.currentQuery = query;
+
+    this.updateSearchMatchesCount();
+
+    if (!this.currentPanel.performSearch)
+        return;
+
+    this.currentPanel.currentQuery = query;
+    this.currentPanel.performSearch(query);
+}
+
+WebInspector.addNodesToSearchResult = function(nodeIds)
+{
+    WebInspector.panels.elements.addNodesToSearchResult(nodeIds);
+}
+
+WebInspector.updateSearchMatchesCount = function(matches, panel)
+{
+    if (!panel)
+        panel = this.currentPanel;
+
+    panel.currentSearchMatches = matches;
+
+    if (panel !== this.currentPanel)
+        return;
+
+    if (!this.currentPanel.currentQuery) {
+        document.getElementById("search-results-matches").addStyleClass("hidden");
+        return;
+    }
+
+    if (matches) {
+        if (matches === 1)
+            var matchesString = WebInspector.UIString("1 match");
+        else
+            var matchesString = WebInspector.UIString("%d matches", matches);
+    } else
+        var matchesString = WebInspector.UIString("Not Found");
+
+    var matchesToolbarElement = document.getElementById("search-results-matches");
+    matchesToolbarElement.removeStyleClass("hidden");
+    matchesToolbarElement.textContent = matchesString;
+}
+
+WebInspector.UIString = function(string)
+{
+    if (window.localizedStrings && string in window.localizedStrings)
+        string = window.localizedStrings[string];
+    else {
+        if (!(string in this.missingLocalizedStrings)) {
+            console.error("Localized string \"" + string + "\" not found.");
+            this.missingLocalizedStrings[string] = true;
+        }
+
+        if (Preferences.showMissingLocalizedStrings)
+            string += " (not localized)";
+    }
+
+    return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
+}
+
+WebInspector.isBeingEdited = function(element)
+{
+    return element.__editing;
+}
+
+WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context)
+{
+    if (element.__editing)
+        return;
+    element.__editing = true;
+
+    var oldText = getContent(element);
+    var oldHandleKeyEvent = element.handleKeyEvent;
+    var moveDirection = "";
+
+    element.addStyleClass("editing");
+
+    var oldTabIndex = element.tabIndex;
+    if (element.tabIndex < 0)
+        element.tabIndex = 0;
+
+    function blurEventListener() {
+        editingCommitted.call(element);
+    }
+
+    function getContent(element) {
+        if (element.tagName === "INPUT" && element.type === "text")
+            return element.value;
+        else
+            return element.textContent;
+    }
+
+    function cleanUpAfterEditing() {
+        delete this.__editing;
+
+        this.removeStyleClass("editing");
+        this.tabIndex = oldTabIndex;
+        this.scrollTop = 0;
+        this.scrollLeft = 0;
+
+        this.handleKeyEvent = oldHandleKeyEvent;
+        element.removeEventListener("blur", blurEventListener, false);
+
+        if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement))
+            WebInspector.currentFocusElement = WebInspector.previousFocusElement;
+    }
+
+    function editingCancelled() {
+        if (this.tagName === "INPUT" && this.type === "text")
+            this.value = oldText;
+        else
+            this.textContent = oldText;
+
+        cleanUpAfterEditing.call(this);
+
+        if (cancelledCallback)
+            cancelledCallback(this, context);
+    }
+
+    function editingCommitted() {
+        cleanUpAfterEditing.call(this);
+
+        if (committedCallback)
+            committedCallback(this, getContent(this), oldText, context, moveDirection);
+    }
+
+    element.handleKeyEvent = function(event) {
+        if (oldHandleKeyEvent)
+            oldHandleKeyEvent(event);
+        if (event.handled)
+            return;
+
+        if (event.keyIdentifier === "Enter") {
+            editingCommitted.call(element);
+            event.preventDefault();
+        } else if (event.keyCode === 27) { // Escape key
+            editingCancelled.call(element);
+            event.preventDefault();
+            event.handled = true;
+        } else if (event.keyIdentifier === "U+0009") // Tab key
+            moveDirection = (event.shiftKey ? "backward" : "forward");
+    }
+
+    element.addEventListener("blur", blurEventListener, false);
+
+    WebInspector.currentFocusElement = element;
+}
+
+WebInspector._toolbarItemClicked = function(event)
+{
+    var toolbarItem = event.currentTarget;
+    this.currentPanel = toolbarItem.panel;
+}
+
+// This table maps MIME types to the Resource.Types which are valid for them.
+// The following line:
+//    "text/html":                {0: 1},
+// means that text/html is a valid MIME type for resources that have type
+// WebInspector.Resource.Type.Document (which has a value of 0).
+WebInspector.MIMETypes = {
+    "text/html":                   {0: true},
+    "text/xml":                    {0: true},
+    "text/plain":                  {0: true},
+    "application/xhtml+xml":       {0: true},
+    "text/css":                    {1: true},
+    "text/xsl":                    {1: true},
+    "image/jpeg":                  {2: true},
+    "image/png":                   {2: true},
+    "image/gif":                   {2: true},
+    "image/bmp":                   {2: true},
+    "image/vnd.microsoft.icon":    {2: true},
+    "image/x-icon":                {2: true},
+    "image/x-xbitmap":             {2: true},
+    "font/ttf":                    {3: true},
+    "font/opentype":               {3: true},
+    "application/x-font-type1":    {3: true},
+    "application/x-font-ttf":      {3: true},
+    "application/x-truetype-font": {3: true},
+    "text/javascript":             {4: true},
+    "text/ecmascript":             {4: true},
+    "application/javascript":      {4: true},
+    "application/ecmascript":      {4: true},
+    "application/x-javascript":    {4: true},
+    "text/javascript1.1":          {4: true},
+    "text/javascript1.2":          {4: true},
+    "text/javascript1.3":          {4: true},
+    "text/jscript":                {4: true},
+    "text/livescript":             {4: true},
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller.js
new file mode 100644
index 0000000..383cba46
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller.js
@@ -0,0 +1,506 @@
+// Copyright (c) 2009 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 Stub implementation of the InspectorController API.
+ * This stub class is supposed to make front-end a standalone WebApp
+ * that can be implemented/refactored in isolation from the Web browser
+ * backend. Clients need to subclass it in order to wire calls to the
+ * non-stub backends.
+ */
+goog.provide('devtools.InspectorController');
+
+
+/**
+ * Creates inspector controller stub instance.
+ * @constructor.
+ */
+devtools.InspectorController = function() {
+  /**
+   * @type {boolean}
+   */
+  this.searchingForNode_ = false;
+
+  /**
+   * @type {boolean}
+   */
+  this.windowVisible_ = true;
+
+  /**
+   * @type {number}
+   */
+  this.attachedWindowHeight_ = 0;
+
+  /**
+   * @type {boolean}
+   */
+  this.debuggerEnabled_ = true;
+
+  /**
+   * @type {boolean}
+   */
+  this.profilerEnabled_ = true;
+
+  /**
+   * @type {boolean}
+   */
+  this.resourceTrackingEnabled_ = false;
+
+  /**
+   * @type {boolean}
+   */
+  this.timelineEnabled_ = false;
+
+  /**
+   * @type {Object}
+   */
+  this.settings_ = {};
+};
+
+
+/**
+ * Wraps javascript callback.
+ * @param {function():undefined} func The callback to wrap.
+ * @return {function():undefined} Callback wrapper.
+ */
+devtools.InspectorController.prototype.wrapCallback = function f(func) {
+  // Just return as is.
+  return func;
+};
+
+
+/**
+ * @return {boolean} True iff inspector window is currently visible.
+ */
+devtools.InspectorController.prototype.isWindowVisible = function() {
+  return this.windowVisible_;
+};
+
+
+/**
+ * @return {string} Platform identifier.
+ */
+devtools.InspectorController.prototype.platform = function() {
+  return 'windows';
+};
+
+
+/**
+ * Closes inspector window.
+ */
+devtools.InspectorController.prototype.closeWindow =  function() {
+  this.windowVisible_ = false;
+};
+
+
+/**
+ * Attaches frontend to the backend.
+ */
+devtools.InspectorController.prototype.attach = function() {
+};
+
+
+/**
+ * Detaches frontend from the backend.
+ */
+devtools.InspectorController.prototype.detach = function() {
+};
+
+
+/**
+ * Tell host that the active panel has changed.
+ * @param {string} panel Panel name that was last active.
+ */
+devtools.InspectorController.prototype.storeLastActivePanel = function(panel) {
+};
+
+
+/**
+ * Clears console message log in the backend.
+ */
+devtools.InspectorController.prototype.clearMessages = function() {
+};
+
+
+/**
+ * Returns true iff browser is currently in the search for node mode.
+ * @return {boolean} True is currently searching for a node.
+ */
+devtools.InspectorController.prototype.searchingForNode = function() {
+  return this.searchingForNode_;
+};
+
+
+/**
+ * Initiates search for a given query starting on a given row.
+ * @param {number} sourceRow Row to start searching from.
+ * @param {string} query Query string for search for.
+ */
+devtools.InspectorController.prototype.search = function(sourceRow, query) {
+};
+
+
+/**
+ * Toggles node search mode on/off.
+ */
+devtools.InspectorController.prototype.toggleNodeSearch = function() {
+  this.searchingForNode_ = !this.searchingForNode_;
+};
+
+
+/**
+ * Sets the inspector window height while in the attached mode.
+ * @param {number} height Window height being set.
+ */
+devtools.InspectorController.prototype.setAttachedWindowHeight =
+    function(height) {
+  this.attachedWindowHeight_ = height;
+};
+
+
+/**
+ * Moves window by the given offset.
+ * @param {number} x X offset.
+ * @param {number} y Y offset.
+ */
+devtools.InspectorController.prototype.moveByUnrestricted = function(x, y) {
+};
+
+
+/**
+ * Adds resource with given identifier into the given iframe element.
+ * @param {number} identifier Identifier of the resource to add into the frame.
+ * @param {Element} element Element to add resource content to.
+ */
+devtools.InspectorController.prototype.addResourceSourceToFrame =
+    function(identifier, element) {
+};
+
+
+/**
+ * Adds given source of a given mimeType into the given iframe element.
+ * @param {string} mimeType MIME type of the content to be added.
+ * @param {string} source String content to be added.
+ * @param {Element} element Element to add resource content to.
+ */
+devtools.InspectorController.prototype.addSourceToFrame =
+    function(mimeType, source, element) {
+  return false;
+};
+
+
+/**
+ * Returns document node corresponding to the resource with given id.
+ * @return {Node} Node containing the resource.
+ */
+devtools.InspectorController.prototype.getResourceDocumentNode =
+    function(identifier) {
+  return undefined;
+};
+
+
+/**
+ * Highlights the given node on the page.
+ * @param {Node} node Node to highlight.
+ */
+devtools.InspectorController.prototype.highlightDOMNode = function(node) {
+  // Does nothing in stub.
+};
+
+
+/**
+ * Clears current highlight.
+ */
+devtools.InspectorController.prototype.hideDOMNodeHighlight = function() {
+  // Does nothing in stub.
+};
+
+
+/**
+ * @return {window} Inspectable window instance.
+ */
+devtools.InspectorController.prototype.inspectedWindow = function() {
+  return window;
+};
+
+
+/**
+ * Notifies backend that the frontend has been successfully loaded.
+ */
+devtools.InspectorController.prototype.loaded = function() {
+  // Does nothing in stub.
+};
+
+
+/**
+ * @return {string} Url of the i18n-ed strings map.
+ */
+devtools.InspectorController.prototype.localizedStringsURL = function() {
+  return undefined;
+};
+
+
+/**
+ * @return {boolean} True iff window is currently unloading.
+ */
+devtools.InspectorController.prototype.windowUnloading = function() {
+  return false;
+};
+
+
+/**
+ * @return {string} Identifiers of the panels that should be hidden.
+ */
+devtools.InspectorController.prototype.hiddenPanels = function() {
+  return '';
+};
+
+
+/**
+ * @return {boolean} True iff debugger is enabled.
+ */
+devtools.InspectorController.prototype.debuggerEnabled = function() {
+  return this.debuggerEnabled_;
+};
+
+
+/**
+ * Enables resource tracking.
+ */
+devtools.InspectorController.prototype.enableResourceTracking = function() {
+  this.resourceTrackingEnabled_ = true;
+  WebInspector.resourceTrackingWasEnabled();
+};
+
+
+/**
+ * Disables resource tracking.
+ */
+devtools.InspectorController.prototype.disableResourceTracking = function() {
+  this.resourceTrackingEnabled_ = false;
+  WebInspector.resourceTrackingWasDisabled();
+};
+
+
+/**
+ * @return {boolean} True iff resource tracking is enabled.
+ */
+devtools.InspectorController.prototype.resourceTrackingEnabled = function() {
+  return this.resourceTrackingEnabled_;
+};
+
+
+/**
+ * Enables timeline.
+ */
+devtools.InspectorController.prototype.enableTimeline = function() {
+  this.timelineEnabled_ = true;
+  WebInspector.timelineWasEnabled();
+};
+
+
+/**
+ * Disables timeline.
+ */
+devtools.InspectorController.prototype.disableTimeline = function() {
+  this.timelineEnabled_ = false;
+  WebInspector.timelineWasDisabled();
+};
+
+/**
+ * @return {boolean} True iff timeline is enabled.
+ */
+devtools.InspectorController.prototype.timelineEnabled = function() {
+  return this.timelineEnabled_;
+};
+
+
+/**
+ * Enables debugger.
+ */
+devtools.InspectorController.prototype.enableDebugger = function() {
+  this.debuggerEnabled_ = true;
+};
+
+
+/**
+ * Disables debugger.
+ */
+devtools.InspectorController.prototype.disableDebugger = function() {
+  this.debuggerEnabled_ = false;
+};
+
+
+/**
+ * Adds breakpoint to the given line of the source with given ID.
+ * @param {string} sourceID Source Id to add breakpoint to.
+ * @param {number} line Line number to add breakpoint to.
+ * @param {?string} condition The breakpoint condition.
+ */
+devtools.InspectorController.prototype.addBreakpoint =
+    function(sourceID, line, condition) {
+};
+
+
+/**
+ * Removes breakpoint from the given line of the source with given ID.
+ * @param {string} sourceID Source Id to remove breakpoint from.
+ * @param {number} line Line number to remove breakpoint from.
+ */
+devtools.InspectorController.prototype.removeBreakpoint =
+    function(sourceID, line) {
+};
+
+
+/**
+ * Sets a breakpoint condition given a line of the source and an ID.
+ * @param {string} sourceID Source Id to remove breakpoint from.
+ * @param {number} line Line number to remove breakpoint from.
+ * @param {?string} condition New breakpoint condition.
+ */
+devtools.InspectorController.prototype.updateBreakpoint =
+    function(sourceID, line, condition) {
+};
+
+
+/**
+ * Tells backend to pause in the debugger.
+ */
+devtools.InspectorController.prototype.pauseInDebugger = function() {
+  // Does nothing in stub.
+};
+
+
+/**
+ * @return {boolean} True iff the debugger will pause execution on the
+ * exceptions.
+ */
+devtools.InspectorController.prototype.pauseOnExceptions = function() {
+  // Does nothing in stub.
+  return false;
+};
+
+
+/**
+ * Tells whether to pause in the debugger on the exceptions or not.
+ * @param {boolean} value True iff execution should be stopped in the debugger
+ * on the exceptions.
+ */
+devtools.InspectorController.prototype.setPauseOnExceptions = function(value) {
+};
+
+
+/**
+ * Tells backend to resume execution.
+ */
+devtools.InspectorController.prototype.resumeDebugger = function() {
+};
+
+
+/**
+ * @return {boolean} True iff profiler is enabled.
+ */
+devtools.InspectorController.prototype.profilerEnabled = function() {
+  return true;
+};
+
+
+/**
+ * Enables profiler.
+ */
+devtools.InspectorController.prototype.enableProfiler = function() {
+  this.profilerEnabled_ = true;
+};
+
+
+/**
+ * Disables profiler.
+ */
+devtools.InspectorController.prototype.disableProfiler = function() {
+  this.profilerEnabled_ = false;
+};
+
+
+/**
+ * Returns given callframe while on a debugger break.
+ * @return {Object} Current call frame.
+ */
+devtools.InspectorController.prototype.currentCallFrame = function() {
+  return undefined;
+};
+
+
+/**
+ * Tells backend to start collecting profiler data.
+ */
+devtools.InspectorController.prototype.startProfiling = function() {
+};
+
+
+/**
+ * Tells backend to stop collecting profiler data.
+ */
+devtools.InspectorController.prototype.stopProfiling = function() {
+};
+
+
+/**
+ * @return {Array.<Object>} Profile snapshots array.
+ */
+devtools.InspectorController.prototype.profiles = function() {
+  return [];
+};
+
+
+/**
+ * @return {Array.<string>} Database table names available offline.
+ */
+devtools.InspectorController.prototype.databaseTableNames =
+    function(database) {
+  return [];
+};
+
+
+/**
+ * Tells backend to step into the function in debugger.
+ */
+devtools.InspectorController.prototype.stepIntoStatementInDebugger =
+    function() {
+};
+
+
+/**
+ * Tells backend to step out of the function in debugger.
+ */
+devtools.InspectorController.prototype.stepOutOfFunctionInDebugger =
+    function() {};
+
+
+/**
+ * Tells backend to step over the statement in debugger.
+ */
+devtools.InspectorController.prototype.stepOverStatementInDebugger =
+    function() {
+};
+
+
+/**
+ * Sets a setting value in backend.
+ */
+devtools.InspectorController.prototype.setSetting =
+    function(setting, value) {
+  this.settings_[setting] = value;
+};
+
+
+/**
+ * Retrieves a setting value stored in backend.
+ */
+devtools.InspectorController.prototype.setting =
+    function(setting) {
+  return this.settings_[setting];
+};
+
+
+var InspectorController = new devtools.InspectorController();
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller_impl.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller_impl.js
new file mode 100644
index 0000000..5bf19b7
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller_impl.js
@@ -0,0 +1,286 @@
+// Copyright (c) 2009 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 DevTools' implementation of the InspectorController API.
+ */
+goog.require('devtools.InspectorController');
+
+goog.provide('devtools.InspectorControllerImpl');
+
+devtools.InspectorControllerImpl = function() {
+  devtools.InspectorController.call(this);
+  this.frame_element_id_ = 1;
+
+  this.installInspectorControllerDelegate_('clearMessages');
+  this.installInspectorControllerDelegate_('storeLastActivePanel');
+  this.installInspectorControllerDelegate_('highlightDOMNode');
+  this.installInspectorControllerDelegate_('hideDOMNodeHighlight');
+  this.installInspectorControllerDelegate_('getChildNodes');
+  this.installInspectorControllerDelegate_('setAttribute');
+  this.installInspectorControllerDelegate_('removeAttribute');
+  this.installInspectorControllerDelegate_('setTextNodeValue');
+  this.installInspectorControllerDelegate_('enableResourceTracking');
+  this.installInspectorControllerDelegate_('disableResourceTracking');
+  this.installInspectorControllerDelegate_('enableTimeline');
+  this.installInspectorControllerDelegate_('disableTimeline');
+  this.installInspectorControllerDelegate_('setting');
+  this.installInspectorControllerDelegate_('setSetting');
+};
+goog.inherits(devtools.InspectorControllerImpl,
+    devtools.InspectorController);
+
+
+/**
+ * {@inheritDoc}.
+ */
+devtools.InspectorControllerImpl.prototype.platform = function() {
+  return DevToolsHost.getPlatform();
+};
+
+
+/**
+ * {@inheritDoc}.
+ */
+devtools.InspectorControllerImpl.prototype.closeWindow = function() {
+  DevToolsHost.closeWindow();
+};
+
+
+/**
+ * {@inheritDoc}.
+ */
+devtools.InspectorControllerImpl.prototype.attach = function() {
+  DevToolsHost.dockWindow();
+};
+
+
+/**
+ * {@inheritDoc}.
+ */
+devtools.InspectorControllerImpl.prototype.detach = function() {
+  DevToolsHost.undockWindow();
+};
+
+
+/**
+ * {@inheritDoc}.
+ */
+devtools.InspectorControllerImpl.prototype.hiddenPanels = function() {
+  return 'databases';
+};
+
+
+/**
+ * {@inheritDoc}.
+ */
+devtools.InspectorControllerImpl.prototype.search = function(sourceRow, query) {
+  return DevToolsHost.search(sourceRow, query);
+};
+
+
+/**
+ * {@inheritDoc}.
+ */
+devtools.InspectorControllerImpl.prototype.toggleNodeSearch = function() {
+  devtools.InspectorController.prototype.toggleNodeSearch.call(this);
+  DevToolsHost.toggleInspectElementMode(this.searchingForNode());
+};
+
+
+/**
+ * {@inheritDoc}.
+ */
+devtools.InspectorControllerImpl.prototype.localizedStringsURL =
+    function(opt_prefix) {
+  // l10n is turned off in test mode because delayed loading of strings
+  // causes test failures.
+  if (false) {
+    var locale = DevToolsHost.getApplicationLocale();
+    locale = locale.replace('_', '-');
+    return 'l10n/localizedStrings_' + locale + '.js';
+  } else {
+    return undefined;
+  }
+};
+
+
+/**
+ * {@inheritDoc}.
+ */
+devtools.InspectorControllerImpl.prototype.addSourceToFrame =
+    function(mimeType, source, element) {
+  return DevToolsHost.addSourceToFrame(mimeType, source, element);
+};
+
+
+/**
+ * {@inheritDoc}.
+ */
+devtools.InspectorControllerImpl.prototype.addResourceSourceToFrame =
+    function(identifier, element) {
+  var resource = WebInspector.resources[identifier];
+  if (!resource) {
+    return;
+  }
+  DevToolsHost.addResourceSourceToFrame(identifier, resource.mimeType, element);
+};
+
+
+/**
+ * {@inheritDoc}.
+ */
+devtools.InspectorControllerImpl.prototype.inspectedWindow = function() {
+  return null;
+};
+
+
+/**
+ * @override
+ */
+devtools.InspectorControllerImpl.prototype.debuggerEnabled = function() {
+  return true;
+};
+
+
+devtools.InspectorControllerImpl.prototype.addBreakpoint = function(
+    sourceID, line, condition) {
+  devtools.tools.getDebuggerAgent().addBreakpoint(sourceID, line, condition);
+};
+
+
+devtools.InspectorControllerImpl.prototype.removeBreakpoint = function(
+    sourceID, line) {
+  devtools.tools.getDebuggerAgent().removeBreakpoint(sourceID, line);
+};
+
+devtools.InspectorControllerImpl.prototype.updateBreakpoint = function(
+    sourceID, line, condition) {
+  devtools.tools.getDebuggerAgent().updateBreakpoint(
+      sourceID, line, condition);
+};
+
+devtools.InspectorControllerImpl.prototype.pauseInDebugger = function() {
+  devtools.tools.getDebuggerAgent().pauseExecution();
+};
+
+
+devtools.InspectorControllerImpl.prototype.resumeDebugger = function() {
+  devtools.tools.getDebuggerAgent().resumeExecution();
+};
+
+
+devtools.InspectorControllerImpl.prototype.stepIntoStatementInDebugger =
+    function() {
+  devtools.tools.getDebuggerAgent().stepIntoStatement();
+};
+
+
+devtools.InspectorControllerImpl.prototype.stepOutOfFunctionInDebugger =
+    function() {
+  devtools.tools.getDebuggerAgent().stepOutOfFunction();
+};
+
+
+devtools.InspectorControllerImpl.prototype.stepOverStatementInDebugger =
+    function() {
+  devtools.tools.getDebuggerAgent().stepOverStatement();
+};
+
+
+/**
+ * @override
+ */
+devtools.InspectorControllerImpl.prototype.pauseOnExceptions = function() {
+  return devtools.tools.getDebuggerAgent().pauseOnExceptions();
+};
+
+
+/**
+ * @override
+ */
+devtools.InspectorControllerImpl.prototype.setPauseOnExceptions = function(
+    value) {
+  return devtools.tools.getDebuggerAgent().setPauseOnExceptions(value);
+};
+
+
+/**
+ * @override
+ */
+devtools.InspectorControllerImpl.prototype.startProfiling = function() {
+  devtools.tools.getDebuggerAgent().startProfiling(
+      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_CPU);
+};
+
+
+/**
+ * @override
+ */
+devtools.InspectorControllerImpl.prototype.stopProfiling = function() {
+  devtools.tools.getDebuggerAgent().stopProfiling(
+      devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_CPU);
+};
+
+
+/**
+ * @override
+ */
+devtools.InspectorControllerImpl.prototype.evaluateInCallFrame =
+    function(callFrameId, code, callback) {
+  devtools.tools.getDebuggerAgent().evaluateInCallFrame(callFrameId, code,
+                                                        callback);
+};
+
+
+/**
+ * @override
+ */
+devtools.InspectorControllerImpl.prototype.dispatchOnInjectedScript = function(
+    callId, methodName, argsString) {
+  var callback = function(result, isException) {
+    WebInspector.didDispatchOnInjectedScript(callId,
+        isException ? result : JSON.parse(result),
+        isException);
+  };
+  RemoteToolsAgent.ExecuteUtilityFunction(
+      devtools.Callback.wrap(callback),
+      'InjectedScript',
+      JSON.stringify(['dispatch', methodName, argsString]));
+};
+
+
+/**
+ * Installs delegating handler into the inspector controller.
+ * @param {string} methodName Method to install delegating handler for.
+ */
+devtools.InspectorControllerImpl.prototype.installInspectorControllerDelegate_
+    = function(methodName) {
+  this[methodName] = goog.bind(this.callInspectorController_, this,
+      methodName);
+};
+
+
+/**
+ * Bound function with the installInjectedScriptDelegate_ actual
+ * implementation.
+ */
+devtools.InspectorControllerImpl.prototype.callInspectorController_ =
+    function(methodName, var_arg) {
+  var args = Array.prototype.slice.call(arguments);
+  RemoteToolsAgent.ExecuteUtilityFunction(
+      devtools.Callback.wrap(function(){}),
+      'InspectorController', JSON.stringify(args));
+};
+
+
+devtools.InspectorControllerImpl.parseWrap_ = function(callback) {
+  return devtools.Callback.wrap(
+      function(data) {
+        callback.call(this, JSON.parse(data));
+      });
+};
+
+
+InspectorController = new devtools.InspectorControllerImpl();
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/logreader.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/logreader.js
new file mode 100644
index 0000000..88ab9077
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/logreader.js
@@ -0,0 +1,320 @@
+// Copyright 2009 the V8 project 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.
+
+/**
+ * @fileoverview Log Reader is used to process log file produced by V8.
+ */
+
+// Initlialize namespaces
+var devtools = devtools || {};
+devtools.profiler = devtools.profiler || {};
+
+
+/**
+ * Base class for processing log files.
+ *
+ * @param {Array.<Object>} dispatchTable A table used for parsing and processing
+ *     log records.
+ * @constructor
+ */
+devtools.profiler.LogReader = function(dispatchTable) {
+  /**
+   * @type {Array.<Object>}
+   */
+  this.dispatchTable_ = dispatchTable;
+  this.dispatchTable_['alias'] =
+      { parsers: [null, null], processor: this.processAlias_ };
+  this.dispatchTable_['repeat'] =
+      { parsers: [parseInt, 'var-args'], processor: this.processRepeat_,
+        backrefs: true };
+
+  /**
+   * A key-value map for aliases. Translates short name -> full name.
+   * @type {Object}
+   */
+  this.aliases_ = {};
+
+  /**
+   * A key-value map for previous address values.
+   * @type {Object}
+   */
+  this.prevAddresses_ = {};
+
+  /**
+   * A key-value map for events than can be backreference-compressed.
+   * @type {Object}
+   */
+  this.backRefsCommands_ = {};
+  this.initBackRefsCommands_();
+
+  /**
+   * Back references for decompression.
+   * @type {Array.<string>}
+   */
+  this.backRefs_ = [];
+};
+
+
+/**
+ * Creates a parser for an address entry.
+ *
+ * @param {string} addressTag Address tag to perform offset decoding.
+ * @return {function(string):number} Address parser.
+ */
+devtools.profiler.LogReader.prototype.createAddressParser = function(
+    addressTag) {
+  var self = this;
+  return (function (str) {
+    var value = parseInt(str, 16);
+    var firstChar = str.charAt(0);
+    if (firstChar == '+' || firstChar == '-') {
+      var addr = self.prevAddresses_[addressTag];
+      addr += value;
+      self.prevAddresses_[addressTag] = addr;
+      return addr;
+    } else if (firstChar != '0' || str.charAt(1) != 'x') {
+      self.prevAddresses_[addressTag] = value;
+    }
+    return value;
+  });
+};
+
+
+/**
+ * Expands an alias symbol, if applicable.
+ *
+ * @param {string} symbol Symbol to expand.
+ * @return {string} Expanded symbol, or the input symbol itself.
+ */
+devtools.profiler.LogReader.prototype.expandAlias = function(symbol) {
+  return symbol in this.aliases_ ? this.aliases_[symbol] : symbol;
+};
+
+
+/**
+ * Used for printing error messages.
+ *
+ * @param {string} str Error message.
+ */
+devtools.profiler.LogReader.prototype.printError = function(str) {
+  // Do nothing.
+};
+
+
+/**
+ * Processes a portion of V8 profiler event log.
+ *
+ * @param {string} chunk A portion of log.
+ */
+devtools.profiler.LogReader.prototype.processLogChunk = function(chunk) {
+  this.processLog_(chunk.split('\n'));
+};
+
+
+/**
+ * Processes stack record.
+ *
+ * @param {number} pc Program counter.
+ * @param {Array.<string>} stack String representation of a stack.
+ * @return {Array.<number>} Processed stack.
+ */
+devtools.profiler.LogReader.prototype.processStack = function(pc, stack) {
+  var fullStack = [pc];
+  var prevFrame = pc;
+  for (var i = 0, n = stack.length; i < n; ++i) {
+    var frame = stack[i];
+    var firstChar = frame.charAt(0);
+    if (firstChar == '+' || firstChar == '-') {
+      // An offset from the previous frame.
+      prevFrame += parseInt(frame, 16);
+      fullStack.push(prevFrame);
+    // Filter out possible 'overflow' string.
+    } else if (firstChar != 'o') {
+      fullStack.push(parseInt(frame, 16));
+    }
+  }
+  return fullStack;
+};
+
+
+/**
+ * Returns whether a particular dispatch must be skipped.
+ *
+ * @param {!Object} dispatch Dispatch record.
+ * @return {boolean} True if dispatch must be skipped.
+ */
+devtools.profiler.LogReader.prototype.skipDispatch = function(dispatch) {
+  return false;
+};
+
+
+/**
+ * Does a dispatch of a log record.
+ *
+ * @param {Array.<string>} fields Log record.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.dispatchLogRow_ = function(fields) {
+  // Obtain the dispatch.
+  var command = fields[0];
+  if (!(command in this.dispatchTable_)) {
+    throw new Error('unknown command: ' + command);
+  }
+  var dispatch = this.dispatchTable_[command];
+
+  if (dispatch === null || this.skipDispatch(dispatch)) {
+    return;
+  }
+
+  // Parse fields.
+  var parsedFields = [];
+  for (var i = 0; i < dispatch.parsers.length; ++i) {
+    var parser = dispatch.parsers[i];
+    if (parser === null) {
+      parsedFields.push(fields[1 + i]);
+    } else if (typeof parser == 'function') {
+      parsedFields.push(parser(fields[1 + i]));
+    } else {
+      // var-args
+      parsedFields.push(fields.slice(1 + i));
+      break;
+    }
+  }
+
+  // Run the processor.
+  dispatch.processor.apply(this, parsedFields);
+};
+
+
+/**
+ * Decompresses a line if it was backreference-compressed.
+ *
+ * @param {string} line Possibly compressed line.
+ * @return {string} Decompressed line.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.expandBackRef_ = function(line) {
+  var backRefPos;
+  // Filter out case when a regexp is created containing '#'.
+  if (line.charAt(line.length - 1) != '"'
+      && (backRefPos = line.lastIndexOf('#')) != -1) {
+    var backRef = line.substr(backRefPos + 1);
+    var backRefIdx = parseInt(backRef, 10) - 1;
+    var colonPos = backRef.indexOf(':');
+    var backRefStart =
+        colonPos != -1 ? parseInt(backRef.substr(colonPos + 1), 10) : 0;
+    line = line.substr(0, backRefPos) +
+        this.backRefs_[backRefIdx].substr(backRefStart);
+  }
+  this.backRefs_.unshift(line);
+  if (this.backRefs_.length > 10) {
+    this.backRefs_.length = 10;
+  }
+  return line;
+};
+
+
+/**
+ * Initializes the map of backward reference compressible commands.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.initBackRefsCommands_ = function() {
+  for (var event in this.dispatchTable_) {
+    var dispatch = this.dispatchTable_[event];
+    if (dispatch && dispatch.backrefs) {
+      this.backRefsCommands_[event] = true;
+    }
+  }
+};
+
+
+/**
+ * Processes alias log record. Adds an alias to a corresponding map.
+ *
+ * @param {string} symbol Short name.
+ * @param {string} expansion Long name.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.processAlias_ = function(
+    symbol, expansion) {
+  if (expansion in this.dispatchTable_) {
+    this.dispatchTable_[symbol] = this.dispatchTable_[expansion];
+    if (expansion in this.backRefsCommands_) {
+      this.backRefsCommands_[symbol] = true;
+    }
+  } else {
+    this.aliases_[symbol] = expansion;
+  }
+};
+
+
+/**
+ * Processes log lines.
+ *
+ * @param {Array.<string>} lines Log lines.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.processLog_ = function(lines) {
+  var csvParser = new devtools.profiler.CsvParser();
+  try {
+    for (var i = 0, n = lines.length; i < n; ++i) {
+      var line = lines[i];
+      if (!line) {
+        continue;
+      }
+      if (line.charAt(0) == '#' ||
+          line.substr(0, line.indexOf(',')) in this.backRefsCommands_) {
+        line = this.expandBackRef_(line);
+      }
+      var fields = csvParser.parseLine(line);
+      this.dispatchLogRow_(fields);
+    }
+  } catch (e) {
+    // An error on the last line is acceptable since log file can be truncated.
+    if (i < n - 1) {
+      this.printError('line ' + (i + 1) + ': ' + (e.message || e));
+      throw e;
+    }
+  }
+};
+
+
+/**
+ * Processes repeat log record. Expands it according to calls count and
+ * invokes processing.
+ *
+ * @param {number} count Count.
+ * @param {Array.<string>} cmd Parsed command.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.processRepeat_ = function(count, cmd) {
+  // Replace the repeat-prefixed command from backrefs list with a non-prefixed.
+  this.backRefs_[0] = cmd.join(',');
+  for (var i = 0; i < count; ++i) {
+    this.dispatchLogRow_(cmd);
+  }
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile.js
new file mode 100644
index 0000000..db4b542f
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile.js
@@ -0,0 +1,621 @@
+// Copyright 2009 the V8 project 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.
+
+
+// Initlialize namespaces
+var devtools = devtools || {};
+devtools.profiler = devtools.profiler || {};
+
+
+/**
+ * Creates a profile object for processing profiling-related events
+ * and calculating function execution times.
+ *
+ * @constructor
+ */
+devtools.profiler.Profile = function() {
+  this.codeMap_ = new devtools.profiler.CodeMap();
+  this.topDownTree_ = new devtools.profiler.CallTree();
+  this.bottomUpTree_ = new devtools.profiler.CallTree();
+};
+
+
+/**
+ * Returns whether a function with the specified name must be skipped.
+ * Should be overriden by subclasses.
+ *
+ * @param {string} name Function name.
+ */
+devtools.profiler.Profile.prototype.skipThisFunction = function(name) {
+  return false;
+};
+
+
+/**
+ * Enum for profiler operations that involve looking up existing
+ * code entries.
+ *
+ * @enum {number}
+ */
+devtools.profiler.Profile.Operation = {
+  MOVE: 0,
+  DELETE: 1,
+  TICK: 2
+};
+
+
+/**
+ * Called whenever the specified operation has failed finding a function
+ * containing the specified address. Should be overriden by subclasses.
+ * See the devtools.profiler.Profile.Operation enum for the list of
+ * possible operations.
+ *
+ * @param {number} operation Operation.
+ * @param {number} addr Address of the unknown code.
+ * @param {number} opt_stackPos If an unknown address is encountered
+ *     during stack strace processing, specifies a position of the frame
+ *     containing the address.
+ */
+devtools.profiler.Profile.prototype.handleUnknownCode = function(
+    operation, addr, opt_stackPos) {
+};
+
+
+/**
+ * Registers a library.
+ *
+ * @param {string} name Code entry name.
+ * @param {number} startAddr Starting address.
+ * @param {number} endAddr Ending address.
+ */
+devtools.profiler.Profile.prototype.addLibrary = function(
+    name, startAddr, endAddr) {
+  var entry = new devtools.profiler.CodeMap.CodeEntry(
+      endAddr - startAddr, name);
+  this.codeMap_.addLibrary(startAddr, entry);
+  return entry;
+};
+
+
+/**
+ * Registers statically compiled code entry.
+ *
+ * @param {string} name Code entry name.
+ * @param {number} startAddr Starting address.
+ * @param {number} endAddr Ending address.
+ */
+devtools.profiler.Profile.prototype.addStaticCode = function(
+    name, startAddr, endAddr) {
+  var entry = new devtools.profiler.CodeMap.CodeEntry(
+      endAddr - startAddr, name);
+  this.codeMap_.addStaticCode(startAddr, entry);
+  return entry;
+};
+
+
+/**
+ * Registers dynamic (JIT-compiled) code entry.
+ *
+ * @param {string} type Code entry type.
+ * @param {string} name Code entry name.
+ * @param {number} start Starting address.
+ * @param {number} size Code entry size.
+ */
+devtools.profiler.Profile.prototype.addCode = function(
+    type, name, start, size) {
+  var entry = new devtools.profiler.Profile.DynamicCodeEntry(size, type, name);
+  this.codeMap_.addCode(start, entry);
+  return entry;
+};
+
+
+/**
+ * Reports about moving of a dynamic code entry.
+ *
+ * @param {number} from Current code entry address.
+ * @param {number} to New code entry address.
+ */
+devtools.profiler.Profile.prototype.moveCode = function(from, to) {
+  try {
+    this.codeMap_.moveCode(from, to);
+  } catch (e) {
+    this.handleUnknownCode(devtools.profiler.Profile.Operation.MOVE, from);
+  }
+};
+
+
+/**
+ * Reports about deletion of a dynamic code entry.
+ *
+ * @param {number} start Starting address.
+ */
+devtools.profiler.Profile.prototype.deleteCode = function(start) {
+  try {
+    this.codeMap_.deleteCode(start);
+  } catch (e) {
+    this.handleUnknownCode(devtools.profiler.Profile.Operation.DELETE, start);
+  }
+};
+
+
+/**
+ * Records a tick event. Stack must contain a sequence of
+ * addresses starting with the program counter value.
+ *
+ * @param {Array<number>} stack Stack sample.
+ */
+devtools.profiler.Profile.prototype.recordTick = function(stack) {
+  var processedStack = this.resolveAndFilterFuncs_(stack);
+  this.bottomUpTree_.addPath(processedStack);
+  processedStack.reverse();
+  this.topDownTree_.addPath(processedStack);
+};
+
+
+/**
+ * Translates addresses into function names and filters unneeded
+ * functions.
+ *
+ * @param {Array<number>} stack Stack sample.
+ */
+devtools.profiler.Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
+  var result = [];
+  for (var i = 0; i < stack.length; ++i) {
+    var entry = this.codeMap_.findEntry(stack[i]);
+    if (entry) {
+      var name = entry.getName();
+      if (!this.skipThisFunction(name)) {
+        result.push(name);
+      }
+    } else {
+      this.handleUnknownCode(
+          devtools.profiler.Profile.Operation.TICK, stack[i], i);
+    }
+  }
+  return result;
+};
+
+
+/**
+ * Performs a BF traversal of the top down call graph.
+ *
+ * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
+ */
+devtools.profiler.Profile.prototype.traverseTopDownTree = function(f) {
+  this.topDownTree_.traverse(f);
+};
+
+
+/**
+ * Performs a BF traversal of the bottom up call graph.
+ *
+ * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
+ */
+devtools.profiler.Profile.prototype.traverseBottomUpTree = function(f) {
+  this.bottomUpTree_.traverse(f);
+};
+
+
+/**
+ * Calculates a top down profile for a node with the specified label.
+ * If no name specified, returns the whole top down calls tree.
+ *
+ * @param {string} opt_label Node label.
+ */
+devtools.profiler.Profile.prototype.getTopDownProfile = function(opt_label) {
+  return this.getTreeProfile_(this.topDownTree_, opt_label);
+};
+
+
+/**
+ * Calculates a bottom up profile for a node with the specified label.
+ * If no name specified, returns the whole bottom up calls tree.
+ *
+ * @param {string} opt_label Node label.
+ */
+devtools.profiler.Profile.prototype.getBottomUpProfile = function(opt_label) {
+  return this.getTreeProfile_(this.bottomUpTree_, opt_label);
+};
+
+
+/**
+ * Helper function for calculating a tree profile.
+ *
+ * @param {devtools.profiler.Profile.CallTree} tree Call tree.
+ * @param {string} opt_label Node label.
+ */
+devtools.profiler.Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
+  if (!opt_label) {
+    tree.computeTotalWeights();
+    return tree;
+  } else {
+    var subTree = tree.cloneSubtree(opt_label);
+    subTree.computeTotalWeights();
+    return subTree;
+  }
+};
+
+
+/**
+ * Calculates a flat profile of callees starting from a node with
+ * the specified label. If no name specified, starts from the root.
+ *
+ * @param {string} opt_label Starting node label.
+ */
+devtools.profiler.Profile.prototype.getFlatProfile = function(opt_label) {
+  var counters = new devtools.profiler.CallTree();
+  var rootLabel = opt_label || devtools.profiler.CallTree.ROOT_NODE_LABEL;
+  var precs = {};
+  precs[rootLabel] = 0;
+  var root = counters.findOrAddChild(rootLabel);
+
+  this.topDownTree_.computeTotalWeights();
+  this.topDownTree_.traverseInDepth(
+    function onEnter(node) {
+      if (!(node.label in precs)) {
+        precs[node.label] = 0;
+      }
+      var nodeLabelIsRootLabel = node.label == rootLabel;
+      if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
+        if (precs[rootLabel] == 0) {
+          root.selfWeight += node.selfWeight;
+          root.totalWeight += node.totalWeight;
+        } else {
+          var rec = root.findOrAddChild(node.label);
+          rec.selfWeight += node.selfWeight;
+          if (nodeLabelIsRootLabel || precs[node.label] == 0) {
+            rec.totalWeight += node.totalWeight;
+          }
+        }
+        precs[node.label]++;
+      }
+    },
+    function onExit(node) {
+      if (node.label == rootLabel || precs[rootLabel] > 0) {
+        precs[node.label]--;
+      }
+    },
+    null);
+
+  if (!opt_label) {
+    // If we have created a flat profile for the whole program, we don't
+    // need an explicit root in it. Thus, replace the counters tree
+    // root with the node corresponding to the whole program.
+    counters.root_ = root;
+  } else {
+    // Propagate weights so percents can be calculated correctly.
+    counters.getRoot().selfWeight = root.selfWeight;
+    counters.getRoot().totalWeight = root.totalWeight;
+  }
+  return counters;
+};
+
+
+/**
+ * Creates a dynamic code entry.
+ *
+ * @param {number} size Code size.
+ * @param {string} type Code type.
+ * @param {string} name Function name.
+ * @constructor
+ */
+devtools.profiler.Profile.DynamicCodeEntry = function(size, type, name) {
+  devtools.profiler.CodeMap.CodeEntry.call(this, size, name);
+  this.type = type;
+};
+
+
+/**
+ * Returns node name.
+ */
+devtools.profiler.Profile.DynamicCodeEntry.prototype.getName = function() {
+  var name = this.name;
+  if (name.length == 0) {
+    name = '<anonymous>';
+  } else if (name.charAt(0) == ' ') {
+    // An anonymous function with location: " aaa.js:10".
+    name = '<anonymous>' + name;
+  }
+  return this.type + ': ' + name;
+};
+
+
+/**
+ * Constructs a call graph.
+ *
+ * @constructor
+ */
+devtools.profiler.CallTree = function() {
+  this.root_ = new devtools.profiler.CallTree.Node(
+      devtools.profiler.CallTree.ROOT_NODE_LABEL);
+};
+
+
+/**
+ * The label of the root node.
+ */
+devtools.profiler.CallTree.ROOT_NODE_LABEL = '';
+
+
+/**
+ * @private
+ */
+devtools.profiler.CallTree.prototype.totalsComputed_ = false;
+
+
+/**
+ * Returns the tree root.
+ */
+devtools.profiler.CallTree.prototype.getRoot = function() {
+  return this.root_;
+};
+
+
+/**
+ * Adds the specified call path, constructing nodes as necessary.
+ *
+ * @param {Array<string>} path Call path.
+ */
+devtools.profiler.CallTree.prototype.addPath = function(path) {
+  if (path.length == 0) {
+    return;
+  }
+  var curr = this.root_;
+  for (var i = 0; i < path.length; ++i) {
+    curr = curr.findOrAddChild(path[i]);
+  }
+  curr.selfWeight++;
+  this.totalsComputed_ = false;
+};
+
+
+/**
+ * Finds an immediate child of the specified parent with the specified
+ * label, creates a child node if necessary. If a parent node isn't
+ * specified, uses tree root.
+ *
+ * @param {string} label Child node label.
+ */
+devtools.profiler.CallTree.prototype.findOrAddChild = function(label) {
+  return this.root_.findOrAddChild(label);
+};
+
+
+/**
+ * Creates a subtree by cloning and merging all subtrees rooted at nodes
+ * with a given label. E.g. cloning the following call tree on label 'A'
+ * will give the following result:
+ *
+ *           <A>--<B>                                     <B>
+ *          /                                            /
+ *     <root>             == clone on 'A' ==>  <root>--<A>
+ *          \                                            \
+ *           <C>--<A>--<D>                                <D>
+ *
+ * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
+ * source call tree.
+ *
+ * @param {string} label The label of the new root node.
+ */
+devtools.profiler.CallTree.prototype.cloneSubtree = function(label) {
+  var subTree = new devtools.profiler.CallTree();
+  this.traverse(function(node, parent) {
+    if (!parent && node.label != label) {
+      return null;
+    }
+    var child = (parent ? parent : subTree).findOrAddChild(node.label);
+    child.selfWeight += node.selfWeight;
+    return child;
+  });
+  return subTree;
+};
+
+
+/**
+ * Computes total weights in the call graph.
+ */
+devtools.profiler.CallTree.prototype.computeTotalWeights = function() {
+  if (this.totalsComputed_) {
+    return;
+  }
+  this.root_.computeTotalWeight();
+  this.totalsComputed_ = true;
+};
+
+
+/**
+ * Traverses the call graph in preorder. This function can be used for
+ * building optionally modified tree clones. This is the boilerplate code
+ * for this scenario:
+ *
+ * callTree.traverse(function(node, parentClone) {
+ *   var nodeClone = cloneNode(node);
+ *   if (parentClone)
+ *     parentClone.addChild(nodeClone);
+ *   return nodeClone;
+ * });
+ *
+ * @param {function(devtools.profiler.CallTree.Node, *)} f Visitor function.
+ *    The second parameter is the result of calling 'f' on the parent node.
+ */
+devtools.profiler.CallTree.prototype.traverse = function(f) {
+  var pairsToProcess = new ConsArray();
+  pairsToProcess.concat([{node: this.root_, param: null}]);
+  while (!pairsToProcess.atEnd()) {
+    var pair = pairsToProcess.next();
+    var node = pair.node;
+    var newParam = f(node, pair.param);
+    var morePairsToProcess = [];
+    node.forEachChild(function (child) {
+        morePairsToProcess.push({node: child, param: newParam}); });
+    pairsToProcess.concat(morePairsToProcess);
+  }
+};
+
+
+/**
+ * Performs an indepth call graph traversal.
+ *
+ * @param {function(devtools.profiler.CallTree.Node)} enter A function called
+ *     prior to visiting node's children.
+ * @param {function(devtools.profiler.CallTree.Node)} exit A function called
+ *     after visiting node's children.
+ */
+devtools.profiler.CallTree.prototype.traverseInDepth = function(enter, exit) {
+  function traverse(node) {
+    enter(node);
+    node.forEachChild(traverse);
+    exit(node);
+  }
+  traverse(this.root_);
+};
+
+
+/**
+ * Constructs a call graph node.
+ *
+ * @param {string} label Node label.
+ * @param {devtools.profiler.CallTree.Node} opt_parent Node parent.
+ */
+devtools.profiler.CallTree.Node = function(label, opt_parent) {
+  this.label = label;
+  this.parent = opt_parent;
+  this.children = {};
+};
+
+
+/**
+ * Node self weight (how many times this node was the last node in
+ * a call path).
+ * @type {number}
+ */
+devtools.profiler.CallTree.Node.prototype.selfWeight = 0;
+
+
+/**
+ * Node total weight (includes weights of all children).
+ * @type {number}
+ */
+devtools.profiler.CallTree.Node.prototype.totalWeight = 0;
+
+
+/**
+ * Adds a child node.
+ *
+ * @param {string} label Child node label.
+ */
+devtools.profiler.CallTree.Node.prototype.addChild = function(label) {
+  var child = new devtools.profiler.CallTree.Node(label, this);
+  this.children[label] = child;
+  return child;
+};
+
+
+/**
+ * Computes node's total weight.
+ */
+devtools.profiler.CallTree.Node.prototype.computeTotalWeight =
+    function() {
+  var totalWeight = this.selfWeight;
+  this.forEachChild(function(child) {
+      totalWeight += child.computeTotalWeight(); });
+  return this.totalWeight = totalWeight;
+};
+
+
+/**
+ * Returns all node's children as an array.
+ */
+devtools.profiler.CallTree.Node.prototype.exportChildren = function() {
+  var result = [];
+  this.forEachChild(function (node) { result.push(node); });
+  return result;
+};
+
+
+/**
+ * Finds an immediate child with the specified label.
+ *
+ * @param {string} label Child node label.
+ */
+devtools.profiler.CallTree.Node.prototype.findChild = function(label) {
+  return this.children[label] || null;
+};
+
+
+/**
+ * Finds an immediate child with the specified label, creates a child
+ * node if necessary.
+ *
+ * @param {string} label Child node label.
+ */
+devtools.profiler.CallTree.Node.prototype.findOrAddChild = function(label) {
+  return this.findChild(label) || this.addChild(label);
+};
+
+
+/**
+ * Calls the specified function for every child.
+ *
+ * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
+ */
+devtools.profiler.CallTree.Node.prototype.forEachChild = function(f) {
+  for (var c in this.children) {
+    f(this.children[c]);
+  }
+};
+
+
+/**
+ * Walks up from the current node up to the call tree root.
+ *
+ * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
+ */
+devtools.profiler.CallTree.Node.prototype.walkUpToRoot = function(f) {
+  for (var curr = this; curr != null; curr = curr.parent) {
+    f(curr);
+  }
+};
+
+
+/**
+ * Tries to find a node with the specified path.
+ *
+ * @param {Array<string>} labels The path.
+ * @param {function(devtools.profiler.CallTree.Node)} opt_f Visitor function.
+ */
+devtools.profiler.CallTree.Node.prototype.descendToChild = function(
+    labels, opt_f) {
+  for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
+    var child = curr.findChild(labels[pos]);
+    if (opt_f) {
+      opt_f(child, pos);
+    }
+    curr = child;
+  }
+  return curr;
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile_view.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile_view.js
new file mode 100644
index 0000000..bdea631
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile_view.js
@@ -0,0 +1,224 @@
+// Copyright 2009 the V8 project 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.
+
+
+// Initlialize namespaces
+var devtools = devtools || {};
+devtools.profiler = devtools.profiler || {};
+
+
+/**
+ * Creates a Profile View builder object.
+ *
+ * @param {number} samplingRate Number of ms between profiler ticks.
+ * @constructor
+ */
+devtools.profiler.ViewBuilder = function(samplingRate) {
+  this.samplingRate = samplingRate;
+};
+
+
+/**
+ * Builds a profile view for the specified call tree.
+ *
+ * @param {devtools.profiler.CallTree} callTree A call tree.
+ * @param {boolean} opt_bottomUpViewWeights Whether remapping
+ *     of self weights for a bottom up view is needed.
+ */
+devtools.profiler.ViewBuilder.prototype.buildView = function(
+    callTree, opt_bottomUpViewWeights) {
+  var head;
+  var samplingRate = this.samplingRate;
+  var createViewNode = this.createViewNode;
+  callTree.traverse(function(node, viewParent) {
+    var totalWeight = node.totalWeight * samplingRate;
+    var selfWeight = node.selfWeight * samplingRate;
+    if (opt_bottomUpViewWeights === true) {
+      if (viewParent === head) {
+        selfWeight = totalWeight;
+      } else {
+        selfWeight = 0;
+      }
+    }
+    var viewNode = createViewNode(node.label, totalWeight, selfWeight, head);
+    if (viewParent) {
+      viewParent.addChild(viewNode);
+    } else {
+      head = viewNode;
+    }
+    return viewNode;
+  });
+  var view = this.createView(head);
+  return view;
+};
+
+
+/**
+ * Factory method for a profile view.
+ *
+ * @param {devtools.profiler.ProfileView.Node} head View head node.
+ * @return {devtools.profiler.ProfileView} Profile view.
+ */
+devtools.profiler.ViewBuilder.prototype.createView = function(head) {
+  return new devtools.profiler.ProfileView(head);
+};
+
+
+/**
+ * Factory method for a profile view node.
+ *
+ * @param {string} internalFuncName A fully qualified function name.
+ * @param {number} totalTime Amount of time that application spent in the
+ *     corresponding function and its descendants (not that depending on
+ *     profile they can be either callees or callers.)
+ * @param {number} selfTime Amount of time that application spent in the
+ *     corresponding function only.
+ * @param {devtools.profiler.ProfileView.Node} head Profile view head.
+ * @return {devtools.profiler.ProfileView.Node} Profile view node.
+ */
+devtools.profiler.ViewBuilder.prototype.createViewNode = function(
+    funcName, totalTime, selfTime, head) {
+  return new devtools.profiler.ProfileView.Node(
+      funcName, totalTime, selfTime, head);
+};
+
+
+/**
+ * Creates a Profile View object. It allows to perform sorting
+ * and filtering actions on the profile.
+ *
+ * @param {devtools.profiler.ProfileView.Node} head Head (root) node.
+ * @constructor
+ */
+devtools.profiler.ProfileView = function(head) {
+  this.head = head;
+};
+
+
+/**
+ * Sorts the profile view using the specified sort function.
+ *
+ * @param {function(devtools.profiler.ProfileView.Node,
+ *     devtools.profiler.ProfileView.Node):number} sortFunc A sorting
+ *     functions. Must comply with Array.sort sorting function requirements.
+ */
+devtools.profiler.ProfileView.prototype.sort = function(sortFunc) {
+  this.traverse(function (node) {
+    node.sortChildren(sortFunc);
+  });
+};
+
+
+/**
+ * Traverses profile view nodes in preorder.
+ *
+ * @param {function(devtools.profiler.ProfileView.Node)} f Visitor function.
+ */
+devtools.profiler.ProfileView.prototype.traverse = function(f) {
+  var nodesToTraverse = new ConsArray();
+  nodesToTraverse.concat([this.head]);
+  while (!nodesToTraverse.atEnd()) {
+    var node = nodesToTraverse.next();
+    f(node);
+    nodesToTraverse.concat(node.children);
+  }
+};
+
+
+/**
+ * Constructs a Profile View node object. Each node object corresponds to
+ * a function call.
+ *
+ * @param {string} internalFuncName A fully qualified function name.
+ * @param {number} totalTime Amount of time that application spent in the
+ *     corresponding function and its descendants (not that depending on
+ *     profile they can be either callees or callers.)
+ * @param {number} selfTime Amount of time that application spent in the
+ *     corresponding function only.
+ * @param {devtools.profiler.ProfileView.Node} head Profile view head.
+ * @constructor
+ */
+devtools.profiler.ProfileView.Node = function(
+    internalFuncName, totalTime, selfTime, head) {
+  this.internalFuncName = internalFuncName;
+  this.totalTime = totalTime;
+  this.selfTime = selfTime;
+  this.head = head;
+  this.parent = null;
+  this.children = [];
+};
+
+
+/**
+ * Returns a share of the function's total time in application's total time.
+ */
+devtools.profiler.ProfileView.Node.prototype.__defineGetter__(
+    'totalPercent',
+    function() { return this.totalTime /
+      (this.head ? this.head.totalTime : this.totalTime) * 100.0; });
+
+
+/**
+ * Returns a share of the function's self time in application's total time.
+ */
+devtools.profiler.ProfileView.Node.prototype.__defineGetter__(
+    'selfPercent',
+    function() { return this.selfTime /
+      (this.head ? this.head.totalTime : this.totalTime) * 100.0; });
+
+
+/**
+ * Returns a share of the function's total time in its parent's total time.
+ */
+devtools.profiler.ProfileView.Node.prototype.__defineGetter__(
+    'parentTotalPercent',
+    function() { return this.totalTime /
+      (this.parent ? this.parent.totalTime : this.totalTime) * 100.0; });
+
+
+/**
+ * Adds a child to the node.
+ *
+ * @param {devtools.profiler.ProfileView.Node} node Child node.
+ */
+devtools.profiler.ProfileView.Node.prototype.addChild = function(node) {
+  node.parent = this;
+  this.children.push(node);
+};
+
+
+/**
+ * Sorts all the node's children recursively.
+ *
+ * @param {function(devtools.profiler.ProfileView.Node,
+ *     devtools.profiler.ProfileView.Node):number} sortFunc A sorting
+ *     functions. Must comply with Array.sort sorting function requirements.
+ */
+devtools.profiler.ProfileView.Node.prototype.sortChildren = function(
+    sortFunc) {
+  this.children.sort(sortFunc);
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profiler_processor.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profiler_processor.js
new file mode 100644
index 0000000..13aeee7
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profiler_processor.js
@@ -0,0 +1,449 @@
+// Copyright (c) 2009 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 Profiler processor is used to process log file produced
+ * by V8 and produce an internal profile representation which is used
+ * for building profile views in 'Profiles' tab.
+ */
+goog.provide('devtools.profiler.Processor');
+
+
+/**
+ * Creates a Profile View builder object compatible with WebKit Profiler UI.
+ *
+ * @param {number} samplingRate Number of ms between profiler ticks.
+ * @constructor
+ */
+devtools.profiler.WebKitViewBuilder = function(samplingRate) {
+  devtools.profiler.ViewBuilder.call(this, samplingRate);
+};
+goog.inherits(devtools.profiler.WebKitViewBuilder,
+    devtools.profiler.ViewBuilder);
+
+
+/**
+ * @override
+ */
+devtools.profiler.WebKitViewBuilder.prototype.createViewNode = function(
+    funcName, totalTime, selfTime, head) {
+  return new devtools.profiler.WebKitViewNode(
+      funcName, totalTime, selfTime, head);
+};
+
+
+/**
+ * Constructs a Profile View node object for displaying in WebKit Profiler UI.
+ *
+ * @param {string} internalFuncName A fully qualified function name.
+ * @param {number} totalTime Amount of time that application spent in the
+ *     corresponding function and its descendants (not that depending on
+ *     profile they can be either callees or callers.)
+ * @param {number} selfTime Amount of time that application spent in the
+ *     corresponding function only.
+ * @param {devtools.profiler.ProfileView.Node} head Profile view head.
+ * @constructor
+ */
+devtools.profiler.WebKitViewNode = function(
+    internalFuncName, totalTime, selfTime, head) {
+  devtools.profiler.ProfileView.Node.call(this,
+      internalFuncName, totalTime, selfTime, head);
+  this.initFuncInfo_();
+  this.callUID = internalFuncName;
+};
+goog.inherits(devtools.profiler.WebKitViewNode,
+    devtools.profiler.ProfileView.Node);
+
+
+/**
+ * RegEx for stripping V8's prefixes of compiled functions.
+ */
+devtools.profiler.WebKitViewNode.FUNC_NAME_STRIP_RE =
+    /^(?:LazyCompile|Function): (.*)$/;
+
+
+/**
+ * RegEx for extracting script source URL and line number.
+ */
+devtools.profiler.WebKitViewNode.FUNC_NAME_PARSE_RE =
+    /^([^ ]+) (.*):(\d+)( \{\d+\})?$/;
+
+
+/**
+ * Inits 'functionName', 'url', and 'lineNumber' fields using 'internalFuncName'
+ * field.
+ * @private
+ */
+devtools.profiler.WebKitViewNode.prototype.initFuncInfo_ = function() {
+  var nodeAlias = devtools.profiler.WebKitViewNode;
+  this.functionName = this.internalFuncName;
+
+  var strippedName = nodeAlias.FUNC_NAME_STRIP_RE.exec(this.functionName);
+  if (strippedName) {
+    this.functionName = strippedName[1];
+  }
+
+  var parsedName = nodeAlias.FUNC_NAME_PARSE_RE.exec(this.functionName);
+  if (parsedName) {
+    this.functionName = parsedName[1];
+    if (parsedName[4]) {
+      this.functionName += parsedName[4];
+    }
+    this.url = parsedName[2];
+    this.lineNumber = parsedName[3];
+  } else {
+    this.url = '';
+    this.lineNumber = 0;
+  }
+};
+
+
+/**
+ * Ancestor of a profile object that leaves out only JS-related functions.
+ * @constructor
+ */
+devtools.profiler.JsProfile = function() {
+  devtools.profiler.Profile.call(this);
+};
+goog.inherits(devtools.profiler.JsProfile, devtools.profiler.Profile);
+
+
+/**
+ * RegExp that leaves only JS functions.
+ * @type {RegExp}
+ */
+devtools.profiler.JsProfile.JS_FUNC_RE = /^(LazyCompile|Function|Script):/;
+
+/**
+ * RegExp that filters out native code (ending with "native src.js:xxx").
+ * @type {RegExp}
+ */
+devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE = /\ native\ \w+\.js:\d+$/;
+
+/**
+ * RegExp that filters out native scripts.
+ * @type {RegExp}
+ */
+devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE = /^Script:\ native/;
+
+/**
+ * RegExp that filters out devtools functions. See inject.js and
+ * inject_dispatch.js.
+ * @type {RegExp}
+ */
+devtools.profiler.JsProfile.JS_DEVTOOLS_FUNC_RE =
+    /^\w+:\ devtools(\$\$|\.Injected)/;
+
+
+/**
+ * @override
+ */
+devtools.profiler.JsProfile.prototype.skipThisFunction = function(name) {
+  return !devtools.profiler.JsProfile.JS_FUNC_RE.test(name) ||
+      // To profile V8's natives comment out two lines below and '||' above.
+      devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE.test(name) ||
+      devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE.test(name) ||
+      devtools.profiler.JsProfile.JS_DEVTOOLS_FUNC_RE.test(name);
+};
+
+
+/**
+ * Profiler processor. Consumes profiler log and builds profile views.
+ *
+ * @param {function(devtools.profiler.ProfileView)} newProfileCallback Callback
+ *     that receives a new processed profile.
+ * @constructor
+ */
+devtools.profiler.Processor = function() {
+  devtools.profiler.LogReader.call(this, {
+      'code-creation': {
+          parsers: [null, this.createAddressParser('code'), parseInt, null],
+          processor: this.processCodeCreation_, backrefs: true,
+          needsProfile: true },
+      'code-move': { parsers: [this.createAddressParser('code'),
+          this.createAddressParser('code-move-to')],
+          processor: this.processCodeMove_, backrefs: true,
+          needsProfile: true },
+      'code-delete': { parsers: [this.createAddressParser('code')],
+          processor: this.processCodeDelete_, backrefs: true,
+          needsProfile: true },
+      'tick': { parsers: [this.createAddressParser('code'),
+          this.createAddressParser('stack'), parseInt, 'var-args'],
+          processor: this.processTick_, backrefs: true, needProfile: true },
+      'profiler': { parsers: [null, 'var-args'],
+          processor: this.processProfiler_, needsProfile: false },
+      'heap-sample-begin': { parsers: [null, null, parseInt],
+          processor: this.processHeapSampleBegin_ },
+      'heap-sample-stats': { parsers: [null, null, parseInt, parseInt],
+          processor: this.processHeapSampleStats_ },
+      'heap-sample-item': { parsers: [null, parseInt, parseInt],
+          processor: this.processHeapSampleItem_ },
+      'heap-js-cons-item': { parsers: [null, parseInt, parseInt],
+          processor: this.processHeapJsConsItem_ },
+      'heap-sample-end': { parsers: [null, null],
+          processor: this.processHeapSampleEnd_ },
+      // Not used in DevTools Profiler.
+      'shared-library': null,
+      'heap-js-ret-item': null,
+      // Obsolete row types.
+      'code-allocate': null,
+      'begin-code-region': null,
+      'end-code-region': null});
+
+
+  /**
+   * Callback that is called when a new profile is encountered in the log.
+   * @type {function()}
+   */
+  this.startedProfileProcessing_ = null;
+
+  /**
+   * Callback that is called periodically to display processing status.
+   * @type {function()}
+   */
+  this.profileProcessingStatus_ = null;
+
+  /**
+   * Callback that is called when a profile has been processed and is ready
+   * to be shown.
+   * @type {function(devtools.profiler.ProfileView)}
+   */
+  this.finishedProfileProcessing_ = null;
+
+  /**
+   * The current profile.
+   * @type {devtools.profiler.JsProfile}
+   */
+  this.currentProfile_ = null;
+
+  /**
+   * Builder of profile views. Created during "profiler,begin" event processing.
+   * @type {devtools.profiler.WebKitViewBuilder}
+   */
+  this.viewBuilder_ = null;
+
+  /**
+   * Next profile id.
+   * @type {number}
+   */
+  this.profileId_ = 1;
+
+  /**
+   * Counter for processed ticks.
+   * @type {number}
+   */
+  this.ticksCount_ = 0;
+
+  /**
+   * The current heap snapshot.
+   * @type {string}
+   */
+  this.currentHeapSnapshot_ = null;
+
+  /**
+   * Next heap snapshot id.
+   * @type {number}
+   */
+  this.heapSnapshotId_ = 1;
+};
+goog.inherits(devtools.profiler.Processor, devtools.profiler.LogReader);
+
+
+/**
+ * @override
+ */
+devtools.profiler.Processor.prototype.printError = function(str) {
+  debugPrint(str);
+};
+
+
+/**
+ * @override
+ */
+devtools.profiler.Processor.prototype.skipDispatch = function(dispatch) {
+  return dispatch.needsProfile && this.currentProfile_ == null;
+};
+
+
+/**
+ * Sets profile processing callbacks.
+ *
+ * @param {function()} started Started processing callback.
+ * @param {function(devtools.profiler.ProfileView)} finished Finished
+ *     processing callback.
+ */
+devtools.profiler.Processor.prototype.setCallbacks = function(
+    started, processing, finished) {
+  this.startedProfileProcessing_ = started;
+  this.profileProcessingStatus_ = processing;
+  this.finishedProfileProcessing_ = finished;
+};
+
+
+/**
+ * An address for the fake "(program)" entry. WebKit's visualisation
+ * has assumptions on how the top of the call tree should look like,
+ * and we need to add a fake entry as the topmost function. This
+ * address is chosen because it's the end address of the first memory
+ * page, which is never used for code or data, but only as a guard
+ * page for catching AV errors.
+ *
+ * @type {number}
+ */
+devtools.profiler.Processor.PROGRAM_ENTRY = 0xffff;
+/**
+ * @type {string}
+ */
+devtools.profiler.Processor.PROGRAM_ENTRY_STR = '0xffff';
+
+
+/**
+ * Sets new profile callback.
+ * @param {function(devtools.profiler.ProfileView)} callback Callback function.
+ */
+devtools.profiler.Processor.prototype.setNewProfileCallback = function(
+    callback) {
+  this.newProfileCallback_ = callback;
+};
+
+
+devtools.profiler.Processor.prototype.processProfiler_ = function(
+    state, params) {
+  var processingInterval = null;
+  switch (state) {
+    case 'resume':
+      if (this.currentProfile_ == null) {
+        this.currentProfile_ = new devtools.profiler.JsProfile();
+        // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
+        this.currentProfile_.addCode(
+          'Function', '(program)',
+          devtools.profiler.Processor.PROGRAM_ENTRY, 1);
+        if (this.startedProfileProcessing_) {
+          this.startedProfileProcessing_();
+        }
+        this.ticksCount_ = 0;
+        var self = this;
+        if (this.profileProcessingStatus_) {
+          processingInterval = window.setInterval(
+              function() { self.profileProcessingStatus_(self.ticksCount_); },
+              1000);
+        }
+      }
+      break;
+    case 'pause':
+      if (this.currentProfile_ != null) {
+        window.clearInterval(processingInterval);
+        if (this.finishedProfileProcessing_) {
+          this.finishedProfileProcessing_(this.createProfileForView());
+        }
+        this.currentProfile_ = null;
+      }
+      break;
+    case 'begin':
+      var samplingRate = NaN;
+      if (params.length > 0) {
+        samplingRate = parseInt(params[0]);
+      }
+      if (isNaN(samplingRate)) {
+        samplingRate = 1;
+      }
+      this.viewBuilder_ = new devtools.profiler.WebKitViewBuilder(samplingRate);
+      break;
+    // These events are valid but aren't used.
+    case 'compression':
+    case 'end': break;
+    default:
+      throw new Error('unknown profiler state: ' + state);
+  }
+};
+
+
+devtools.profiler.Processor.prototype.processCodeCreation_ = function(
+    type, start, size, name) {
+  this.currentProfile_.addCode(this.expandAlias(type), name, start, size);
+};
+
+
+devtools.profiler.Processor.prototype.processCodeMove_ = function(from, to) {
+  this.currentProfile_.moveCode(from, to);
+};
+
+
+devtools.profiler.Processor.prototype.processCodeDelete_ = function(start) {
+  this.currentProfile_.deleteCode(start);
+};
+
+
+devtools.profiler.Processor.prototype.processTick_ = function(
+    pc, sp, vmState, stack) {
+  // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
+  stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR);
+  this.currentProfile_.recordTick(this.processStack(pc, stack));
+  this.ticksCount_++;
+};
+
+
+devtools.profiler.Processor.prototype.processHeapSampleBegin_ = function(
+    space, state, ticks) {
+  if (space != 'Heap') return;
+  this.currentHeapSnapshot_ = {
+      number: this.heapSnapshotId_++,
+      entries: {},
+      lowlevels: {},
+      ticks: ticks
+  };
+};
+
+
+devtools.profiler.Processor.prototype.processHeapSampleStats_ = function(
+    space, state, capacity, used) {
+  if (space != 'Heap') return;
+  this.currentHeapSnapshot_.capacity = capacity;
+  this.currentHeapSnapshot_.used = used;
+};
+
+
+devtools.profiler.Processor.prototype.processHeapSampleItem_ = function(
+    item, number, size) {
+  if (!this.currentHeapSnapshot_) return;
+  this.currentHeapSnapshot_.lowlevels[item] = {
+    type: item, count: number, size: size
+  };
+};
+
+
+devtools.profiler.Processor.prototype.processHeapJsConsItem_ = function(
+    item, number, size) {
+  if (!this.currentHeapSnapshot_) return;
+  this.currentHeapSnapshot_.entries[item] = {
+    cons: item, count: number, size: size
+  };
+};
+
+
+devtools.profiler.Processor.prototype.processHeapSampleEnd_ = function(
+    space, state) {
+  if (space != 'Heap') return;
+  var snapshot = this.currentHeapSnapshot_;
+  this.currentHeapSnapshot_ = null;
+  // For some reason, 'used' from 'heap-sample-stats' sometimes differ from
+  // the sum of objects sizes. To avoid discrepancy, we re-calculate 'used'.
+  snapshot.used = 0;
+  for (var item in snapshot.lowlevels) {
+      snapshot.used += snapshot.lowlevels[item].size;
+  }
+  WebInspector.panels.heap.addSnapshot(snapshot);
+};
+
+
+/**
+ * Creates a profile for further displaying in ProfileView.
+ */
+devtools.profiler.Processor.prototype.createProfileForView = function() {
+  var profile = this.viewBuilder_.buildView(
+      this.currentProfile_.getTopDownProfile());
+  profile.uid = this.profileId_++;
+  profile.title = UserInitiatedProfileName + '.' + profile.uid;
+  return profile;
+};
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/splaytree.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/splaytree.js
new file mode 100644
index 0000000..7b3af8b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/splaytree.js
@@ -0,0 +1,322 @@
+// Copyright 2009 the V8 project 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.
+
+
+// A namespace stub. It will become more clear how to declare it properly
+// during integration of this script into Dev Tools.
+var goog = goog || {};
+goog.structs = goog.structs || {};
+
+
+/**
+ * Constructs a Splay tree.  A splay tree is a self-balancing binary
+ * search tree with the additional property that recently accessed
+ * elements are quick to access again. It performs basic operations
+ * such as insertion, look-up and removal in O(log(n)) amortized time.
+ *
+ * @constructor
+ */
+goog.structs.SplayTree = function() {
+};
+
+
+/**
+ * Pointer to the root node of the tree.
+ *
+ * @type {goog.structs.SplayTree.Node}
+ * @private
+ */
+goog.structs.SplayTree.prototype.root_ = null;
+
+
+/**
+ * @return {boolean} Whether the tree is empty.
+ */
+goog.structs.SplayTree.prototype.isEmpty = function() {
+  return !this.root_;
+};
+
+
+
+/**
+ * Inserts a node into the tree with the specified key and value if
+ * the tree does not already contain a node with the specified key. If
+ * the value is inserted, it becomes the root of the tree.
+ *
+ * @param {number} key Key to insert into the tree.
+ * @param {*} value Value to insert into the tree.
+ */
+goog.structs.SplayTree.prototype.insert = function(key, value) {
+  if (this.isEmpty()) {
+    this.root_ = new goog.structs.SplayTree.Node(key, value);
+    return;
+  }
+  // Splay on the key to move the last node on the search path for
+  // the key to the root of the tree.
+  this.splay_(key);
+  if (this.root_.key == key) {
+    return;
+  }
+  var node = new goog.structs.SplayTree.Node(key, value);
+  if (key > this.root_.key) {
+    node.left = this.root_;
+    node.right = this.root_.right;
+    this.root_.right = null;
+  } else {
+    node.right = this.root_;
+    node.left = this.root_.left;
+    this.root_.left = null;
+  }
+  this.root_ = node;
+};
+
+
+/**
+ * Removes a node with the specified key from the tree if the tree
+ * contains a node with this key. The removed node is returned. If the
+ * key is not found, an exception is thrown.
+ *
+ * @param {number} key Key to find and remove from the tree.
+ * @return {goog.structs.SplayTree.Node} The removed node.
+ */
+goog.structs.SplayTree.prototype.remove = function(key) {
+  if (this.isEmpty()) {
+    throw Error('Key not found: ' + key);
+  }
+  this.splay_(key);
+  if (this.root_.key != key) {
+    throw Error('Key not found: ' + key);
+  }
+  var removed = this.root_;
+  if (!this.root_.left) {
+    this.root_ = this.root_.right;
+  } else {
+    var right = this.root_.right;
+    this.root_ = this.root_.left;
+    // Splay to make sure that the new root has an empty right child.
+    this.splay_(key);
+    // Insert the original right child as the right child of the new
+    // root.
+    this.root_.right = right;
+  }
+  return removed;
+};
+
+
+/**
+ * Returns the node having the specified key or null if the tree doesn't contain
+ * a node with the specified key.
+ *
+ * @param {number} key Key to find in the tree.
+ * @return {goog.structs.SplayTree.Node} Node having the specified key.
+ */
+goog.structs.SplayTree.prototype.find = function(key) {
+  if (this.isEmpty()) {
+    return null;
+  }
+  this.splay_(key);
+  return this.root_.key == key ? this.root_ : null;
+};
+
+
+/**
+ * @return {goog.structs.SplayTree.Node} Node having the minimum key value.
+ */
+goog.structs.SplayTree.prototype.findMin = function() {
+  if (this.isEmpty()) {
+    return null;
+  }
+  var current = this.root_;
+  while (current.left) {
+    current = current.left;
+  }
+  return current;
+};
+
+
+/**
+ * @return {goog.structs.SplayTree.Node} Node having the maximum key value.
+ */
+goog.structs.SplayTree.prototype.findMax = function(opt_startNode) {
+  if (this.isEmpty()) {
+    return null;
+  }
+  var current = opt_startNode || this.root_;
+  while (current.right) {
+    current = current.right;
+  }
+  return current;
+};
+
+
+/**
+ * @return {goog.structs.SplayTree.Node} Node having the maximum key value that
+ *     is less or equal to the specified key value.
+ */
+goog.structs.SplayTree.prototype.findGreatestLessThan = function(key) {
+  if (this.isEmpty()) {
+    return null;
+  }
+  // Splay on the key to move the node with the given key or the last
+  // node on the search path to the top of the tree.
+  this.splay_(key);
+  // Now the result is either the root node or the greatest node in
+  // the left subtree.
+  if (this.root_.key <= key) {
+    return this.root_;
+  } else if (this.root_.left) {
+    return this.findMax(this.root_.left);
+  } else {
+    return null;
+  }
+};
+
+
+/**
+ * @return {Array<*>} An array containing all the values of tree's nodes.
+ */
+goog.structs.SplayTree.prototype.exportValues = function() {
+  var result = [];
+  this.traverse_(function(node) { result.push(node.value); });
+  return result;
+};
+
+
+/**
+ * Perform the splay operation for the given key. Moves the node with
+ * the given key to the top of the tree.  If no node has the given
+ * key, the last node on the search path is moved to the top of the
+ * tree. This is the simplified top-down splaying algorithm from:
+ * "Self-adjusting Binary Search Trees" by Sleator and Tarjan
+ *
+ * @param {number} key Key to splay the tree on.
+ * @private
+ */
+goog.structs.SplayTree.prototype.splay_ = function(key) {
+  if (this.isEmpty()) {
+    return;
+  }
+  // Create a dummy node.  The use of the dummy node is a bit
+  // counter-intuitive: The right child of the dummy node will hold
+  // the L tree of the algorithm.  The left child of the dummy node
+  // will hold the R tree of the algorithm.  Using a dummy node, left
+  // and right will always be nodes and we avoid special cases.
+  var dummy, left, right;
+  dummy = left = right = new goog.structs.SplayTree.Node(null, null);
+  var current = this.root_;
+  while (true) {
+    if (key < current.key) {
+      if (!current.left) {
+        break;
+      }
+      if (key < current.left.key) {
+        // Rotate right.
+        var tmp = current.left;
+        current.left = tmp.right;
+        tmp.right = current;
+        current = tmp;
+        if (!current.left) {
+          break;
+        }
+      }
+      // Link right.
+      right.left = current;
+      right = current;
+      current = current.left;
+    } else if (key > current.key) {
+      if (!current.right) {
+        break;
+      }
+      if (key > current.right.key) {
+        // Rotate left.
+        var tmp = current.right;
+        current.right = tmp.left;
+        tmp.left = current;
+        current = tmp;
+        if (!current.right) {
+          break;
+        }
+      }
+      // Link left.
+      left.right = current;
+      left = current;
+      current = current.right;
+    } else {
+      break;
+    }
+  }
+  // Assemble.
+  left.right = current.left;
+  right.left = current.right;
+  current.left = dummy.right;
+  current.right = dummy.left;
+  this.root_ = current;
+};
+
+
+/**
+ * Performs a preorder traversal of the tree.
+ *
+ * @param {function(goog.structs.SplayTree.Node)} f Visitor function.
+ * @private
+ */
+goog.structs.SplayTree.prototype.traverse_ = function(f) {
+  var nodesToVisit = [this.root_];
+  while (nodesToVisit.length > 0) {
+    var node = nodesToVisit.shift();
+    if (node == null) {
+      continue;
+    }
+    f(node);
+    nodesToVisit.push(node.left);
+    nodesToVisit.push(node.right);
+  }
+};
+
+
+/**
+ * Constructs a Splay tree node.
+ *
+ * @param {number} key Key.
+ * @param {*} value Value.
+ */
+goog.structs.SplayTree.Node = function(key, value) {
+  this.key = key;
+  this.value = value;
+};
+
+
+/**
+ * @type {goog.structs.SplayTree.Node}
+ */
+goog.structs.SplayTree.Node.prototype.left = null;
+
+
+/**
+ * @type {goog.structs.SplayTree.Node}
+ */
+goog.structs.SplayTree.Node.prototype.right = null;
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/tests.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/tests.js
new file mode 100644
index 0000000..5691017
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/tests.js
@@ -0,0 +1,628 @@
+// Copyright (c) 2009 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 file contains small testing framework along with the
+ * test suite for the frontend. These tests are a part of the continues build
+ * and are executed by the devtools_sanity_unittest.cc as a part of the
+ * Interactive UI Test suite.
+ */
+
+if (window.domAutomationController) {
+
+var ___interactiveUiTestsMode = true;
+
+/**
+ * Test suite for interactive UI tests.
+ * @constructor
+ */
+TestSuite = function() {
+  this.controlTaken_ = false;
+  this.timerId_ = -1;
+};
+
+
+/**
+ * Reports test failure.
+ * @param {string} message Failure description.
+ */
+TestSuite.prototype.fail = function(message) {
+  if (this.controlTaken_) {
+    this.reportFailure_(message);
+  } else {
+    throw message;
+  }
+};
+
+
+/**
+ * Equals assertion tests that expected == actual.
+ * @param {Object} expected Expected object.
+ * @param {Object} actual Actual object.
+ * @param {string} opt_message User message to print if the test fails.
+ */
+TestSuite.prototype.assertEquals = function(expected, actual, opt_message) {
+  if (expected != actual) {
+    var message = 'Expected: "' + expected + '", but was "' + actual + '"';
+    if (opt_message) {
+      message = opt_message + '(' + message + ')';
+    }
+    this.fail(message);
+  }
+};
+
+
+/**
+ * True assertion tests that value == true.
+ * @param {Object} value Actual object.
+ * @param {string} opt_message User message to print if the test fails.
+ */
+TestSuite.prototype.assertTrue = function(value, opt_message) {
+  this.assertEquals(true, value, opt_message);
+};
+
+
+/**
+ * Contains assertion tests that string contains substring.
+ * @param {string} string Outer.
+ * @param {string} substring Inner.
+ */
+TestSuite.prototype.assertContains = function(string, substring) {
+  if (string.indexOf(substring) == -1) {
+    this.fail('Expected to: "' + string + '" to contain "' + substring + '"');
+  }
+};
+
+
+/**
+ * Takes control over execution.
+ */
+TestSuite.prototype.takeControl = function() {
+  this.controlTaken_ = true;
+  // Set up guard timer.
+  var self = this;
+  this.timerId_ = setTimeout(function() {
+    self.reportFailure_('Timeout exceeded: 20 sec');
+  }, 20000);
+};
+
+
+/**
+ * Releases control over execution.
+ */
+TestSuite.prototype.releaseControl = function() {
+  if (this.timerId_ != -1) {
+    clearTimeout(this.timerId_);
+    this.timerId_ = -1;
+  }
+  this.reportOk_();
+};
+
+
+/**
+ * Async tests use this one to report that they are completed.
+ */
+TestSuite.prototype.reportOk_ = function() {
+  window.domAutomationController.send('[OK]');
+};
+
+
+/**
+ * Async tests use this one to report failures.
+ */
+TestSuite.prototype.reportFailure_ = function(error) {
+  if (this.timerId_ != -1) {
+    clearTimeout(this.timerId_);
+    this.timerId_ = -1;
+  }
+  window.domAutomationController.send('[FAILED] ' + error);
+};
+
+
+/**
+ * Runs all global functions starting with 'test' as unit tests.
+ */
+TestSuite.prototype.runTest = function(testName) {
+  try {
+    this[testName]();
+    if (!this.controlTaken_) {
+      this.reportOk_();
+    }
+  } catch (e) {
+    this.reportFailure_(e);
+  }
+};
+
+
+/**
+ * @param {string} panelName Name of the panel to show.
+ */
+TestSuite.prototype.showPanel = function(panelName) {
+  // Open Scripts panel.
+  var toolbar = document.getElementById('toolbar');
+  var button = toolbar.getElementsByClassName(panelName)[0];
+  button.click();
+  this.assertEquals(WebInspector.panels[panelName],
+      WebInspector.currentPanel);
+};
+
+
+/**
+ * Overrides the method with specified name until it's called first time.
+ * @param {Object} receiver An object whose method to override.
+ * @param {string} methodName Name of the method to override.
+ * @param {Function} override A function that should be called right after the
+ *     overriden method returns.
+ * @param {boolean} opt_sticky Whether restore original method after first run
+ *     or not.
+ */
+TestSuite.prototype.addSniffer = function(receiver, methodName, override,
+                                          opt_sticky) {
+  var orig = receiver[methodName];
+  if (typeof orig != 'function') {
+    this.fail('Cannot find method to override: ' + methodName);
+  }
+  var test = this;
+  receiver[methodName] = function(var_args) {
+    try {
+      var result = orig.apply(this, arguments);
+    } finally {
+      if (!opt_sticky) {
+        receiver[methodName] = orig;
+      }
+    }
+    // In case of exception the override won't be called.
+    try {
+      override.apply(this, arguments);
+    } catch (e) {
+      test.fail('Exception in overriden method "' + methodName + '": ' + e);
+    }
+    return result;
+  };
+};
+
+
+// UI Tests
+
+
+/**
+ * Tests that the real injected host is present in the context.
+ */
+TestSuite.prototype.testHostIsPresent = function() {
+  this.assertTrue(typeof DevToolsHost == 'object' && !DevToolsHost.isStub);
+};
+
+
+/**
+ * Tests elements tree has an 'HTML' root.
+ */
+TestSuite.prototype.testElementsTreeRoot = function() {
+  var doc = WebInspector.domAgent.document;
+  this.assertEquals('HTML', doc.documentElement.nodeName);
+  this.assertTrue(doc.documentElement.hasChildNodes());
+};
+
+
+/**
+ * Tests that main resource is present in the system and that it is
+ * the only resource.
+ */
+TestSuite.prototype.testMainResource = function() {
+  var tokens = [];
+  var resources = WebInspector.resources;
+  for (var id in resources) {
+    tokens.push(resources[id].lastPathComponent);
+  }
+  this.assertEquals('simple_page.html', tokens.join(','));
+};
+
+
+/**
+ * Tests that resources tab is enabled when corresponding item is selected.
+ */
+TestSuite.prototype.testEnableResourcesTab = function() {
+  this.showPanel('resources');
+
+  var test = this;
+  this.addSniffer(WebInspector, 'addResource',
+      function(identifier, payload) {
+        test.assertEquals('simple_page.html', payload.lastPathComponent);
+        WebInspector.panels.resources.refresh();
+        WebInspector.resources[identifier]._resourcesTreeElement.select();
+
+        test.releaseControl();
+      });
+
+  // Following call should lead to reload that we capture in the
+  // addResource override.
+  WebInspector.panels.resources._enableResourceTracking();
+
+  // We now have some time to report results to controller.
+  this.takeControl();
+};
+
+
+/**
+ * Tests resource headers.
+ */
+TestSuite.prototype.testResourceHeaders = function() {
+  this.showPanel('resources');
+
+  var test = this;
+
+  var requestOk = false;
+  var responseOk = false;
+  var timingOk = false;
+
+  this.addSniffer(WebInspector, 'addResource',
+      function(identifier, payload) {
+        var resource = this.resources[identifier];
+        if (resource.mainResource) {
+          // We are only interested in secondary resources in this test.
+          return;
+        }
+
+        var requestHeaders = JSON.stringify(resource.requestHeaders);
+        test.assertContains(requestHeaders, 'Accept');
+        requestOk = true;
+      }, true);
+
+  this.addSniffer(WebInspector, 'updateResource',
+      function(identifier, payload) {
+        var resource = this.resources[identifier];
+        if (resource.mainResource) {
+          // We are only interested in secondary resources in this test.
+          return;
+        }
+
+        if (payload.didResponseChange) {
+          var responseHeaders = JSON.stringify(resource.responseHeaders);
+          test.assertContains(responseHeaders, 'Content-type');
+          test.assertContains(responseHeaders, 'Content-Length');
+          test.assertTrue(typeof resource.responseReceivedTime != 'undefnied');
+          responseOk = true;
+        }
+
+        if (payload.didTimingChange) {
+          test.assertTrue(typeof resource.startTime != 'undefnied');
+          timingOk = true;
+        }
+
+        if (payload.didCompletionChange) {
+          test.assertTrue(requestOk);
+          test.assertTrue(responseOk);
+          test.assertTrue(timingOk);
+          test.assertTrue(typeof resource.endTime != 'undefnied');
+          test.releaseControl();
+        }
+      }, true);
+
+  WebInspector.panels.resources._enableResourceTracking();
+  this.takeControl();
+};
+
+
+/**
+ * Test that profiler works.
+ */
+TestSuite.prototype.testProfilerTab = function() {
+  this.showPanel('profiles');
+
+  var test = this;
+  this.addSniffer(WebInspector, 'addProfile',
+      function(profile) {
+        var panel = WebInspector.panels.profiles;
+        panel.showProfile(profile);
+        var node = panel.visibleView.profileDataGridTree.children[0];
+        // Iterate over displayed functions and search for a function
+        // that is called 'fib' or 'eternal_fib'. If found, it will mean
+        // that we actually have profiled page's code.
+        while (node) {
+          if (node.functionName.indexOf('fib') != -1) {
+            test.releaseControl();
+          }
+          node = node.traverseNextNode(true, null, true);
+        }
+
+        test.fail();
+      });
+  var ticksCount = 0;
+  var tickRecord = '\nt,';
+  this.addSniffer(RemoteDebuggerAgent, 'DidGetNextLogLines',
+      function(log) {
+        var pos = 0;
+        while ((pos = log.indexOf(tickRecord, pos)) != -1) {
+          pos += tickRecord.length;
+          ticksCount++;
+        }
+        if (ticksCount > 100) {
+          InspectorController.stopProfiling();
+        }
+      }, true);
+
+  InspectorController.startProfiling();
+  this.takeControl();
+};
+
+
+/**
+ * Tests that scripts tab can be open and populated with inspected scripts.
+ */
+TestSuite.prototype.testShowScriptsTab = function() {
+  var parsedDebuggerTestPageHtml = false;
+
+  // Intercept parsedScriptSource calls to check that all expected scripts are
+  // added to the debugger.
+  var test = this;
+  var receivedConsoleApiSource = false;
+  this.addSniffer(WebInspector, 'parsedScriptSource',
+      function(sourceID, sourceURL, source, startingLine) {
+        if (sourceURL == undefined) {
+          if (receivedConsoleApiSource) {
+            test.fail('Unexpected script without URL');
+          } else {
+            receivedConsoleApiSource = true;
+          }
+        } else if (sourceURL.search(/debugger_test_page.html$/) != -1) {
+          if (parsedDebuggerTestPageHtml) {
+            test.fail('Unexpected parse event: ' + sourceURL);
+          }
+          parsedDebuggerTestPageHtml = true;
+        } else {
+          test.fail('Unexpected script URL: ' + sourceURL);
+        }
+
+        if (!WebInspector.panels.scripts.visibleView) {
+          test.fail('No visible script view: ' + sourceURL);
+        }
+
+        // There should be two scripts: one for the main page and another
+        // one which is source of console API(see
+        // InjectedScript._ensureCommandLineAPIInstalled).
+        if (parsedDebuggerTestPageHtml && receivedConsoleApiSource) {
+           test.releaseControl();
+        }
+      }, true /* sticky */);
+
+  this.showPanel('scripts');
+
+  // Wait until all scripts are added to the debugger.
+  this.takeControl();
+};
+
+
+/**
+ * Tests that a breakpoint can be set.
+ */
+TestSuite.prototype.testSetBreakpoint = function() {
+  var parsedDebuggerTestPageHtml = false;
+  var parsedDebuggerTestJs = false;
+
+  this.showPanel('scripts');
+
+  var scriptUrl = null;
+  var breakpointLine = 12;
+
+  var test = this;
+  var orig = devtools.DebuggerAgent.prototype.handleScriptsResponse_;
+  this.addSniffer(devtools.DebuggerAgent.prototype, 'handleScriptsResponse_',
+      function(msg) {
+        var scriptSelect = document.getElementById('scripts-files');
+        var options = scriptSelect.options;
+
+        // There should be console API source (see
+        // InjectedScript._ensureCommandLineAPIInstalled) and the page script.
+        test.assertEquals(2, options.length, 'Unexpected number of scripts.');
+
+        // Select page's script if it's not current option.
+        var scriptResource;
+        if (options[scriptSelect.selectedIndex].text ==
+            'debugger_test_page.html') {
+          scriptResource =
+              options[scriptSelect.selectedIndex].representedObject;
+        } else {
+          var pageScriptIndex = (1 - scriptSelect.selectedIndex);
+          test.assertEquals('debugger_test_page.html',
+                            options[pageScriptIndex].text);
+          scriptResource = options[pageScriptIndex].representedObject;
+          // Current panel is 'Scripts'.
+          WebInspector.currentPanel._showScriptOrResource(scriptResource);
+        }
+
+        test.assertTrue(scriptResource instanceof WebInspector.Resource,
+                        'Unexpected resource class.');
+        test.assertTrue(!!scriptResource.url, 'Resource URL is null.');
+        test.assertTrue(
+            scriptResource.url.search(/debugger_test_page.html$/) != -1,
+            'Main HTML resource should be selected.');
+
+        // Store for access from setbreakpoint handler.
+        scriptUrl = scriptResource.url;
+
+        var scriptsPanel = WebInspector.panels.scripts;
+
+        var view = scriptsPanel.visibleView;
+        test.assertTrue(view instanceof WebInspector.SourceView);
+
+        if (!view.sourceFrame._isContentLoaded()) {
+          test.addSniffer(view, '_sourceFrameSetupFinished', function(event) {
+            view._addBreakpoint(breakpointLine);
+            // Force v8 execution.
+            RemoteToolsAgent.ExecuteVoidJavaScript();
+          });
+        } else {
+          view._addBreakpoint(breakpointLine);
+          // Force v8 execution.
+          RemoteToolsAgent.ExecuteVoidJavaScript();
+        }
+      });
+
+  this.addSniffer(
+      devtools.DebuggerAgent.prototype,
+      'handleSetBreakpointResponse_',
+      function(msg) {
+        var bps = this.urlToBreakpoints_[scriptUrl];
+        test.assertTrue(!!bps, 'No breakpoints for line ' + breakpointLine);
+        var line = devtools.DebuggerAgent.webkitToV8LineNumber_(breakpointLine);
+        test.assertTrue(!!bps[line].getV8Id(),
+                        'Breakpoint id was not assigned.');
+        test.releaseControl();
+      });
+
+  this.takeControl();
+};
+
+
+/**
+ * Tests 'Pause' button will pause debugger when a snippet is evaluated.
+ */
+TestSuite.prototype.testPauseInEval = function() {
+  this.showPanel('scripts');
+
+  var test = this;
+
+  var pauseButton = document.getElementById('scripts-pause');
+  pauseButton.click();
+
+  devtools.tools.evaluateJavaScript('fib(10)');
+
+  this.addSniffer(WebInspector, 'pausedScript',
+      function() {
+        test.releaseControl();
+      });
+
+  test.takeControl();
+};
+
+
+/**
+ * Key event with given key identifier.
+ */
+TestSuite.KeyEvent = function(key) {
+  this.keyIdentifier = key;
+};
+TestSuite.KeyEvent.prototype.preventDefault = function() {};
+TestSuite.KeyEvent.prototype.stopPropagation = function() {};
+
+
+/**
+ * Tests console eval.
+ */
+TestSuite.prototype.testConsoleEval = function() {
+  WebInspector.console.visible = true;
+  WebInspector.console.prompt.text = '123';
+  WebInspector.console.promptElement.handleKeyEvent(
+      new TestSuite.KeyEvent('Enter'));
+
+  var test = this;
+  this.addSniffer(WebInspector.ConsoleView.prototype, 'addMessage',
+      function(commandResult) {
+        test.assertEquals('123', commandResult.toMessageElement().textContent);
+        test.releaseControl();
+      });
+
+  this.takeControl();
+};
+
+
+/**
+ * Tests console log.
+ */
+TestSuite.prototype.testConsoleLog = function() {
+  WebInspector.console.visible = true;
+  var messages = WebInspector.console.messages;
+  var index = 0;
+
+  var test = this;
+  var assertNext = function(line, message, opt_class, opt_count, opt_substr) {
+    var elem = messages[index++].toMessageElement();
+    var clazz = elem.getAttribute('class');
+    var expectation = (opt_count || '') + 'console_test_page.html:' +
+        line + message;
+    if (opt_substr) {
+      test.assertContains(elem.textContent, expectation);
+    } else {
+      test.assertEquals(expectation, elem.textContent);
+    }
+    if (opt_class) {
+      test.assertContains(clazz, 'console-' + opt_class);
+    }
+  };
+
+  assertNext('5', 'log', 'log-level');
+  assertNext('7', 'debug', 'log-level');
+  assertNext('9', 'info', 'log-level');
+  assertNext('11', 'warn', 'warning-level');
+  assertNext('13', 'error', 'error-level');
+  assertNext('15', 'Message format number 1, 2 and 3.5');
+  assertNext('17', 'Message format for string');
+  assertNext('19', 'Object Object');
+  assertNext('22', 'repeated', 'log-level', 5);
+  assertNext('26', 'count: 1');
+  assertNext('26', 'count: 2');
+  assertNext('29', 'group', 'group-title');
+  index++;
+  assertNext('33', 'timer:', 'log-level', '', true);
+};
+
+
+/**
+ * Tests eval of global objects.
+ */
+TestSuite.prototype.testEvalGlobal = function() {
+  WebInspector.console.visible = true;
+  WebInspector.console.prompt.text = 'foo';
+  WebInspector.console.promptElement.handleKeyEvent(
+      new TestSuite.KeyEvent('Enter'));
+
+  var test = this;
+  this.addSniffer(WebInspector.ConsoleView.prototype, 'addMessage',
+      function(commandResult) {
+        test.assertEquals('fooValue',
+                          commandResult.toMessageElement().textContent);
+        test.releaseControl();
+      });
+
+  this.takeControl();
+};
+
+
+/**
+ * Tests eval on call frame.
+ */
+TestSuite.prototype.testEvalCallFrame = function() {
+};
+
+
+/**
+ * Test runner for the test suite.
+ */
+var uiTests = {};
+
+
+/**
+ * Run each test from the test suit on a fresh instance of the suite.
+ */
+uiTests.runAllTests = function() {
+  // For debugging purposes.
+  for (var name in TestSuite.prototype) {
+    if (name.substring(0, 4) == 'test' &&
+        typeof TestSuite.prototype[name] == 'function') {
+      uiTests.runTest(name);
+    }
+  }
+};
+
+
+/**
+ * Run specified test on a fresh instance of the test suite.
+ * @param {string} name Name of a test method from TestSuite class.
+ */
+uiTests.runTest = function(name) {
+  new TestSuite().runTest(name);
+};
+
+
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/treeoutline.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/treeoutline.js
new file mode 100644
index 0000000..ecc322b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/treeoutline.js
@@ -0,0 +1,849 @@
+/*
+ * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+function TreeOutline(listNode)
+{
+    this.children = [];
+    this.selectedTreeElement = null;
+    this._childrenListNode = listNode;
+    this._childrenListNode.removeChildren();
+    this._knownTreeElements = [];
+    this._treeElementsExpandedState = [];
+    this.expandTreeElementsWhenArrowing = false;
+    this.root = true;
+    this.hasChildren = false;
+    this.expanded = true;
+    this.selected = false;
+    this.treeOutline = this;
+}
+
+TreeOutline._knownTreeElementNextIdentifier = 1;
+
+TreeOutline._appendChild = function(child)
+{
+    if (!child)
+        throw("child can't be undefined or null");
+
+    var lastChild = this.children[this.children.length - 1];
+    if (lastChild) {
+        lastChild.nextSibling = child;
+        child.previousSibling = lastChild;
+    } else {
+        child.previousSibling = null;
+        child.nextSibling = null;
+    }
+
+    this.children.push(child);
+    this.hasChildren = true;
+    child.parent = this;
+    child.treeOutline = this.treeOutline;
+    child.treeOutline._rememberTreeElement(child);
+
+    var current = child.children[0];
+    while (current) {
+        current.treeOutline = this.treeOutline;
+        current.treeOutline._rememberTreeElement(current);
+        current = current.traverseNextTreeElement(false, child, true);
+    }
+
+    if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
+        child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];
+
+    if (!this._childrenListNode) {
+        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
+        this._childrenListNode.parentTreeElement = this;
+        this._childrenListNode.addStyleClass("children");
+        if (this.hidden)
+            this._childrenListNode.addStyleClass("hidden");
+    }
+
+    child._attach();
+}
+
+TreeOutline._insertChild = function(child, index)
+{
+    if (!child)
+        throw("child can't be undefined or null");
+
+    var previousChild = (index > 0 ? this.children[index - 1] : null);
+    if (previousChild) {
+        previousChild.nextSibling = child;
+        child.previousSibling = previousChild;
+    } else {
+        child.previousSibling = null;
+    }
+
+    var nextChild = this.children[index];
+    if (nextChild) {
+        nextChild.previousSibling = child;
+        child.nextSibling = nextChild;
+    } else {
+        child.nextSibling = null;
+    }
+
+    this.children.splice(index, 0, child);
+    this.hasChildren = true;
+    child.parent = this;
+    child.treeOutline = this.treeOutline;
+    child.treeOutline._rememberTreeElement(child);
+
+    var current = child.children[0];
+    while (current) {
+        current.treeOutline = this.treeOutline;
+        current.treeOutline._rememberTreeElement(current);
+        current = current.traverseNextTreeElement(false, child, true);
+    }
+
+    if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
+        child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];
+
+    if (!this._childrenListNode) {
+        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
+        this._childrenListNode.parentTreeElement = this;
+        this._childrenListNode.addStyleClass("children");
+        if (this.hidden)
+            this._childrenListNode.addStyleClass("hidden");
+    }
+
+    child._attach();
+}
+
+TreeOutline._removeChildAtIndex = function(childIndex)
+{
+    if (childIndex < 0 || childIndex >= this.children.length)
+        throw("childIndex out of range");
+
+    var child = this.children[childIndex];
+    this.children.splice(childIndex, 1);
+
+    child.deselect();
+
+    if (child.previousSibling)
+        child.previousSibling.nextSibling = child.nextSibling;
+    if (child.nextSibling)
+        child.nextSibling.previousSibling = child.previousSibling;
+
+    if (child.treeOutline) {
+        child.treeOutline._forgetTreeElement(child);
+        child.treeOutline._forgetChildrenRecursive(child);
+    }
+
+    child._detach();
+    child.treeOutline = null;
+    child.parent = null;
+    child.nextSibling = null;
+    child.previousSibling = null;
+}
+
+TreeOutline._removeChild = function(child)
+{
+    if (!child)
+        throw("child can't be undefined or null");
+
+    var childIndex = this.children.indexOf(child);
+    if (childIndex === -1)
+        throw("child not found in this node's children");
+
+    TreeOutline._removeChildAtIndex.call(this, childIndex);
+}
+
+TreeOutline._removeChildren = function()
+{
+    for (var i = 0; i < this.children.length; ++i) {
+        var child = this.children[i];
+        child.deselect();
+
+        if (child.treeOutline) {
+            child.treeOutline._forgetTreeElement(child);
+            child.treeOutline._forgetChildrenRecursive(child);
+        }
+
+        child._detach();
+        child.treeOutline = null;
+        child.parent = null;
+        child.nextSibling = null;
+        child.previousSibling = null;
+    }
+
+    this.children = [];
+}
+
+TreeOutline._removeChildrenRecursive = function()
+{
+    var childrenToRemove = this.children;
+
+    var child = this.children[0];
+    while (child) {
+        if (child.children.length)
+            childrenToRemove = childrenToRemove.concat(child.children);
+        child = child.traverseNextTreeElement(false, this, true);
+    }
+
+    for (var i = 0; i < childrenToRemove.length; ++i) {
+        var child = childrenToRemove[i];
+        child.deselect();
+        if (child.treeOutline)
+            child.treeOutline._forgetTreeElement(child);
+        child._detach();
+        child.children = [];
+        child.treeOutline = null;
+        child.parent = null;
+        child.nextSibling = null;
+        child.previousSibling = null;
+    }
+
+    this.children = [];
+}
+
+TreeOutline.prototype._rememberTreeElement = function(element)
+{
+    if (!this._knownTreeElements[element.identifier])
+        this._knownTreeElements[element.identifier] = [];
+
+    // check if the element is already known
+    var elements = this._knownTreeElements[element.identifier];
+    if (elements.indexOf(element) !== -1)
+        return;
+
+    // add the element
+    elements.push(element);
+}
+
+TreeOutline.prototype._forgetTreeElement = function(element)
+{
+    if (this._knownTreeElements[element.identifier])
+        this._knownTreeElements[element.identifier].remove(element, true);
+}
+
+TreeOutline.prototype._forgetChildrenRecursive = function(parentElement)
+{
+    var child = parentElement.children[0];
+    while (child) {
+        this._forgetTreeElement(child);
+        child = child.traverseNextTreeElement(false, this, true);
+    }
+}
+
+TreeOutline.prototype.getCachedTreeElement = function(representedObject)
+{
+    if (!representedObject)
+        return null;
+
+    if ("__treeElementIdentifier" in representedObject) {
+        // If this representedObject has a tree element identifier, and it is a known TreeElement
+        // in our tree we can just return that tree element.
+        var elements = this._knownTreeElements[representedObject.__treeElementIdentifier];
+        if (elements) {
+            for (var i = 0; i < elements.length; ++i)
+                if (elements[i].representedObject === representedObject)
+                    return elements[i];
+        }
+    }
+    return null;
+}
+
+TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent)
+{
+    if (!representedObject)
+        return null;
+
+    var cachedElement = this.getCachedTreeElement(representedObject);
+    if (cachedElement)
+        return cachedElement;
+
+    // The representedObject isn't know, so we start at the top of the tree and work down to find the first
+    // tree element that represents representedObject or one of its ancestors.
+    var item;
+    var found = false;
+    for (var i = 0; i < this.children.length; ++i) {
+        item = this.children[i];
+        if (item.representedObject === representedObject || isAncestor(item.representedObject, representedObject)) {
+            found = true;
+            break;
+        }
+    }
+
+    if (!found)
+        return null;
+
+    // Make sure the item that we found is connected to the root of the tree.
+    // Build up a list of representedObject's ancestors that aren't already in our tree.
+    var ancestors = [];
+    var currentObject = representedObject;
+    while (currentObject) {
+        ancestors.unshift(currentObject);
+        if (currentObject === item.representedObject)
+            break;
+        currentObject = getParent(currentObject);
+    }
+
+    // For each of those ancestors we populate them to fill in the tree.
+    for (var i = 0; i < ancestors.length; ++i) {
+        // Make sure we don't call findTreeElement with the same representedObject
+        // again, to prevent infinite recursion.
+        if (ancestors[i] === representedObject)
+            continue;
+        // FIXME: we could do something faster than findTreeElement since we will know the next
+        // ancestor exists in the tree.
+        item = this.findTreeElement(ancestors[i], isAncestor, getParent);
+        if (item && item.onpopulate)
+            item.onpopulate(item);
+    }
+
+    return this.getCachedTreeElement(representedObject);
+}
+
+TreeOutline.prototype.treeElementFromPoint = function(x, y)
+{
+    var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
+    var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
+    if (listNode)
+        return listNode.parentTreeElement || listNode.treeElement;
+    return null;
+}
+
+TreeOutline.prototype.handleKeyEvent = function(event)
+{
+    if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
+        return false;
+
+    var handled = false;
+    var nextSelectedElement;
+    if (event.keyIdentifier === "Up" && !event.altKey) {
+        nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
+        while (nextSelectedElement && !nextSelectedElement.selectable)
+            nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing);
+        handled = nextSelectedElement ? true : false;
+    } else if (event.keyIdentifier === "Down" && !event.altKey) {
+        nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
+        while (nextSelectedElement && !nextSelectedElement.selectable)
+            nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing);
+        handled = nextSelectedElement ? true : false;
+    } else if (event.keyIdentifier === "Left") {
+        if (this.selectedTreeElement.expanded) {
+            if (event.altKey)
+                this.selectedTreeElement.collapseRecursively();
+            else
+                this.selectedTreeElement.collapse();
+            handled = true;
+        } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
+            handled = true;
+            if (this.selectedTreeElement.parent.selectable) {
+                nextSelectedElement = this.selectedTreeElement.parent;
+                handled = nextSelectedElement ? true : false;
+            } else if (this.selectedTreeElement.parent)
+                this.selectedTreeElement.parent.collapse();
+        }
+    } else if (event.keyIdentifier === "Right") {
+        if (!this.selectedTreeElement.revealed()) {
+            this.selectedTreeElement.reveal();
+            handled = true;
+        } else if (this.selectedTreeElement.hasChildren) {
+            handled = true;
+            if (this.selectedTreeElement.expanded) {
+                nextSelectedElement = this.selectedTreeElement.children[0];
+                handled = nextSelectedElement ? true : false;
+            } else {
+                if (event.altKey)
+                    this.selectedTreeElement.expandRecursively();
+                else
+                    this.selectedTreeElement.expand();
+            }
+        }
+    }
+
+    if (nextSelectedElement) {
+        nextSelectedElement.reveal();
+        nextSelectedElement.select();
+    }
+
+    if (handled) {
+        event.preventDefault();
+        event.stopPropagation();
+    }
+
+    return handled;
+}
+
+TreeOutline.prototype.expand = function()
+{
+    // this is the root, do nothing
+}
+
+TreeOutline.prototype.collapse = function()
+{
+    // this is the root, do nothing
+}
+
+TreeOutline.prototype.revealed = function()
+{
+    return true;
+}
+
+TreeOutline.prototype.reveal = function()
+{
+    // this is the root, do nothing
+}
+
+TreeOutline.prototype.appendChild = TreeOutline._appendChild;
+TreeOutline.prototype.insertChild = TreeOutline._insertChild;
+TreeOutline.prototype.removeChild = TreeOutline._removeChild;
+TreeOutline.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex;
+TreeOutline.prototype.removeChildren = TreeOutline._removeChildren;
+TreeOutline.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;
+
+function TreeElement(title, representedObject, hasChildren)
+{
+    this._title = title;
+    this.representedObject = (representedObject || {});
+
+    if (this.representedObject.__treeElementIdentifier)
+        this.identifier = this.representedObject.__treeElementIdentifier;
+    else {
+        this.identifier = TreeOutline._knownTreeElementNextIdentifier++;
+        this.representedObject.__treeElementIdentifier = this.identifier;
+    }
+
+    this._hidden = false;
+    this.expanded = false;
+    this.selected = false;
+    this.hasChildren = hasChildren;
+    this.children = [];
+    this.treeOutline = null;
+    this.parent = null;
+    this.previousSibling = null;
+    this.nextSibling = null;
+    this._listItemNode = null;
+}
+
+TreeElement.prototype = {
+    selectable: true,
+    arrowToggleWidth: 10,
+
+    get listItemElement() {
+        return this._listItemNode;
+    },
+
+    get childrenListElement() {
+        return this._childrenListNode;
+    },
+
+    get title() {
+        return this._title;
+    },
+
+    set title(x) {
+        this._title = x;
+        if (this._listItemNode)
+            this._listItemNode.innerHTML = x;
+    },
+
+    get tooltip() {
+        return this._tooltip;
+    },
+
+    set tooltip(x) {
+        this._tooltip = x;
+        if (this._listItemNode)
+            this._listItemNode.title = x ? x : "";
+    },
+
+    get hasChildren() {
+        return this._hasChildren;
+    },
+
+    set hasChildren(x) {
+        if (this._hasChildren === x)
+            return;
+
+        this._hasChildren = x;
+
+        if (!this._listItemNode)
+            return;
+
+        if (x)
+            this._listItemNode.addStyleClass("parent");
+        else {
+            this._listItemNode.removeStyleClass("parent");
+            this.collapse();
+        }
+    },
+
+    get hidden() {
+        return this._hidden;
+    },
+
+    set hidden(x) {
+        if (this._hidden === x)
+            return;
+
+        this._hidden = x;
+
+        if (x) {
+            if (this._listItemNode)
+                this._listItemNode.addStyleClass("hidden");
+            if (this._childrenListNode)
+                this._childrenListNode.addStyleClass("hidden");
+        } else {
+            if (this._listItemNode)
+                this._listItemNode.removeStyleClass("hidden");
+            if (this._childrenListNode)
+                this._childrenListNode.removeStyleClass("hidden");
+        }
+    },
+
+    get shouldRefreshChildren() {
+        return this._shouldRefreshChildren;
+    },
+
+    set shouldRefreshChildren(x) {
+        this._shouldRefreshChildren = x;
+        if (x && this.expanded)
+            this.expand();
+    }
+}
+
+TreeElement.prototype.appendChild = TreeOutline._appendChild;
+TreeElement.prototype.insertChild = TreeOutline._insertChild;
+TreeElement.prototype.removeChild = TreeOutline._removeChild;
+TreeElement.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex;
+TreeElement.prototype.removeChildren = TreeOutline._removeChildren;
+TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;
+
+TreeElement.prototype._attach = function()
+{
+    if (!this._listItemNode || this.parent._shouldRefreshChildren) {
+        if (this._listItemNode && this._listItemNode.parentNode)
+            this._listItemNode.parentNode.removeChild(this._listItemNode);
+
+        this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li");
+        this._listItemNode.treeElement = this;
+        this._listItemNode.innerHTML = this._title;
+        this._listItemNode.title = this._tooltip ? this._tooltip : "";
+
+        if (this.hidden)
+            this._listItemNode.addStyleClass("hidden");
+        if (this.hasChildren)
+            this._listItemNode.addStyleClass("parent");
+        if (this.expanded)
+            this._listItemNode.addStyleClass("expanded");
+        if (this.selected)
+            this._listItemNode.addStyleClass("selected");
+
+        this._listItemNode.addEventListener("mousedown", TreeElement.treeElementSelected, false);
+        this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false);
+        this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false);
+
+        if (this.onattach)
+            this.onattach(this);
+    }
+
+    var nextSibling = null;
+    if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode)
+        nextSibling = this.nextSibling._listItemNode;
+    this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
+    if (this._childrenListNode)
+        this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
+    if (this.selected)
+        this.select();
+    if (this.expanded)
+        this.expand();
+}
+
+TreeElement.prototype._detach = function()
+{
+    if (this._listItemNode && this._listItemNode.parentNode)
+        this._listItemNode.parentNode.removeChild(this._listItemNode);
+    if (this._childrenListNode && this._childrenListNode.parentNode)
+        this._childrenListNode.parentNode.removeChild(this._childrenListNode);
+}
+
+TreeElement.treeElementSelected = function(event)
+{
+    var element = event.currentTarget;
+    if (!element || !element.treeElement || !element.treeElement.selectable)
+        return;
+
+    if (element.treeElement.isEventWithinDisclosureTriangle(event))
+        return;
+
+    element.treeElement.select();
+}
+
+TreeElement.treeElementToggled = function(event)
+{
+    var element = event.currentTarget;
+    if (!element || !element.treeElement)
+        return;
+
+    if (!element.treeElement.isEventWithinDisclosureTriangle(event))
+        return;
+
+    if (element.treeElement.expanded) {
+        if (event.altKey)
+            element.treeElement.collapseRecursively();
+        else
+            element.treeElement.collapse();
+    } else {
+        if (event.altKey)
+            element.treeElement.expandRecursively();
+        else
+            element.treeElement.expand();
+    }
+}
+
+TreeElement.treeElementDoubleClicked = function(event)
+{
+    var element = event.currentTarget;
+    if (!element || !element.treeElement)
+        return;
+
+    if (element.treeElement.ondblclick)
+        element.treeElement.ondblclick(element.treeElement, event);
+    else if (element.treeElement.hasChildren && !element.treeElement.expanded)
+        element.treeElement.expand();
+}
+
+TreeElement.prototype.collapse = function()
+{
+    if (this._listItemNode)
+        this._listItemNode.removeStyleClass("expanded");
+    if (this._childrenListNode)
+        this._childrenListNode.removeStyleClass("expanded");
+
+    this.expanded = false;
+    if (this.treeOutline)
+        this.treeOutline._treeElementsExpandedState[this.identifier] = true;
+
+    if (this.oncollapse)
+        this.oncollapse(this);
+}
+
+TreeElement.prototype.collapseRecursively = function()
+{
+    var item = this;
+    while (item) {
+        if (item.expanded)
+            item.collapse();
+        item = item.traverseNextTreeElement(false, this, true);
+    }
+}
+
+TreeElement.prototype.expand = function()
+{
+    if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
+        return;
+
+    if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) {
+        if (this._childrenListNode && this._childrenListNode.parentNode)
+            this._childrenListNode.parentNode.removeChild(this._childrenListNode);
+
+        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
+        this._childrenListNode.parentTreeElement = this;
+        this._childrenListNode.addStyleClass("children");
+
+        if (this.hidden)
+            this._childrenListNode.addStyleClass("hidden");
+
+        if (this.onpopulate)
+            this.onpopulate(this);
+
+        for (var i = 0; i < this.children.length; ++i)
+            this.children[i]._attach();
+
+        delete this._shouldRefreshChildren;
+    }
+
+    if (this._listItemNode) {
+        this._listItemNode.addStyleClass("expanded");
+        if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode)
+            this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
+    }
+
+    if (this._childrenListNode)
+        this._childrenListNode.addStyleClass("expanded");
+
+    this.expanded = true;
+    if (this.treeOutline)
+        this.treeOutline._treeElementsExpandedState[this.identifier] = true;
+
+    if (this.onexpand)
+        this.onexpand(this);
+}
+
+TreeElement.prototype.expandRecursively = function(maxDepth)
+{
+    var item = this;
+    var info = {};
+    var depth = 0;
+
+    // The Inspector uses TreeOutlines to represents object properties, so recursive expansion
+    // in some case can be infinite, since JavaScript objects can hold circular references.
+    // So default to a recursion cap of 3 levels, since that gives fairly good results.
+    if (typeof maxDepth === "undefined" || typeof maxDepth === "null")
+        maxDepth = 3;
+
+    while (item) {
+        if (depth < maxDepth)
+            item.expand();
+        item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
+        depth += info.depthChange;
+    }
+}
+
+TreeElement.prototype.hasAncestor = function(ancestor) {
+    if (!ancestor)
+        return false;
+
+    var currentNode = this.parent;
+    while (currentNode) {
+        if (ancestor === currentNode)
+            return true;
+        currentNode = currentNode.parent;
+    }
+
+    return false;
+}
+
+TreeElement.prototype.reveal = function()
+{
+    var currentAncestor = this.parent;
+    while (currentAncestor && !currentAncestor.root) {
+        if (!currentAncestor.expanded)
+            currentAncestor.expand();
+        currentAncestor = currentAncestor.parent;
+    }
+
+    if (this.onreveal)
+        this.onreveal(this);
+}
+
+TreeElement.prototype.revealed = function()
+{
+    var currentAncestor = this.parent;
+    while (currentAncestor && !currentAncestor.root) {
+        if (!currentAncestor.expanded)
+            return false;
+        currentAncestor = currentAncestor.parent;
+    }
+
+    return true;
+}
+
+TreeElement.prototype.select = function(supressOnSelect)
+{
+    if (!this.treeOutline || !this.selectable || this.selected)
+        return;
+
+    if (this.treeOutline.selectedTreeElement)
+        this.treeOutline.selectedTreeElement.deselect();
+
+    this.selected = true;
+    this.treeOutline.selectedTreeElement = this;
+    if (this._listItemNode)
+        this._listItemNode.addStyleClass("selected");
+
+    if (this.onselect && !supressOnSelect)
+        this.onselect(this);
+}
+
+TreeElement.prototype.deselect = function(supressOnDeselect)
+{
+    if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
+        return;
+
+    this.selected = false;
+    this.treeOutline.selectedTreeElement = null;
+    if (this._listItemNode)
+        this._listItemNode.removeStyleClass("selected");
+
+    if (this.ondeselect && !supressOnDeselect)
+        this.ondeselect(this);
+}
+
+TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate, info)
+{
+    if (!dontPopulate && this.hasChildren && this.onpopulate)
+        this.onpopulate(this);
+
+    if (info)
+        info.depthChange = 0;
+
+    var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0];
+    if (element && (!skipHidden || (skipHidden && this.expanded))) {
+        if (info)
+            info.depthChange = 1;
+        return element;
+    }
+
+    if (this === stayWithin)
+        return null;
+
+    element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
+    if (element)
+        return element;
+
+    element = this;
+    while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
+        if (info)
+            info.depthChange -= 1;
+        element = element.parent;
+    }
+
+    if (!element)
+        return null;
+
+    return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
+}
+
+TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate)
+{
+    var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
+    if (!dontPopulate && element && element.hasChildren && element.onpopulate)
+        element.onpopulate(element);
+
+    while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) {
+        if (!dontPopulate && element.hasChildren && element.onpopulate)
+            element.onpopulate(element);
+        element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]);
+    }
+
+    if (element)
+        return element;
+
+    if (!this.parent || this.parent.root)
+        return null;
+
+    return this.parent;
+}
+
+TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
+{
+    var left = this._listItemNode.totalOffsetLeft;
+    return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/utilities.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/utilities.js
new file mode 100644
index 0000000..e831abd
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/utilities.js
@@ -0,0 +1,905 @@
+/*
+ * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer. 
+ * 2.  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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
+ */
+
+Object.proxyType = function(objectProxy)
+{
+    if (objectProxy === null)
+        return "null";
+
+    var type = typeof objectProxy;
+    if (type !== "object" && type !== "function")
+        return type;
+
+    return objectProxy.type;
+}
+
+Object.properties = function(obj)
+{
+    var properties = [];
+    for (var prop in obj)
+        properties.push(prop);
+    return properties;
+}
+
+Object.sortedProperties = function(obj, sortFunc)
+{
+    return Object.properties(obj).sort(sortFunc);
+}
+
+Function.prototype.bind = function(thisObject)
+{
+    var func = this;
+    var args = Array.prototype.slice.call(arguments, 1);
+    return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))) };
+}
+
+Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, direction)
+{
+    var startNode;
+    var startOffset = 0;
+    var endNode;
+    var endOffset = 0;
+
+    if (!stayWithinNode)
+        stayWithinNode = this;
+
+    if (!direction || direction === "backward" || direction === "both") {
+        var node = this;
+        while (node) {
+            if (node === stayWithinNode) {
+                if (!startNode)
+                    startNode = stayWithinNode;
+                break;
+            }
+
+            if (node.nodeType === Node.TEXT_NODE) {
+                var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1));
+                for (var i = start; i >= 0; --i) {
+                    if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
+                        startNode = node;
+                        startOffset = i + 1;
+                        break;
+                    }
+                }
+            }
+
+            if (startNode)
+                break;
+
+            node = node.traversePreviousNode(false, stayWithinNode);
+        }
+
+        if (!startNode) {
+            startNode = stayWithinNode;
+            startOffset = 0;
+        }
+    } else {
+        startNode = this;
+        startOffset = offset;
+    }
+
+    if (!direction || direction === "forward" || direction === "both") {
+        node = this;
+        while (node) {
+            if (node === stayWithinNode) {
+                if (!endNode)
+                    endNode = stayWithinNode;
+                break;
+            }
+
+            if (node.nodeType === Node.TEXT_NODE) {
+                var start = (node === this ? offset : 0);
+                for (var i = start; i < node.nodeValue.length; ++i) {
+                    if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
+                        endNode = node;
+                        endOffset = i;
+                        break;
+                    }
+                }
+            }
+
+            if (endNode)
+                break;
+
+            node = node.traverseNextNode(false, stayWithinNode);
+        }
+
+        if (!endNode) {
+            endNode = stayWithinNode;
+            endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length;
+        }
+    } else {
+        endNode = this;
+        endOffset = offset;
+    }
+
+    var result = this.ownerDocument.createRange();
+    result.setStart(startNode, startOffset);
+    result.setEnd(endNode, endOffset);
+
+    return result;
+}
+
+Element.prototype.removeStyleClass = function(className) 
+{
+    // Test for the simple case before using a RegExp.
+    if (this.className === className) {
+        this.className = "";
+        return;
+    }
+
+    this.removeMatchingStyleClasses(className.escapeForRegExp());
+}
+
+Element.prototype.removeMatchingStyleClasses = function(classNameRegex)
+{
+    var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)");
+    if (regex.test(this.className))
+        this.className = this.className.replace(regex, " ");
+}
+
+Element.prototype.addStyleClass = function(className) 
+{
+    if (className && !this.hasStyleClass(className))
+        this.className += (this.className.length ? " " + className : className);
+}
+
+Element.prototype.hasStyleClass = function(className) 
+{
+    if (!className)
+        return false;
+    // Test for the simple case before using a RegExp.
+    if (this.className === className)
+        return true;
+    var regex = new RegExp("(^|\\s)" + className.escapeForRegExp() + "($|\\s)");
+    return regex.test(this.className);
+}
+
+Element.prototype.positionAt = function(x, y)
+{
+    this.style.left = x + "px";
+    this.style.top = y + "px";
+}
+
+Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray)
+{
+    for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
+        for (var i = 0; i < nameArray.length; ++i)
+            if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase())
+                return node;
+    return null;
+}
+
+Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName)
+{
+    return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
+}
+
+Node.prototype.enclosingNodeOrSelfWithClass = function(className)
+{
+    for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
+        if (node.nodeType === Node.ELEMENT_NODE && node.hasStyleClass(className))
+            return node;
+    return null;
+}
+
+Node.prototype.enclosingNodeWithClass = function(className)
+{
+    if (!this.parentNode)
+        return null;
+    return this.parentNode.enclosingNodeOrSelfWithClass(className);
+}
+
+Element.prototype.query = function(query) 
+{
+    return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+}
+
+Element.prototype.removeChildren = function()
+{
+    while (this.firstChild) 
+        this.removeChild(this.firstChild);        
+}
+
+Element.prototype.isInsertionCaretInside = function()
+{
+    var selection = window.getSelection();
+    if (!selection.rangeCount || !selection.isCollapsed)
+        return false;
+    var selectionRange = selection.getRangeAt(0);
+    return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this);
+}
+
+Element.prototype.__defineGetter__("totalOffsetLeft", function()
+{
+    var total = 0;
+    for (var element = this; element; element = element.offsetParent)
+        total += element.offsetLeft;
+    return total;
+});
+
+Element.prototype.__defineGetter__("totalOffsetTop", function()
+{
+    var total = 0;
+    for (var element = this; element; element = element.offsetParent)
+        total += element.offsetTop;
+    return total;
+});
+
+Element.prototype.offsetRelativeToWindow = function(targetWindow)
+{
+    var elementOffset = {x: 0, y: 0};
+    var curElement = this;
+    var curWindow = this.ownerDocument.defaultView;
+    while (curWindow && curElement) {
+        elementOffset.x += curElement.totalOffsetLeft;
+        elementOffset.y += curElement.totalOffsetTop;
+        if (curWindow === targetWindow)
+            break;
+
+        curElement = curWindow.frameElement;
+        curWindow = curWindow.parent;
+    }
+
+    return elementOffset;
+}
+
+Element.prototype.firstChildSkippingWhitespace = firstChildSkippingWhitespace;
+Element.prototype.lastChildSkippingWhitespace = lastChildSkippingWhitespace;
+
+Node.prototype.isWhitespace = isNodeWhitespace;
+Node.prototype.displayName = nodeDisplayName;
+Node.prototype.isAncestor = function(node)
+{
+    return isAncestorNode(this, node);
+};
+Node.prototype.isDescendant = isDescendantNode;
+Node.prototype.nextSiblingSkippingWhitespace = nextSiblingSkippingWhitespace;
+Node.prototype.previousSiblingSkippingWhitespace = previousSiblingSkippingWhitespace;
+Node.prototype.traverseNextNode = traverseNextNode;
+Node.prototype.traversePreviousNode = traversePreviousNode;
+Node.prototype.onlyTextChild = onlyTextChild;
+
+String.prototype.hasSubstring = function(string, caseInsensitive)
+{
+    if (!caseInsensitive)
+        return this.indexOf(string) !== -1;
+    return this.match(new RegExp(string.escapeForRegExp(), "i"));
+}
+
+String.prototype.escapeCharacters = function(chars)
+{
+    var foundChar = false;
+    for (var i = 0; i < chars.length; ++i) {
+        if (this.indexOf(chars.charAt(i)) !== -1) {
+            foundChar = true;
+            break;
+        }
+    }
+
+    if (!foundChar)
+        return this;
+
+    var result = "";
+    for (var i = 0; i < this.length; ++i) {
+        if (chars.indexOf(this.charAt(i)) !== -1)
+            result += "\\";
+        result += this.charAt(i);
+    }
+
+    return result;
+}
+
+String.prototype.escapeForRegExp = function()
+{
+    return this.escapeCharacters("^[]{}()\\.$*+?|");
+}
+
+String.prototype.escapeHTML = function()
+{
+    return this.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+}
+
+String.prototype.collapseWhitespace = function()
+{
+    return this.replace(/[\s\xA0]+/g, " ");
+}
+
+String.prototype.trimLeadingWhitespace = function()
+{
+    return this.replace(/^[\s\xA0]+/g, "");
+}
+
+String.prototype.trimTrailingWhitespace = function()
+{
+    return this.replace(/[\s\xA0]+$/g, "");
+}
+
+String.prototype.trimWhitespace = function()
+{
+    return this.replace(/^[\s\xA0]+|[\s\xA0]+$/g, "");
+}
+
+String.prototype.trimURL = function(baseURLDomain)
+{
+    var result = this.replace(new RegExp("^http[s]?:\/\/", "i"), "");
+    if (baseURLDomain)
+        result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), "");
+    return result;
+}
+
+function isNodeWhitespace()
+{
+    if (!this || this.nodeType !== Node.TEXT_NODE)
+        return false;
+    if (!this.nodeValue.length)
+        return true;
+    return this.nodeValue.match(/^[\s\xA0]+$/);
+}
+
+function nodeDisplayName()
+{
+    if (!this)
+        return "";
+
+    switch (this.nodeType) {
+        case Node.DOCUMENT_NODE:
+            return "Document";
+
+        case Node.ELEMENT_NODE:
+            var name = "<" + this.nodeName.toLowerCase();
+
+            if (this.hasAttributes()) {
+                var value = this.getAttribute("id");
+                if (value)
+                    name += " id=\"" + value + "\"";
+                value = this.getAttribute("class");
+                if (value)
+                    name += " class=\"" + value + "\"";
+                if (this.nodeName.toLowerCase() === "a") {
+                    value = this.getAttribute("name");
+                    if (value)
+                        name += " name=\"" + value + "\"";
+                    value = this.getAttribute("href");
+                    if (value)
+                        name += " href=\"" + value + "\"";
+                } else if (this.nodeName.toLowerCase() === "img") {
+                    value = this.getAttribute("src");
+                    if (value)
+                        name += " src=\"" + value + "\"";
+                } else if (this.nodeName.toLowerCase() === "iframe") {
+                    value = this.getAttribute("src");
+                    if (value)
+                        name += " src=\"" + value + "\"";
+                } else if (this.nodeName.toLowerCase() === "input") {
+                    value = this.getAttribute("name");
+                    if (value)
+                        name += " name=\"" + value + "\"";
+                    value = this.getAttribute("type");
+                    if (value)
+                        name += " type=\"" + value + "\"";
+                } else if (this.nodeName.toLowerCase() === "form") {
+                    value = this.getAttribute("action");
+                    if (value)
+                        name += " action=\"" + value + "\"";
+                }
+            }
+
+            return name + ">";
+
+        case Node.TEXT_NODE:
+            if (isNodeWhitespace.call(this))
+                return "(whitespace)";
+            return "\"" + this.nodeValue + "\"";
+
+        case Node.COMMENT_NODE:
+            return "<!--" + this.nodeValue + "-->";
+            
+        case Node.DOCUMENT_TYPE_NODE:
+            var docType = "<!DOCTYPE " + this.nodeName;
+            if (this.publicId) {
+                docType += " PUBLIC \"" + this.publicId + "\"";
+                if (this.systemId)
+                    docType += " \"" + this.systemId + "\"";
+            } else if (this.systemId)
+                docType += " SYSTEM \"" + this.systemId + "\"";
+            if (this.internalSubset)
+                docType += " [" + this.internalSubset + "]";
+            return docType + ">";
+    }
+
+    return this.nodeName.toLowerCase().collapseWhitespace();
+}
+
+function isAncestorNode(ancestor, node)
+{
+    if (!node || !ancestor)
+        return false;
+
+    var currentNode = node.parentNode;
+    while (currentNode) {
+        if (ancestor === currentNode)
+            return true;
+        currentNode = currentNode.parentNode;
+    }
+    return false;
+}
+
+function isDescendantNode(descendant)
+{
+    return isAncestorNode(descendant, this);
+}
+
+function nextSiblingSkippingWhitespace()
+{
+    if (!this)
+        return;
+    var node = this.nextSibling;
+    while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node))
+        node = node.nextSibling;
+    return node;
+}
+
+function previousSiblingSkippingWhitespace()
+{
+    if (!this)
+        return;
+    var node = this.previousSibling;
+    while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node))
+        node = node.previousSibling;
+    return node;
+}
+
+function firstChildSkippingWhitespace()
+{
+    if (!this)
+        return;
+    var node = this.firstChild;
+    while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node))
+        node = nextSiblingSkippingWhitespace.call(node);
+    return node;
+}
+
+function lastChildSkippingWhitespace()
+{
+    if (!this)
+        return;
+    var node = this.lastChild;
+    while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node))
+        node = previousSiblingSkippingWhitespace.call(node);
+    return node;
+}
+
+function traverseNextNode(skipWhitespace, stayWithin)
+{
+    if (!this)
+        return;
+
+    var node = skipWhitespace ? firstChildSkippingWhitespace.call(this) : this.firstChild;
+    if (node)
+        return node;
+
+    if (stayWithin && this === stayWithin)
+        return null;
+
+    node = skipWhitespace ? nextSiblingSkippingWhitespace.call(this) : this.nextSibling;
+    if (node)
+        return node;
+
+    node = this;
+    while (node && !(skipWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling) && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin))
+        node = node.parentNode;
+    if (!node)
+        return null;
+
+    return skipWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling;
+}
+
+function traversePreviousNode(skipWhitespace, stayWithin)
+{
+    if (!this)
+        return;
+    if (stayWithin && this === stayWithin)
+        return null;
+    var node = skipWhitespace ? previousSiblingSkippingWhitespace.call(this) : this.previousSibling;
+    while (node && (skipWhitespace ? lastChildSkippingWhitespace.call(node) : node.lastChild) )
+        node = skipWhitespace ? lastChildSkippingWhitespace.call(node) : node.lastChild;
+    if (node)
+        return node;
+    return this.parentNode;
+}
+
+function onlyTextChild(ignoreWhitespace)
+{
+    if (!this)
+        return null;
+
+    var firstChild = ignoreWhitespace ? firstChildSkippingWhitespace.call(this) : this.firstChild;
+    if (!firstChild || firstChild.nodeType !== Node.TEXT_NODE)
+        return null;
+
+    var sibling = ignoreWhitespace ? nextSiblingSkippingWhitespace.call(firstChild) : firstChild.nextSibling;
+    return sibling ? null : firstChild;
+}
+
+function nodeTitleInfo(hasChildren, linkify)
+{
+    var info = {title: "", hasChildren: hasChildren};
+
+    switch (this.nodeType) {
+        case Node.DOCUMENT_NODE:
+            info.title = "Document";
+            break;
+
+        case Node.ELEMENT_NODE:
+            info.title = "<span class=\"webkit-html-tag\">&lt;" + this.nodeName.toLowerCase().escapeHTML();
+
+            if (this.hasAttributes()) {
+                for (var i = 0; i < this.attributes.length; ++i) {
+                    var attr = this.attributes[i];
+                    info.title += " <span class=\"webkit-html-attribute\"><span class=\"webkit-html-attribute-name\">" + attr.name.escapeHTML() + "</span>=&#8203;\"";
+
+                    var value = attr.value;
+                    if (linkify && (attr.name === "src" || attr.name === "href")) {
+                        var value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B");
+                        info.title += linkify(attr.value, value, "webkit-html-attribute-value", this.nodeName.toLowerCase() == "a");
+                    } else {
+                        var value = value.escapeHTML();
+                        value = value.replace(/([\/;:\)\]\}])/g, "$1&#8203;");
+                        info.title += "<span class=\"webkit-html-attribute-value\">" + value + "</span>";
+                    }
+                    info.title += "\"</span>";
+                }
+            }
+            info.title += "&gt;</span>&#8203;";
+
+            // If this element only has a single child that is a text node,
+            // just show that text and the closing tag inline rather than
+            // create a subtree for them
+
+            var textChild = onlyTextChild.call(this, Preferences.ignoreWhitespace);
+            var showInlineText = textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength;
+
+            if (showInlineText) {
+                info.title += "<span class=\"webkit-html-text-node\">" + textChild.nodeValue.escapeHTML() + "</span>&#8203;<span class=\"webkit-html-tag\">&lt;/" + this.nodeName.toLowerCase().escapeHTML() + "&gt;</span>";
+                info.hasChildren = false;
+            }
+            break;
+
+        case Node.TEXT_NODE:
+            if (isNodeWhitespace.call(this))
+                info.title = "(whitespace)";
+            else
+                info.title = "\"<span class=\"webkit-html-text-node\">" + this.nodeValue.escapeHTML() + "</span>\"";
+            break
+
+        case Node.COMMENT_NODE:
+            info.title = "<span class=\"webkit-html-comment\">&lt;!--" + this.nodeValue.escapeHTML() + "--&gt;</span>";
+            break;
+
+        case Node.DOCUMENT_TYPE_NODE:
+            info.title = "<span class=\"webkit-html-doctype\">&lt;!DOCTYPE " + this.nodeName;
+            if (this.publicId) {
+                info.title += " PUBLIC \"" + this.publicId + "\"";
+                if (this.systemId)
+                    info.title += " \"" + this.systemId + "\"";
+            } else if (this.systemId)
+                info.title += " SYSTEM \"" + this.systemId + "\"";
+            if (this.internalSubset)
+                info.title += " [" + this.internalSubset + "]";
+            info.title += "&gt;</span>";
+            break;
+        default:
+            info.title = this.nodeName.toLowerCase().collapseWhitespace().escapeHTML();
+    }
+
+    return info;
+}
+
+function getDocumentForNode(node) {
+    return node.nodeType == Node.DOCUMENT_NODE ? node : node.ownerDocument;
+}
+
+function parentNode(node) {
+    return node.parentNode;
+}
+
+Number.secondsToString = function(seconds, formatterFunction, higherResolution)
+{
+    if (!formatterFunction)
+        formatterFunction = String.sprintf;
+
+    var ms = seconds * 1000;
+    if (higherResolution && ms < 1000)
+        return formatterFunction("%.3fms", ms);
+    else if (ms < 1000)
+        return formatterFunction("%.0fms", ms);
+
+    if (seconds < 60)
+        return formatterFunction("%.2fs", seconds);
+
+    var minutes = seconds / 60;
+    if (minutes < 60)
+        return formatterFunction("%.1fmin", minutes);
+
+    var hours = minutes / 60;
+    if (hours < 24)
+        return formatterFunction("%.1fhrs", hours);
+
+    var days = hours / 24;
+    return formatterFunction("%.1f days", days);
+}
+
+Number.bytesToString = function(bytes, formatterFunction, higherResolution)
+{
+    if (!formatterFunction)
+        formatterFunction = String.sprintf;
+    if (typeof higherResolution === "undefined")
+        higherResolution = true;
+
+    if (bytes < 1024)
+        return formatterFunction("%.0fB", bytes);
+
+    var kilobytes = bytes / 1024;
+    if (higherResolution && kilobytes < 1024)
+        return formatterFunction("%.2fKB", kilobytes);
+    else if (kilobytes < 1024)
+        return formatterFunction("%.0fKB", kilobytes);
+
+    var megabytes = kilobytes / 1024;
+    if (higherResolution)
+        return formatterFunction("%.3fMB", megabytes);
+    else
+        return formatterFunction("%.0fMB", megabytes);
+}
+
+Number.constrain = function(num, min, max)
+{
+    if (num < min)
+        num = min;
+    else if (num > max)
+        num = max;
+    return num;
+}
+
+HTMLTextAreaElement.prototype.moveCursorToEnd = function()
+{
+    var length = this.value.length;
+    this.setSelectionRange(length, length);
+}
+
+Array.prototype.remove = function(value, onlyFirst)
+{
+    if (onlyFirst) {
+        var index = this.indexOf(value);
+        if (index !== -1)
+            this.splice(index, 1);
+        return;
+    }
+
+    var length = this.length;
+    for (var i = 0; i < length; ++i) {
+        if (this[i] === value)
+            this.splice(i, 1);
+    }
+}
+
+function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction)
+{
+    // indexOf returns (-lowerBound - 1). Taking (-result - 1) works out to lowerBound.
+    return (-indexOfObjectInListSortedByFunction(anObject, aList, aFunction) - 1);
+}
+
+function indexOfObjectInListSortedByFunction(anObject, aList, aFunction)
+{
+    var first = 0;
+    var last = aList.length - 1;
+    var floor = Math.floor;
+    var mid, c;
+
+    while (first <= last) {
+        mid = floor((first + last) / 2);
+        c = aFunction(anObject, aList[mid]);
+
+        if (c > 0)
+            first = mid + 1;
+        else if (c < 0)
+            last = mid - 1;
+        else {
+            // Return the first occurance of an item in the list.
+            while (mid > 0 && aFunction(anObject, aList[mid - 1]) === 0)
+                mid--;
+            first = mid;
+            break;
+        }
+    }
+
+    // By returning 1 less than the negative lower search bound, we can reuse this function
+    // for both indexOf and insertionIndexFor, with some simple arithmetic.
+    return (-first - 1);
+}
+
+String.sprintf = function(format)
+{
+    return String.vsprintf(format, Array.prototype.slice.call(arguments, 1));
+}
+
+String.tokenizeFormatString = function(format)
+{
+    var tokens = [];
+    var substitutionIndex = 0;
+
+    function addStringToken(str)
+    {
+        tokens.push({ type: "string", value: str });
+    }
+
+    function addSpecifierToken(specifier, precision, substitutionIndex)
+    {
+        tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex });
+    }
+
+    var index = 0;
+    for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
+        addStringToken(format.substring(index, precentIndex));
+        index = precentIndex + 1;
+
+        if (format[index] === "%") {
+            addStringToken("%");
+            ++index;
+            continue;
+        }
+
+        if (!isNaN(format[index])) {
+            // The first character is a number, it might be a substitution index.
+            var number = parseInt(format.substring(index));
+            while (!isNaN(format[index]))
+                ++index;
+            // If the number is greater than zero and ends with a "$",
+            // then this is a substitution index.
+            if (number > 0 && format[index] === "$") {
+                substitutionIndex = (number - 1);
+                ++index;
+            }
+        }
+
+        var precision = -1;
+        if (format[index] === ".") {
+            // This is a precision specifier. If no digit follows the ".",
+            // then the precision should be zero.
+            ++index;
+            precision = parseInt(format.substring(index));
+            if (isNaN(precision))
+                precision = 0;
+            while (!isNaN(format[index]))
+                ++index;
+        }
+
+        addSpecifierToken(format[index], precision, substitutionIndex);
+
+        ++substitutionIndex;
+        ++index;
+    }
+
+    addStringToken(format.substring(index));
+
+    return tokens;
+}
+
+String.standardFormatters = {
+    d: function(substitution)
+    {
+        substitution = parseInt(substitution);
+        return !isNaN(substitution) ? substitution : 0;
+    },
+
+    f: function(substitution, token)
+    {
+        substitution = parseFloat(substitution);
+        if (substitution && token.precision > -1)
+            substitution = substitution.toFixed(token.precision);
+        return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0);
+    },
+
+    s: function(substitution)
+    {
+        return substitution;
+    },
+};
+
+String.vsprintf = function(format, substitutions)
+{
+    return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
+}
+
+String.format = function(format, substitutions, formatters, initialValue, append)
+{
+    if (!format || !substitutions || !substitutions.length)
+        return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions };
+
+    function prettyFunctionName()
+    {
+        return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")";
+    }
+
+    function warn(msg)
+    {
+        console.warn(prettyFunctionName() + ": " + msg);
+    }
+
+    function error(msg)
+    {
+        console.error(prettyFunctionName() + ": " + msg);
+    }
+
+    var result = initialValue;
+    var tokens = String.tokenizeFormatString(format);
+    var usedSubstitutionIndexes = {};
+
+    for (var i = 0; i < tokens.length; ++i) {
+        var token = tokens[i];
+
+        if (token.type === "string") {
+            result = append(result, token.value);
+            continue;
+        }
+
+        if (token.type !== "specifier") {
+            error("Unknown token type \"" + token.type + "\" found.");
+            continue;
+        }
+
+        if (token.substitutionIndex >= substitutions.length) {
+            // If there are not enough substitutions for the current substitutionIndex
+            // just output the format specifier literally and move on.
+            error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped.");
+            result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier);
+            continue;
+        }
+
+        usedSubstitutionIndexes[token.substitutionIndex] = true;
+
+        if (!(token.specifier in formatters)) {
+            // Encountered an unsupported format character, treat as a string.
+            warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string.");
+            result = append(result, substitutions[token.substitutionIndex]);
+            continue;
+        }
+
+        result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
+    }
+
+    var unusedSubstitutions = [];
+    for (var i = 0; i < substitutions.length; ++i) {
+        if (i in usedSubstitutionIndexes)
+            continue;
+        unusedSubstitutions.push(substitutions[i]);
+    }
+
+    return { formattedResult: result, unusedSubstitutions: unusedSubstitutions };
+}
diff --git a/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.exe b/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.exe
new file mode 100644
index 0000000..a979e1b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.exe
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.pdb b/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.pdb
new file mode 100644
index 0000000..aaa155a1
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.pdb
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.dll b/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.dll
new file mode 100644
index 0000000..f7eca43
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.pdb b/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.pdb
new file mode 100644
index 0000000..6326dce2
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.pdb
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/setup.pdb b/chrome_frame/tools/test/reference_build/chrome/setup.pdb
new file mode 100644
index 0000000..046e7a1
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/setup.pdb
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/syncapi.dll b/chrome_frame/tools/test/reference_build/chrome/syncapi.dll
new file mode 100644
index 0000000..e3f7e31
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/syncapi.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/themes/default.dll b/chrome_frame/tools/test/reference_build/chrome/themes/default.dll
new file mode 100644
index 0000000..0261aac
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/themes/default.dll
Binary files differ
diff --git a/chrome_frame/tools/test/reference_build/chrome/wow_helper.exe b/chrome_frame/tools/test/reference_build/chrome/wow_helper.exe
new file mode 100644
index 0000000..f9bfb4b
--- /dev/null
+++ b/chrome_frame/tools/test/reference_build/chrome/wow_helper.exe
Binary files differ
diff --git a/chrome_frame/unittest_precompile.cc b/chrome_frame/unittest_precompile.cc
new file mode 100644
index 0000000..9fb41a7
--- /dev/null
+++ b/chrome_frame/unittest_precompile.cc
@@ -0,0 +1,5 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/unittest_precompile.h"
diff --git a/chrome_frame/unittest_precompile.h b/chrome_frame/unittest_precompile.h
new file mode 100644
index 0000000..814f508
--- /dev/null
+++ b/chrome_frame/unittest_precompile.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2009 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.
+
+// precompiled.h : include file for standard system include files,
+// or project specific include files that are used frequently,
+// but are changed infrequently
+
+#ifndef CHROME_FRAME_UNITTEST_PRECOMPILE_H_
+#define CHROME_FRAME_UNITTEST_PRECOMPILE_H_
+
+#include "resource.h"
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <atlctl.h>
+
+#endif  // CHROME_FRAME_UNITTEST_PRECOMPILE_H_
diff --git a/chrome_frame/urlmon_upload_data_stream.cc b/chrome_frame/urlmon_upload_data_stream.cc
new file mode 100644
index 0000000..af7a1de
--- /dev/null
+++ b/chrome_frame/urlmon_upload_data_stream.cc
@@ -0,0 +1,99 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/urlmon_upload_data_stream.h"
+
+#include "net/base/io_buffer.h"
+
+void UrlmonUploadDataStream::Initialize(net::UploadData* upload_data) {
+  upload_data_ = upload_data;
+  request_body_stream_.reset(new net::UploadDataStream(upload_data));
+}
+
+STDMETHODIMP UrlmonUploadDataStream::Read(void* pv, ULONG cb, ULONG* read) {
+  if (pv == NULL) {
+    NOTREACHED();
+    return E_POINTER;
+  }
+
+  // Have we already read past the end of the stream?
+  if (request_body_stream_->position() >= request_body_stream_->size()) {
+    if (read) {
+      *read = 0;
+    }
+    return S_FALSE;
+  }
+
+  uint64 total_bytes_to_copy = std::min(static_cast<uint64>(cb),
+      request_body_stream_->size() - request_body_stream_->position());
+  uint64 initial_position = request_body_stream_->position();
+
+  uint64 bytes_copied = 0;
+
+  char* write_pointer = reinterpret_cast<char*>(pv);
+  while (bytes_copied < total_bytes_to_copy) {
+    net::IOBuffer* buf = request_body_stream_->buf();
+
+    // Make sure our length doesn't run past the end of the available data.
+    size_t bytes_to_copy_now = static_cast<size_t>(
+        std::min(static_cast<uint64>(request_body_stream_->buf_len()),
+                 total_bytes_to_copy - bytes_copied));
+
+    memcpy(write_pointer, buf->data(), bytes_to_copy_now);
+
+    // Advance our copy tally
+    bytes_copied += bytes_to_copy_now;
+
+    // Advance our write pointer
+    write_pointer += bytes_to_copy_now;
+
+    // Advance the UploadDataStream read pointer:
+    request_body_stream_->DidConsume(bytes_to_copy_now);
+  }
+
+  DCHECK(bytes_copied == total_bytes_to_copy);
+  DCHECK(request_body_stream_->position() ==
+         initial_position + total_bytes_to_copy);
+
+  if (read) {
+    *read = static_cast<ULONG>(total_bytes_to_copy);
+  }
+
+  return S_OK;
+}
+
+STDMETHODIMP UrlmonUploadDataStream::Seek(LARGE_INTEGER move, DWORD origin,
+                                          ULARGE_INTEGER* new_pos) {
+  // UploadDataStream is really not very seek-able, so for now allow
+  // STREAM_SEEK_SETs to work with a 0 offset, but fail on everything else.
+  if (origin == STREAM_SEEK_SET && move.QuadPart == 0) {
+    if (request_body_stream_->position() != 0) {
+      request_body_stream_.reset(new net::UploadDataStream(upload_data_));
+    }
+    if (new_pos) {
+      new_pos->QuadPart = 0;
+    }
+    return S_OK;
+  }
+
+  DCHECK(false) << __FUNCTION__;
+  return STG_E_INVALIDFUNCTION;
+}
+
+STDMETHODIMP UrlmonUploadDataStream::Stat(STATSTG *stat_stg,
+                                          DWORD grf_stat_flag) {
+  if (stat_stg == NULL)
+    return E_POINTER;
+
+  memset(stat_stg, 0, sizeof(STATSTG));
+  if (0 == (grf_stat_flag & STATFLAG_NONAME)) {
+    const wchar_t kStreamBuffer[] = L"PostStream";
+    stat_stg->pwcsName =
+        static_cast<wchar_t*>(::CoTaskMemAlloc(sizeof(kStreamBuffer)));
+    lstrcpy(stat_stg->pwcsName, kStreamBuffer);
+  }
+  stat_stg->type = STGTY_STREAM;
+  stat_stg->cbSize.QuadPart = upload_data_->GetContentLength();
+  return S_OK;
+}
diff --git a/chrome_frame/urlmon_upload_data_stream.h b/chrome_frame/urlmon_upload_data_stream.h
new file mode 100644
index 0000000..eacb433
--- /dev/null
+++ b/chrome_frame/urlmon_upload_data_stream.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_URLMON_UPLOAD_DATA_STREAM_H_
+#define CHROME_FRAME_URLMON_UPLOAD_DATA_STREAM_H_
+
+#include <urlmon.h>
+#include <atlbase.h>
+#include <atlcom.h>
+
+#include "base/logging.h"
+#include "base/ref_counted.h"
+
+#include "net/base/upload_data.h"
+#include "net/base/upload_data_stream.h"
+
+// Provides an IStream interface to the very different UploadDataStream
+// implementation.
+class UrlmonUploadDataStream : public CComObjectRoot,
+                               public IStream {
+ public:
+  UrlmonUploadDataStream() {}
+
+  BEGIN_COM_MAP(UrlmonUploadDataStream)
+    COM_INTERFACE_ENTRY(ISequentialStream)
+    COM_INTERFACE_ENTRY(IStream)
+  END_COM_MAP()
+
+  void Initialize(net::UploadData* upload_data);
+
+  // Partial implementation of IStream.
+  STDMETHOD(Read)(void* pv, ULONG cb, ULONG* read);
+
+  // E_NOTIMPL the rest and DCHECK if they get called (could also use
+  // IStreamImpl but we'd lose the DCHECKS().
+  STDMETHOD(Write)(const void * buffer, ULONG size, ULONG* size_written) {
+    DCHECK(false) << __FUNCTION__;
+    return E_NOTIMPL;
+  }
+
+  STDMETHOD(CopyTo)(IStream* stream, ULARGE_INTEGER cb, ULARGE_INTEGER* read,
+                    ULARGE_INTEGER* written) {
+    DCHECK(false) << __FUNCTION__;
+    return E_NOTIMPL;
+  }
+
+  STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos);
+
+  STDMETHOD(SetSize)(ULARGE_INTEGER new_size) {
+    DCHECK(false) << __FUNCTION__;
+    return E_NOTIMPL;
+  }
+
+  STDMETHOD(Commit)(DWORD flags) {
+    DCHECK(false) << __FUNCTION__;
+    return E_NOTIMPL;
+  }
+
+  STDMETHOD(Revert)() {
+    DCHECK(false) << __FUNCTION__;
+    return E_NOTIMPL;
+  }
+
+  STDMETHOD(LockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb,
+                        DWORD type) {
+    DCHECK(false) << __FUNCTION__;
+    return E_NOTIMPL;
+  }
+
+  STDMETHOD(UnlockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb,
+                          DWORD type) {
+    DCHECK(false) << __FUNCTION__;
+    return E_NOTIMPL;
+  }
+
+  STDMETHOD(Stat)(STATSTG *pstatstg, DWORD grfStatFlag);
+
+  STDMETHOD(Clone)(IStream** stream) {
+    DCHECK(false) << __FUNCTION__;
+    return E_NOTIMPL;
+  }
+
+ private:
+  scoped_refptr<net::UploadData> upload_data_;
+  scoped_ptr<net::UploadDataStream> request_body_stream_;
+};
+
+#endif  // CHROME_FRAME_URLMON_UPLOAD_DATA_STREAM_H_
diff --git a/chrome_frame/urlmon_upload_data_stream_unittest.cc b/chrome_frame/urlmon_upload_data_stream_unittest.cc
new file mode 100644
index 0000000..a34ba2c
--- /dev/null
+++ b/chrome_frame/urlmon_upload_data_stream_unittest.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 2009 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.
+
+#include "gtest/gtest.h"
+
+#include "base/ref_counted.h"
+#include "base/scoped_comptr_win.h"
+#include "chrome_frame/urlmon_upload_data_stream.h"
+
+TEST(UrlmonUploadDataStreamTest, TestBasicRead) {
+  char random_string[] = "some random data, no really this totally random";
+  int random_string_length = strlen(random_string);
+  scoped_refptr<net::UploadData> upload_data = new net::UploadData();
+  upload_data->AppendBytes(random_string, random_string_length);
+
+  CComObject<UrlmonUploadDataStream>* upload_stream = NULL;
+  HRESULT hr =
+      CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream);
+  ASSERT_TRUE(SUCCEEDED(hr));
+
+  upload_stream->Initialize(upload_data.get());
+  ScopedComPtr<IStream> upload_istream(upload_stream);
+
+  char buffer[500];
+  memset(buffer, 0, 500);
+  ULONG bytes_read = 0;
+  hr = upload_istream->Read(buffer, 500, &bytes_read);
+
+  EXPECT_TRUE(SUCCEEDED(hr));
+  EXPECT_EQ(bytes_read, random_string_length);
+  EXPECT_TRUE(strcmp(buffer, random_string) == 0);
+
+  char buffer2[500];
+  memset(buffer2, 0, 500);
+  ULONG bytes_read2 = 0;
+  hr = upload_istream->Read(buffer2, 500, &bytes_read2);
+
+  EXPECT_EQ(S_FALSE, hr);
+  EXPECT_EQ(bytes_read2, 0);
+  EXPECT_FALSE(strcmp(buffer2, random_string) == 0);
+}
+
+TEST(UrlmonUploadDataStreamTest, TestBigRead) {
+  const size_t kBigBufferLength = 100000;
+  char big_buffer[kBigBufferLength];
+  memset(big_buffer, 'a', kBigBufferLength);
+
+  scoped_refptr<net::UploadData> upload_data = new net::UploadData();
+  upload_data->AppendBytes(big_buffer, kBigBufferLength);
+
+  CComObject<UrlmonUploadDataStream>* upload_stream = NULL;
+  HRESULT hr =
+      CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream);
+  ASSERT_TRUE(SUCCEEDED(hr));
+
+  upload_stream->Initialize(upload_data.get());
+  ScopedComPtr<IStream> upload_istream(upload_stream);
+
+  char big_rcv_buffer[kBigBufferLength];
+  int write_pos = 0;
+  ULONG bytes_read = 0;
+  hr = E_UNEXPECTED;
+
+  while ((hr = upload_istream->Read(&big_rcv_buffer[write_pos],
+                                    kBigBufferLength,
+                                    &bytes_read)) != S_FALSE) {
+    EXPECT_TRUE(SUCCEEDED(hr));
+    EXPECT_GT(bytes_read, static_cast<ULONG>(0));
+
+    write_pos += bytes_read;
+    bytes_read = 0;
+  }
+
+  EXPECT_EQ(S_FALSE, hr);
+  EXPECT_TRUE((write_pos + bytes_read) == kBigBufferLength);
+  EXPECT_EQ(0, memcmp(big_buffer, big_rcv_buffer, kBigBufferLength));
+}
+
+TEST(UrlmonUploadDataStreamTest, TestStat) {
+  char random_string[] = "some random data, no really this totally random";
+  int random_string_length = strlen(random_string);
+  scoped_refptr<net::UploadData> upload_data = new net::UploadData();
+  upload_data->AppendBytes(random_string, random_string_length);
+
+  CComObject<UrlmonUploadDataStream>* upload_stream = NULL;
+  HRESULT hr =
+      CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream);
+  ASSERT_TRUE(SUCCEEDED(hr));
+
+  upload_stream->Initialize(upload_data.get());
+  ScopedComPtr<IStream> upload_istream(upload_stream);
+
+  STATSTG statstg;
+  hr = upload_stream->Stat(&statstg, STATFLAG_NONAME);
+  EXPECT_TRUE(SUCCEEDED(hr));
+  EXPECT_EQ(static_cast<LONGLONG>(random_string_length),
+            statstg.cbSize.QuadPart);
+}
+
+TEST(UrlmonUploadDataStreamTest, TestRepeatedRead) {
+  char random_string[] = "some random data, no really this totally random";
+  int random_string_length = strlen(random_string);
+  scoped_refptr<net::UploadData> upload_data = new net::UploadData();
+  upload_data->AppendBytes(random_string, random_string_length);
+
+  CComObject<UrlmonUploadDataStream>* upload_stream = NULL;
+  HRESULT hr =
+      CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream);
+  ASSERT_TRUE(SUCCEEDED(hr));
+
+  upload_stream->Initialize(upload_data.get());
+  ScopedComPtr<IStream> upload_istream(upload_stream);
+
+  char buffer[500];
+  memset(buffer, 0, 500);
+  ULONG bytes_read = 0;
+  hr = upload_istream->Read(buffer, 500, &bytes_read);
+
+  EXPECT_TRUE(SUCCEEDED(hr));
+  EXPECT_EQ(bytes_read, random_string_length);
+  EXPECT_EQ(0, strcmp(buffer, random_string));
+
+  char buffer2[500];
+  memset(buffer2, 0, 500);
+  ULONG bytes_read2 = 0;
+
+  for (int i = 0; i < 10; i++) {
+    hr = upload_istream->Read(buffer2, 500, &bytes_read2);
+    EXPECT_EQ(S_FALSE, hr);
+    EXPECT_EQ(bytes_read2, 0);
+    EXPECT_NE(0, strcmp(buffer2, random_string));
+  }
+}
+
+TEST(UrlmonUploadDataStreamTest, TestZeroRead) {
+  char random_string[] = "some random data, no really this totally random";
+  int random_string_length = strlen(random_string);
+  scoped_refptr<net::UploadData> upload_data = new net::UploadData();
+  upload_data->AppendBytes(random_string, random_string_length);
+
+  CComObject<UrlmonUploadDataStream>* upload_stream = NULL;
+  HRESULT hr =
+      CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream);
+  ASSERT_TRUE(SUCCEEDED(hr));
+
+  upload_stream->Initialize(upload_data.get());
+  ScopedComPtr<IStream> upload_istream(upload_stream);
+
+  char buffer[500];
+  memset(buffer, 0, 500);
+  ULONG bytes_read = 42;
+  hr = upload_istream->Read(&buffer[0], 0, &bytes_read);
+
+  EXPECT_EQ(S_OK, hr);
+  EXPECT_EQ(0, bytes_read);
+
+  char buffer2[500];
+  memset(&buffer2[0], 0, 500);
+  EXPECT_EQ(0, memcmp(buffer, buffer2, 500));
+}
+
diff --git a/chrome_frame/urlmon_url_request.cc b/chrome_frame/urlmon_url_request.cc
new file mode 100644
index 0000000..f51da418
--- /dev/null
+++ b/chrome_frame/urlmon_url_request.cc
@@ -0,0 +1,751 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/urlmon_url_request.h"
+
+#include <wininet.h>
+
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/logging.h"
+#include "chrome_frame/urlmon_upload_data_stream.h"
+#include "net/http/http_util.h"
+#include "net/http/http_response_headers.h"
+
+static const LARGE_INTEGER kZero = {0};
+static const ULARGE_INTEGER kUnsignedZero = {0};
+int UrlmonUrlRequest::instance_count_ = 0;
+const char kXFrameOptionsHeader[] = "X-Frame-Options";
+
+UrlmonUrlRequest::UrlmonUrlRequest()
+    : pending_read_size_(0),
+      status_(URLRequestStatus::FAILED, net::ERR_FAILED),
+      thread_(PlatformThread::CurrentId()),
+      is_request_started_(false),
+      post_data_len_(0),
+      redirect_status_(0),
+      parent_window_(NULL) {
+  DLOG(INFO) << StringPrintf("Created request. Obj: %X", this)
+      << " Count: " << ++instance_count_;
+}
+
+UrlmonUrlRequest::~UrlmonUrlRequest() {
+  DLOG(INFO) << StringPrintf("Deleted request. Obj: %X", this)
+      << " Count: " << --instance_count_;
+}
+
+bool UrlmonUrlRequest::Start() {
+  DCHECK_EQ(PlatformThread::CurrentId(), thread_);
+
+  status_.set_status(URLRequestStatus::IO_PENDING);
+  HRESULT hr = StartAsyncDownload();
+  if (FAILED(hr)) {
+    // Do not call EndRequest() here since it will attempt to free references
+    // that have not been established.
+    status_.set_os_error(HresultToNetError(hr));
+    status_.set_status(URLRequestStatus::FAILED);
+    DLOG(ERROR) << "StartAsyncDownload failed";
+    OnResponseEnd(status_);
+    return false;
+  }
+
+  // Take a self reference to maintain COM lifetime. This will be released
+  // in EndRequest. Set a flag indicating that we have an additional
+  // reference here
+  is_request_started_ = true;
+  AddRef();
+  request_handler()->AddRequest(this);
+  return true;
+}
+
+void UrlmonUrlRequest::Stop() {
+  DCHECK_EQ(PlatformThread::CurrentId(), thread_);
+
+  if (binding_) {
+    binding_->Abort();
+  } else {
+    status_.set_status(URLRequestStatus::CANCELED);
+    status_.set_os_error(net::ERR_FAILED);
+    EndRequest();
+  }
+}
+
+bool UrlmonUrlRequest::Read(int bytes_to_read) {
+  DCHECK_EQ(PlatformThread::CurrentId(), thread_);
+
+  DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this);
+
+  // Send cached data if available.
+  CComObjectStackEx<SendStream> send_stream;
+  send_stream.Initialize(this);
+  size_t bytes_copied = 0;
+  if (cached_data_.is_valid() && cached_data_.Read(&send_stream, bytes_to_read,
+                                                   &bytes_copied)) {
+    DLOG(INFO) << StringPrintf("URL: %s Obj: %X - bytes read from cache: %d",
+                               url().c_str(), this, bytes_copied);
+    return true;
+  }
+
+  // if the request is finished or there's nothing more to read
+  // then end the request
+  if (!status_.is_io_pending() || !binding_) {
+    DLOG(INFO) << StringPrintf("URL: %s Obj: %X. Response finished. Status: %d",
+                               url().c_str(), this, status_.status());
+    EndRequest();
+    return true;
+  }
+
+  pending_read_size_ = bytes_to_read;
+  DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
+      "- Read pending for: " << bytes_to_read;
+  return true;
+}
+
+STDMETHODIMP UrlmonUrlRequest::OnStartBinding(
+    DWORD reserved, IBinding *binding) {
+  binding_ = binding;
+  return S_OK;
+}
+
+STDMETHODIMP UrlmonUrlRequest::GetPriority(LONG *priority) {
+  if (!priority)
+    return E_POINTER;
+  *priority = THREAD_PRIORITY_NORMAL;
+  return S_OK;
+}
+
+STDMETHODIMP UrlmonUrlRequest::OnLowResource(DWORD reserved) {
+  return S_OK;
+}
+
+STDMETHODIMP UrlmonUrlRequest::OnProgress(ULONG progress, ULONG max_progress,
+    ULONG status_code, LPCWSTR status_text) {
+  switch (status_code) {
+    case BINDSTATUS_REDIRECTING:
+      DCHECK(status_text != NULL);
+      DLOG(INFO) << "URL: " << url() << " redirected to "
+                 << status_text;
+      redirect_url_ = status_text;
+      // Fetch the redirect status as they aren't all equal (307 in particular
+      // retains the HTTP request verb).
+      redirect_status_ = GetHttpResponseStatus();
+      break;
+
+    default:
+      DLOG(INFO) << " Obj: " << std::hex << this << " OnProgress(" << url()
+          << StringPrintf(L") code: %i status: %ls", status_code, status_text);
+      break;
+  }
+
+  return S_OK;
+}
+
+STDMETHODIMP UrlmonUrlRequest::OnStopBinding(HRESULT result, LPCWSTR error) {
+  DCHECK_EQ(PlatformThread::CurrentId(), thread_);
+
+  DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
+      " - Request stopped, Result: " << std::hex << result <<
+      " Status: " << status_.status();
+  if (FAILED(result)) {
+    status_.set_status(URLRequestStatus::FAILED);
+    status_.set_os_error(HresultToNetError(result));
+    EndRequest();
+  } else {
+    status_.set_status(URLRequestStatus::SUCCESS);
+    status_.set_os_error(0);
+  }
+
+  // Release these variables after reporting EndRequest since we might need to
+  // access their state.
+  binding_ = NULL;
+  bind_context_ = NULL;
+
+  return S_OK;
+}
+
+STDMETHODIMP UrlmonUrlRequest::GetBindInfo(DWORD* bind_flags,
+    BINDINFO *bind_info) {
+  DCHECK_EQ(PlatformThread::CurrentId(), thread_);
+
+  if ((bind_info == NULL) || (bind_info->cbSize == 0) || (bind_flags == NULL))
+    return E_INVALIDARG;
+
+  *bind_flags = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
+  if (LowerCaseEqualsASCII(method(), "get")) {
+    bind_info->dwBindVerb = BINDVERB_GET;
+  } else if (LowerCaseEqualsASCII(method(), "post")) {
+    bind_info->dwBindVerb = BINDVERB_POST;
+
+    // Bypass caching proxies on POSTs and avoid writing responses to POST
+    // requests to the browser's cache.
+    *bind_flags |= BINDF_GETNEWESTVERSION | BINDF_NOWRITECACHE |
+                   BINDF_PRAGMA_NO_CACHE;
+
+    // Initialize the STGMEDIUM.
+    memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM));
+    bind_info->grfBindInfoF = 0;
+    bind_info->szCustomVerb = NULL;
+
+    scoped_refptr<net::UploadData> upload_data(upload_data());
+    post_data_len_ = upload_data.get() ? upload_data->GetContentLength() : 0;
+    if (post_data_len_) {
+      DLOG(INFO) << " Obj: " << std::hex << this << " POST request with "
+          << Int64ToString(post_data_len_) << " bytes";
+      CComObject<UrlmonUploadDataStream>* upload_stream = NULL;
+      HRESULT hr =
+          CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream);
+      if (FAILED(hr)) {
+        NOTREACHED();
+        return hr;
+      }
+      upload_stream->Initialize(upload_data.get());
+
+      // Fill the STGMEDIUM with the data to post
+      bind_info->stgmedData.tymed = TYMED_ISTREAM;
+      bind_info->stgmedData.pstm = static_cast<IStream*>(upload_stream);
+      bind_info->stgmedData.pstm->AddRef();
+    } else {
+      DLOG(INFO) << " Obj: " << std::hex << this
+          << "POST request with no data!";
+    }
+  } else {
+    NOTREACHED() << "Unknown HTTP method.";
+    status_.set_status(URLRequestStatus::FAILED);
+    status_.set_os_error(net::ERR_INVALID_URL);
+    EndRequest();
+    return E_FAIL;
+  }
+
+  return S_OK;
+}
+
+STDMETHODIMP UrlmonUrlRequest::OnDataAvailable(DWORD flags, DWORD size,
+                                               FORMATETC* formatetc,
+                                               STGMEDIUM* storage) {
+  DCHECK_EQ(PlatformThread::CurrentId(), thread_);
+  DLOG(INFO) << StringPrintf("URL: %s Obj: %X - Bytes available: %d",
+                             url().c_str(), this, size);
+
+  if (!storage || (storage->tymed != TYMED_ISTREAM)) {
+    NOTREACHED();
+    return E_INVALIDARG;
+  }
+
+  IStream* read_stream = storage->pstm;
+  if (!read_stream) {
+    NOTREACHED();
+    return E_UNEXPECTED;
+  }
+
+  HRESULT hr = S_OK;
+  if (BSCF_FIRSTDATANOTIFICATION & flags) {
+    DCHECK(!cached_data_.is_valid());
+    cached_data_.Create();
+  }
+
+  // Always read data into cache. We have to read all the data here at this
+  // time or it won't be available later. Since the size of the data could
+  // be more than pending read size, it's not straightforward (or might even
+  // be impossible) to implement a true data pull model.
+  size_t bytes_available = 0;
+  cached_data_.Append(read_stream, &bytes_available);
+  DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
+      " -  Bytes read into cache: " << bytes_available;
+
+  if (pending_read_size_) {
+    CComObjectStackEx<SendStream> send_stream;
+    send_stream.Initialize(this);
+    cached_data_.Read(&send_stream, pending_read_size_, &pending_read_size_);
+    DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
+        " - size read: " << pending_read_size_;
+    pending_read_size_ = 0;
+  } else {
+    DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
+        " - waiting for remote read";
+  }
+
+  if (BSCF_LASTDATANOTIFICATION & flags) {
+    status_.set_status(URLRequestStatus::SUCCESS);
+    DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
+        " - end of data.";
+  }
+
+  return S_OK;
+}
+
+STDMETHODIMP UrlmonUrlRequest::OnObjectAvailable(REFIID iid, IUnknown *object) {
+  // We are calling BindToStorage on the moniker we should always get called
+  // back on OnDataAvailable and should never get OnObjectAvailable
+  NOTREACHED();
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP UrlmonUrlRequest::BeginningTransaction(const wchar_t* url,
+    const wchar_t* current_headers, DWORD reserved,
+    wchar_t** additional_headers) {
+  DCHECK_EQ(PlatformThread::CurrentId(), thread_);
+  if (!additional_headers) {
+    NOTREACHED();
+    return E_POINTER;
+  }
+
+  DLOG(INFO) << "URL: " << url << " Obj: " << std::hex << this <<
+      " - Request headers: \n" << current_headers;
+
+  HRESULT hr = S_OK;
+
+  std::string new_headers;
+  if (post_data_len_ > 0) {
+    // Tack on the Content-Length header since when using an IStream type
+    // STGMEDIUM, it looks like it doesn't get set for us :(
+    new_headers = StringPrintf("Content-Length: %s\r\n",
+                               Int64ToString(post_data_len_).c_str());
+  }
+
+  if (!extra_headers().empty()) {
+    // TODO(robertshield): We may need to sanitize headers on POST here.
+    new_headers += extra_headers();
+  }
+
+  if (!referrer().empty()) {
+    // Referrer is famously misspelled in HTTP:
+    new_headers += StringPrintf("Referer: %s\r\n", referrer().c_str());
+  }
+
+  if (!new_headers.empty()) {
+    *additional_headers = reinterpret_cast<wchar_t*>(
+        CoTaskMemAlloc((new_headers.size() + 1) * sizeof(wchar_t)));
+
+    if (*additional_headers == NULL) {
+      NOTREACHED();
+      hr = E_OUTOFMEMORY;
+    } else {
+      lstrcpynW(*additional_headers, ASCIIToWide(new_headers).c_str(),
+                new_headers.size());
+    }
+  }
+
+  return hr;
+}
+
+STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode,
+    const wchar_t* response_headers, const wchar_t* request_headers,
+    wchar_t** additional_headers) {
+  DCHECK_EQ(PlatformThread::CurrentId(), thread_);
+
+  std::string raw_headers = WideToUTF8(response_headers);
+
+  // Security check for frame busting headers. We don't honor the headers
+  // as-such, but instead simply kill requests which we've been asked to 
+  // look for. This puts the onus on the user of the UrlRequest to specify 
+  // whether or not requests should be inspected. For ActiveDocuments, the 
+  // answer is "no", since WebKit's detection/handling is sufficient and since 
+  // ActiveDocuments cannot be hosted as iframes. For NPAPI and ActiveX 
+  // documents, the Initialize() function of the PluginUrlRequest object 
+  // allows them to specify how they'd like requests handled. Both should 
+  // set enable_frame_busting_ to true to avoid CSRF attacks.
+  // Should WebKit's handling of this ever change, we will need to re-visit 
+  // how and when frames are killed to better mirror a policy which may 
+  // do something other than kill the sub-document outright.
+
+  // NOTE(slightlyoff): We don't use net::HttpResponseHeaders here because 
+  //    of lingering ICU/base_noicu issues.
+  if (frame_busting_enabled_ &&
+      net::HttpUtil::HasHeader(raw_headers, kXFrameOptionsHeader)) {
+    DLOG(ERROR) << "X-Frame-Options header detected, navigation canceled";
+    return E_FAIL;
+  }
+
+  std::wstring url_for_persistent_cookies =
+      redirect_url_.empty() ? UTF8ToWide(url()) : redirect_url_;
+
+  std::string persistent_cookies;
+
+  DWORD cookie_size = 0;  // NOLINT
+  InternetGetCookie(url_for_persistent_cookies.c_str(), NULL, NULL,
+                    &cookie_size);
+  if (cookie_size) {
+    scoped_ptr<wchar_t> cookies(new wchar_t[cookie_size + 1]);
+    if (!InternetGetCookie(url_for_persistent_cookies.c_str(), NULL,
+                           cookies.get(), &cookie_size)) {
+      NOTREACHED() << "InternetGetCookie failed. Error: " << GetLastError();
+    } else {
+      persistent_cookies = WideToUTF8(cookies.get());
+    }
+  }
+
+  OnResponseStarted("",
+                    raw_headers.c_str(),
+                    0,
+                    base::Time(),
+                    persistent_cookies,
+                    redirect_url_.empty() ? std::string() :
+                        WideToUTF8(redirect_url_),
+                    redirect_status_);
+
+  return S_OK;
+}
+
+STDMETHODIMP UrlmonUrlRequest::GetWindow(const GUID& guid_reason,
+                                         HWND* parent_window) {
+  if (!parent_window) {
+    return E_INVALIDARG;
+  }
+
+#ifndef NDEBUG
+  wchar_t guid[40] = {0};
+  ::StringFromGUID2(guid_reason, guid, arraysize(guid));
+
+  DLOG(INFO) << " Obj: " << std::hex << this << " GetWindow: " <<
+      (guid_reason == IID_IAuthenticate ? L" - IAuthenticate" :
+       (guid_reason == IID_IHttpSecurity ? L"IHttpSecurity" :
+        (guid_reason == IID_IWindowForBindingUI ? L"IWindowForBindingUI" :
+                                                  guid)));
+#endif
+
+  // TODO(iyengar): This hits when running the URL request tests.
+  DLOG_IF(ERROR, !::IsWindow(parent_window_))
+      << "UrlmonUrlRequest::GetWindow - no window!";
+  *parent_window = parent_window_;
+  return S_OK;
+}
+
+STDMETHODIMP UrlmonUrlRequest::Authenticate(HWND* parent_window,
+                                            LPWSTR* user_name,
+                                            LPWSTR* password) {
+  if (!parent_window) {
+    return E_INVALIDARG;
+  }
+
+  DCHECK(::IsWindow(parent_window_));
+  *parent_window = parent_window_;
+  return S_OK;
+}
+
+STDMETHODIMP UrlmonUrlRequest::OnSecurityProblem(DWORD problem) {
+  // Urlmon notifies the client of authentication problems, certificate
+  // errors, etc by querying the object implementing the IBindStatusCallback
+  // interface for the IHttpSecurity interface. If this interface is not
+  // implemented then Urlmon checks for the problem codes defined below
+  // and performs actions as defined below:-
+  // It invokes the ReportProgress method of the protocol sink with
+  // these problem codes and eventually invokes the ReportResult method
+  // on the protocol sink which ends up in a call to the OnStopBinding
+  // method of the IBindStatusCallBack interface.
+
+  // MSHTML's implementation of the IBindStatusCallback interface does not
+  // implement the IHttpSecurity interface. However it handles the
+  // OnStopBinding call with a HRESULT of 0x800c0019 and navigates to
+  // an interstitial page which presents the user with a choice of whether
+  // to abort the navigation.
+
+  // In our OnStopBinding implementation we stop the navigation and inform
+  // Chrome about the result. Ideally Chrome should behave in a manner similar
+  // to IE, i.e. display the SSL error interstitial page and if the user
+  // decides to proceed anyway we would turn off SSL warnings for that
+  // particular navigation and allow IE to download the content.
+  // We would need to return the certificate information to Chrome for display
+  // purposes. Currently we only return a dummy certificate to Chrome.
+  // At this point we decided that it is a lot of work at this point and
+  // decided to go with the easier option of implementing the IHttpSecurity
+  // interface and replicating the checks performed by Urlmon. This
+  // causes Urlmon to display a dialog box on the same lines as IE6.
+  DLOG(INFO) << __FUNCTION__ << " Security problem : " << problem;
+
+  HRESULT hr = E_ABORT;
+
+  switch (problem) {
+    case ERROR_INTERNET_SEC_CERT_REV_FAILED: {
+      hr = RPC_E_RETRY;
+      break;
+    }
+
+    case ERROR_INTERNET_SEC_CERT_DATE_INVALID:
+    case ERROR_INTERNET_SEC_CERT_CN_INVALID:
+    case ERROR_INTERNET_INVALID_CA: {
+      hr = S_FALSE;
+      break;
+    }
+
+    default: {
+      NOTREACHED() << "Unhandled security problem : " << problem;
+      break;
+    }
+  }
+  return hr;
+}
+
+HRESULT UrlmonUrlRequest::ConnectToExistingMoniker(IMoniker* moniker,
+                                                   IBindCtx* context,
+                                                   const std::wstring& url) {
+  if (!moniker || url.empty()) {
+    NOTREACHED() << "Invalid arguments";
+    return E_INVALIDARG;
+  }
+
+  DCHECK(moniker_.get() == NULL);
+  DCHECK(bind_context_.get() == NULL);
+
+  moniker_ = moniker;
+  bind_context_ = context;
+  set_url(WideToUTF8(url));
+  return S_OK;
+}
+
+HRESULT UrlmonUrlRequest::StartAsyncDownload() {
+  HRESULT hr = E_FAIL;
+  if (moniker_.get() == NULL) {
+    std::wstring wide_url = UTF8ToWide(url());
+    hr = CreateURLMonikerEx(NULL, wide_url.c_str(), moniker_.Receive(),
+                            URL_MK_UNIFORM);
+    if (FAILED(hr)) {
+      NOTREACHED() << "CreateURLMonikerEx failed. Error: " << hr;
+    } else {
+      hr = CreateAsyncBindCtx(0, this, NULL, bind_context_.Receive());
+      DCHECK(SUCCEEDED(hr)) << "CreateAsyncBindCtx failed. Error: " << hr;
+    }
+  } else {
+    DCHECK(bind_context_.get() != NULL);
+    hr = RegisterBindStatusCallback(bind_context_, this, NULL, 0);
+  }
+
+  if (SUCCEEDED(hr)) {
+    ScopedComPtr<IStream> stream;
+    hr = moniker_->BindToStorage(bind_context_, NULL, __uuidof(IStream),
+                                 reinterpret_cast<void**>(stream.Receive()));
+    if (FAILED(hr)) {
+      // TODO(joshia): Look into. This currently fails for:
+      // http://user2:secret@localhost:1337/auth-basic?set-cookie-if-challenged
+      // when running the UrlRequest unit tests.
+      DLOG(ERROR) <<
+          StringPrintf("IUrlMoniker::BindToStorage failed. Error: 0x%08X.", hr)
+          << std::endl << url();
+      DCHECK(hr == MK_E_SYNTAX);
+    }
+  }
+
+  DLOG_IF(ERROR, FAILED(hr))
+      << StringPrintf(L"StartAsyncDownload failed: 0x%08X", hr);
+
+  return hr;
+}
+
+void UrlmonUrlRequest::EndRequest() {
+  DLOG(INFO) << __FUNCTION__;
+  // Special case.  If the last request was a redirect and the current OS
+  // error value is E_ACCESSDENIED, that means an unsafe redirect was attempted.
+  // In that case, correct the OS error value to be the more specific
+  // ERR_UNSAFE_REDIRECT error value.
+  if (!status_.is_success() && status_.os_error() == net::ERR_ACCESS_DENIED) {
+    int status = GetHttpResponseStatus();
+    if (status >= 300 && status < 400) {
+      redirect_status_ = status;  // store the latest redirect status value.
+      status_.set_os_error(net::ERR_UNSAFE_REDIRECT);
+    }
+  }
+  request_handler()->RemoveRequest(this);
+  OnResponseEnd(status_);
+
+  // If the request was started then we must have an additional reference on the
+  // request.
+  if (is_request_started_) {
+    is_request_started_ = false;
+    Release();
+  }
+}
+
+int UrlmonUrlRequest::GetHttpResponseStatus() const {
+  if (binding_ == NULL) {
+    DLOG(WARNING) << "GetHttpResponseStatus - no binding_";
+    return 0;
+  }
+
+  int http_status = 0;
+
+  ScopedComPtr<IWinInetHttpInfo> info;
+  if (SUCCEEDED(info.QueryFrom(binding_))) {
+    char status[10] = {0};
+    DWORD buf_size = sizeof(status);
+    if (SUCCEEDED(info->QueryInfo(HTTP_QUERY_STATUS_CODE, status, &buf_size,
+                                  0, NULL))) {
+      http_status = StringToInt(status);
+    } else {
+      NOTREACHED() << "Failed to get HTTP status";
+    }
+  } else {
+    NOTREACHED() << "failed to get IWinInetHttpInfo from binding_";
+  }
+
+  return http_status;
+}
+
+//
+// UrlmonUrlRequest::Cache implementation.
+//
+
+size_t UrlmonUrlRequest::Cache::Size() {
+  size_t size = 0;
+  if (stream_) {
+    STATSTG cache_stat = {0};
+    stream_->Stat(&cache_stat, STATFLAG_NONAME);
+
+    DCHECK_EQ(0, cache_stat.cbSize.HighPart);
+    size = cache_stat.cbSize.LowPart;
+  }
+
+  return size;
+}
+
+size_t UrlmonUrlRequest::Cache::CurrentPos() {
+  size_t pos = 0;
+  if (stream_) {
+    ULARGE_INTEGER current_index = {0};
+    stream_->Seek(kZero, STREAM_SEEK_CUR, &current_index);
+
+    DCHECK_EQ(0, current_index.HighPart);
+    pos = current_index.LowPart;
+  }
+
+  return pos;
+}
+
+size_t UrlmonUrlRequest::Cache::SizeRemaining() {
+  size_t size = Size();
+  size_t pos = CurrentPos();
+  size_t size_remaining = 0;
+
+  if (size) {
+    DCHECK(pos <= size);
+    size_remaining = size - pos;
+  }
+  return size_remaining;
+}
+
+void UrlmonUrlRequest::Cache::Clear() {
+  if (!stream_) {
+    NOTREACHED();
+    return;
+  }
+
+  HRESULT hr = stream_->SetSize(kUnsignedZero);
+  DCHECK(SUCCEEDED(hr));
+}
+
+bool UrlmonUrlRequest::Cache::Read(IStream* dest, size_t size,
+                                   size_t* bytes_copied) {
+  if (!dest || !size) {
+    NOTREACHED();
+    return false;
+  }
+
+  // Copy the data and clear cache if there is no more data to copy.
+  ULARGE_INTEGER size_to_copy = {size, 0};
+  ULARGE_INTEGER size_written = {0};
+  stream_->CopyTo(dest, size_to_copy, NULL, &size_written);
+
+  if (size_written.LowPart && bytes_copied)
+    *bytes_copied = size_written.LowPart;
+
+  if (!SizeRemaining()) {
+    Clear();
+    stream_->Seek(kZero, STREAM_SEEK_SET, NULL);
+  }
+
+  return (size_written.LowPart != 0);
+}
+
+bool UrlmonUrlRequest::Cache::Append(IStream* source,
+                                     size_t* bytes_copied) {
+  if (!source) {
+    NOTREACHED();
+    return false;
+  }
+
+  size_t current_pos = CurrentPos();
+  stream_->Seek(kZero, STREAM_SEEK_END, NULL);
+
+  HRESULT hr = S_OK;
+  while (SUCCEEDED(hr)) {
+    DWORD chunk_read = 0;  // NOLINT
+    hr = source->Read(read_buffer_, sizeof(read_buffer_), &chunk_read);
+    if (!chunk_read)
+      break;
+
+    DWORD chunk_written = 0;  // NOLINT
+    stream_->Write(read_buffer_, chunk_read, &chunk_written);
+    DCHECK_EQ(chunk_read, chunk_written);
+
+    if (bytes_copied)
+      *bytes_copied += chunk_written;
+  }
+
+  LARGE_INTEGER last_read_position = {current_pos, 0};
+  stream_->Seek(last_read_position, STREAM_SEEK_SET, NULL);
+  return SUCCEEDED(hr);
+}
+
+bool UrlmonUrlRequest::Cache::Create() {
+  DCHECK(stream_ == NULL);
+  bool ret = SUCCEEDED(CreateStreamOnHGlobal(NULL, TRUE, stream_.Receive()));
+  DCHECK(ret && stream_);
+  return ret;
+}
+
+net::Error UrlmonUrlRequest::HresultToNetError(HRESULT hr) {
+  // Useful reference:
+  // http://msdn.microsoft.com/en-us/library/ms775145(VS.85).aspx
+
+  net::Error ret = net::ERR_UNEXPECTED;
+
+  switch (hr) {
+    case S_OK:
+      ret = net::OK;
+      break;
+
+    case MK_E_SYNTAX:
+      ret = net::ERR_INVALID_URL;
+      break;
+
+    case INET_E_CANNOT_CONNECT:
+      ret = net::ERR_CONNECTION_FAILED;
+      break;
+
+    case INET_E_DOWNLOAD_FAILURE:
+    case INET_E_CONNECTION_TIMEOUT:
+    case E_ABORT:
+      ret = net::ERR_CONNECTION_ABORTED;
+      break;
+
+    case INET_E_DATA_NOT_AVAILABLE:
+      ret = net::ERR_EMPTY_RESPONSE;
+      break;
+
+    case INET_E_RESOURCE_NOT_FOUND:
+      // To behave more closely to the chrome network stack, we translate this

+      // error value as tunnel connection failed.  This error value is tested

+      // in the ProxyTunnelRedirectTest and UnexpectedServerAuthTest tests.
+      ret = net::ERR_TUNNEL_CONNECTION_FAILED;
+      break;
+
+    case INET_E_INVALID_URL:
+    case INET_E_UNKNOWN_PROTOCOL:
+    case INET_E_REDIRECT_FAILED:
+      ret = net::ERR_INVALID_URL;
+      break;
+
+    case INET_E_INVALID_CERTIFICATE:
+      ret = net::ERR_CERT_INVALID;
+      break;
+
+    case E_ACCESSDENIED:
+      ret = net::ERR_ACCESS_DENIED;
+      break;
+
+    default:
+      DLOG(WARNING)
+          << StringPrintf("TODO: translate HRESULT 0x%08X to net::Error", hr);
+      break;
+  }
+  return ret;
+}
diff --git a/chrome_frame/urlmon_url_request.h b/chrome_frame/urlmon_url_request.h
new file mode 100644
index 0000000..114ee6b
--- /dev/null
+++ b/chrome_frame/urlmon_url_request.h
@@ -0,0 +1,231 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_URLMON_URL_REQUEST_H_
+#define CHROME_FRAME_URLMON_URL_REQUEST_H_
+
+#include <urlmon.h>
+#include <atlbase.h>
+#include <atlcom.h>
+
+#include <algorithm>
+
+#include "base/lock.h"
+#include "base/platform_thread.h"
+#include "base/scoped_comptr_win.h"
+#include "chrome_frame/plugin_url_request.h"
+
+#include "net/base/net_errors.h"
+#include "net/base/upload_data.h"
+
+class UrlmonUrlRequest
+    : public CComObjectRootEx<CComSingleThreadModel>,
+      public PluginUrlRequest,
+      public IServiceProviderImpl<UrlmonUrlRequest>,
+      public IBindStatusCallback,
+      public IHttpNegotiate,
+      public IAuthenticate,
+      public IHttpSecurity {
+ public:
+  UrlmonUrlRequest();
+  ~UrlmonUrlRequest();
+
+BEGIN_COM_MAP(UrlmonUrlRequest)
+  COM_INTERFACE_ENTRY(IHttpNegotiate)
+  COM_INTERFACE_ENTRY(IServiceProvider)
+  COM_INTERFACE_ENTRY(IBindStatusCallback)
+  COM_INTERFACE_ENTRY(IWindowForBindingUI)
+  COM_INTERFACE_ENTRY(IAuthenticate)
+  COM_INTERFACE_ENTRY(IHttpSecurity)
+END_COM_MAP()
+
+BEGIN_SERVICE_MAP(UrlmonUrlRequest)
+  SERVICE_ENTRY(IID_IHttpNegotiate);
+END_SERVICE_MAP()
+
+  // PluginUrlRequest implementation
+  virtual bool Start();
+  virtual void Stop();
+  virtual bool Read(int bytes_to_read);
+
+  // IBindStatusCallback implementation
+  STDMETHOD(OnStartBinding)(DWORD reserved, IBinding* binding);
+  STDMETHOD(GetPriority)(LONG* priority);
+  STDMETHOD(OnLowResource)(DWORD reserved);
+  STDMETHOD(OnProgress)(ULONG progress, ULONG max_progress,
+      ULONG status_code, LPCWSTR status_text);
+  STDMETHOD(OnStopBinding)(HRESULT result, LPCWSTR error);
+  STDMETHOD(GetBindInfo)(DWORD* bind_flags, BINDINFO* bind_info);
+  STDMETHOD(OnDataAvailable)(DWORD flags, DWORD size, FORMATETC* formatetc,
+      STGMEDIUM* storage);
+  STDMETHOD(OnObjectAvailable)(REFIID iid, IUnknown* object);
+
+  // IHttpNegotiate implementation
+  STDMETHOD(BeginningTransaction)(const wchar_t* url,
+      const wchar_t* current_headers, DWORD reserved,
+      wchar_t** additional_headers);
+  STDMETHOD(OnResponse)(DWORD dwResponseCode, const wchar_t* response_headers,
+      const wchar_t* request_headers, wchar_t** additional_headers);
+
+  // IWindowForBindingUI implementation. This interface is used typically to
+  // query the window handle which URLMON uses as the parent of error dialogs.
+  STDMETHOD(GetWindow)(REFGUID guid_reason, HWND* parent_window);
+
+  // IAuthenticate implementation. Used to return the parent window for the
+  // dialog displayed by IE for authenticating with a proxy.
+  STDMETHOD(Authenticate)(HWND* parent_window, LPWSTR* user_name,
+                          LPWSTR* password);
+
+  // IHttpSecurity implementation.
+  STDMETHOD(OnSecurityProblem)(DWORD problem);
+
+  HRESULT ConnectToExistingMoniker(IMoniker* moniker, IBindCtx* context,
+                                   const std::wstring& url);
+
+  void set_parent_window(HWND parent_window) {
+    parent_window_ = parent_window;
+  }
+
+ protected:
+  static const size_t kCopyChunkSize = 32 * 1024;
+
+  // A fake stream class to make it easier to copy received data using
+  // IStream::CopyTo instead of allocating temporary buffers and keeping
+  // track of data copied so far.
+  class SendStream
+      : public CComObjectRoot,
+        public IStream {
+   public:
+    SendStream() {
+    }
+
+  BEGIN_COM_MAP(SendStream)
+    COM_INTERFACE_ENTRY(IStream)
+    COM_INTERFACE_ENTRY(ISequentialStream)
+  END_COM_MAP()
+
+    void Initialize(UrlmonUrlRequest* request) {
+      request_ = request;
+    }
+
+    STDMETHOD(Read)(void* pv, ULONG cb, ULONG* read) {
+      DCHECK(false) << __FUNCTION__;
+      return E_NOTIMPL;
+    }
+
+    STDMETHOD(Write)(const void * buffer, ULONG size, ULONG* size_written) {
+      DCHECK(request_);
+      int size_to_write = static_cast<int>(
+          std::min(static_cast<ULONG>(MAXINT), size));
+      request_->OnReadComplete(buffer, size_to_write);
+      if (size_written)
+        *size_written = size_to_write;
+      return S_OK;
+    }
+
+    STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos) {
+      DCHECK(false) << __FUNCTION__;
+      return E_NOTIMPL;
+    }
+
+    STDMETHOD(SetSize)(ULARGE_INTEGER new_size) {
+      DCHECK(false) << __FUNCTION__;
+      return E_NOTIMPL;
+    }
+
+    STDMETHOD(CopyTo)(IStream* stream, ULARGE_INTEGER cb, ULARGE_INTEGER* read,
+                      ULARGE_INTEGER* written) {
+      DCHECK(false) << __FUNCTION__;
+      return E_NOTIMPL;
+    }
+
+    STDMETHOD(Commit)(DWORD flags) {
+      DCHECK(false) << __FUNCTION__;
+      return E_NOTIMPL;
+    }
+
+    STDMETHOD(Revert)() {
+      DCHECK(false) << __FUNCTION__;
+      return E_NOTIMPL;
+    }
+
+    STDMETHOD(LockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb,
+                          DWORD type) {
+      DCHECK(false) << __FUNCTION__;
+      return E_NOTIMPL;
+    }
+
+    STDMETHOD(UnlockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb,
+                            DWORD type) {
+      DCHECK(false) << __FUNCTION__;
+      return E_NOTIMPL;
+    }
+
+    STDMETHOD(Stat)(STATSTG *pstatstg, DWORD grfStatFlag) {
+      return E_NOTIMPL;
+    }
+
+    STDMETHOD(Clone)(IStream** stream) {
+      DCHECK(false) << __FUNCTION__;
+      return E_NOTIMPL;
+    }
+
+   protected:
+    scoped_refptr<UrlmonUrlRequest> request_;
+    DISALLOW_COPY_AND_ASSIGN(SendStream);
+  };
+
+  // Manage data caching. Note: this class supports cache
+  // size less than 2GB
+  class Cache {
+   public:
+    bool Create();
+
+    // Adds data to the end of the cache.
+    bool Append(IStream* source, size_t* bytes_copied);
+
+    // Reads from the cache.
+    bool Read(IStream* dest, size_t size, size_t* bytes_copied);
+
+    size_t Size();
+    size_t CurrentPos();
+    size_t SizeRemaining();
+    void Clear();
+    bool is_valid() const {
+      return (stream_ != NULL);
+    }
+
+   protected:
+    ScopedComPtr<IStream> stream_;
+    char read_buffer_[kCopyChunkSize];
+  };
+
+  HRESULT StartAsyncDownload();
+  void EndRequest();
+
+  int GetHttpResponseStatus() const;
+
+  static net::Error HresultToNetError(HRESULT hr);
+
+ private:
+  std::wstring redirect_url_;
+  int redirect_status_;
+  ScopedComPtr<IBinding> binding_;
+  ScopedComPtr<IMoniker> moniker_;
+  ScopedComPtr<IBindCtx> bind_context_;
+  Cache cached_data_;
+  size_t pending_read_size_;
+  URLRequestStatus status_;
+
+  uint64 post_data_len_;
+
+  PlatformThreadId thread_;
+  bool is_request_started_;
+  static int instance_count_;
+  HWND parent_window_;
+  DISALLOW_COPY_AND_ASSIGN(UrlmonUrlRequest);
+};
+
+#endif  // CHROME_FRAME_URLMON_URL_REQUEST_H_
+
diff --git a/chrome_frame/utils.cc b/chrome_frame/utils.cc
new file mode 100644
index 0000000..b445c26
--- /dev/null
+++ b/chrome_frame/utils.cc
@@ -0,0 +1,643 @@
+// Copyright (c) 2009 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.
+
+#include <shlobj.h>
+
+#include "chrome_frame/html_utils.h"
+#include "chrome_frame/utils.h"
+
+#include "base/file_util.h"
+#include "base/file_version_info.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/registry.h"
+#include "base/scoped_comptr_win.h"
+#include "base/string_util.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/installer/util/google_update_constants.h"
+#include "googleurl/src/gurl.h"
+#include "grit/chrome_frame_resources.h"
+#include "chrome_frame/resource.h"
+
+// Note that these values are all lower case and are compared to
+// lower-case-transformed values.
+const wchar_t kMetaTag[] = L"meta";
+const wchar_t kHttpEquivAttribName[] = L"http-equiv";
+const wchar_t kContentAttribName[] = L"content";
+const wchar_t kXUACompatValue[] = L"x-ua-compatible";
+const wchar_t kBodyTag[] = L"body";
+const wchar_t kChromeContentPrefix[] = L"chrome=";
+const wchar_t kChromeProtocolPrefix[] = L"cf:";
+
+static const wchar_t kChromeFrameConfigKey[] =
+    L"Software\\Google\\ChromeFrame";
+static const wchar_t kChromeFrameOptinUrlsKey[] = L"OptinUrls";
+
+// Used to isolate chrome frame builds from google chrome release channels.
+const wchar_t kChromeFrameOmahaSuffix[] = L"-cf";
+const wchar_t kDevChannelName[] = L"-dev";
+
+const wchar_t kChromeAttachExternalTabPrefix[] = L"attach_external_tab";
+
+HRESULT UtilRegisterTypeLib(HINSTANCE tlb_instance,
+                            LPCOLESTR index,
+                            bool for_current_user_only) {
+  CComBSTR path;
+  CComPtr<ITypeLib> type_lib;
+  HRESULT hr = AtlLoadTypeLib(tlb_instance, index, &path, &type_lib);
+  if (SUCCEEDED(hr)) {
+    hr = UtilRegisterTypeLib(type_lib, path, NULL, for_current_user_only);
+  }
+  return hr;
+}
+
+HRESULT UtilUnRegisterTypeLib(HINSTANCE tlb_instance,
+                              LPCOLESTR index,
+                              bool for_current_user_only) {
+  CComBSTR path;
+  CComPtr<ITypeLib> type_lib;
+  HRESULT hr = AtlLoadTypeLib(tlb_instance, index, &path, &type_lib);
+  if (SUCCEEDED(hr)) {
+    hr = UtilUnRegisterTypeLib(type_lib, for_current_user_only);
+  }
+  return hr;
+}
+
+HRESULT UtilRegisterTypeLib(LPCWSTR typelib_path,
+                            bool for_current_user_only) {
+  if (NULL == typelib_path) {
+    return E_INVALIDARG;
+  }
+  CComBSTR path;
+  CComPtr<ITypeLib> type_lib;
+  HRESULT hr = ::LoadTypeLib(typelib_path, &type_lib);
+  if (SUCCEEDED(hr)) {
+    hr = UtilRegisterTypeLib(type_lib,
+                             typelib_path,
+                             NULL,
+                             for_current_user_only);
+  }
+  return hr;
+}
+
+HRESULT UtilUnRegisterTypeLib(LPCWSTR typelib_path,
+                              bool for_current_user_only) {
+  CComPtr<ITypeLib> type_lib;
+  HRESULT hr = ::LoadTypeLib(typelib_path, &type_lib);
+  if (SUCCEEDED(hr)) {
+    hr = UtilUnRegisterTypeLib(type_lib, for_current_user_only);
+  }
+  return hr;
+}
+
+HRESULT UtilRegisterTypeLib(ITypeLib* typelib,
+                            LPCWSTR typelib_path,
+                            LPCWSTR help_dir,
+                            bool for_current_user_only) {
+  typedef HRESULT(WINAPI *RegisterTypeLibPrototype)(ITypeLib FAR* type_lib,
+                                                    OLECHAR FAR* full_path,
+                                                    OLECHAR FAR* help_dir);
+  LPCSTR function_name =
+    for_current_user_only ? "RegisterTypeLibForUser" : "RegisterTypeLib";
+  RegisterTypeLibPrototype reg_tlb =
+      reinterpret_cast<RegisterTypeLibPrototype>(
+          GetProcAddress(GetModuleHandle(_T("oleaut32.dll")),
+                                         function_name));
+  if (NULL == reg_tlb) {
+    return E_FAIL;
+  }
+  return reg_tlb(typelib,
+                 const_cast<OLECHAR*>(typelib_path),
+                 const_cast<OLECHAR*>(help_dir));
+}
+
+HRESULT UtilUnRegisterTypeLib(ITypeLib* typelib,
+                              bool for_current_user_only) {
+  if (NULL == typelib) {
+    return E_INVALIDARG;
+  }
+  typedef HRESULT(WINAPI *UnRegisterTypeLibPrototype)(
+      REFGUID libID,
+      unsigned short wVerMajor,  // NOLINT
+      unsigned short wVerMinor,  // NOLINT
+      LCID lcid,
+      SYSKIND syskind);
+  LPCSTR function_name =
+    for_current_user_only ? "UnRegisterTypeLibForUser" : "UnRegisterTypeLib";
+
+  UnRegisterTypeLibPrototype unreg_tlb =
+      reinterpret_cast<UnRegisterTypeLibPrototype>(
+          GetProcAddress(GetModuleHandle(_T("oleaut32.dll")),
+                                         function_name));
+  if (NULL == unreg_tlb) {
+    return E_FAIL;
+  }
+  TLIBATTR* tla = NULL;
+  HRESULT hr = typelib->GetLibAttr(&tla);
+  if (SUCCEEDED(hr)) {
+    hr = unreg_tlb(tla->guid,
+                   tla->wMajorVerNum,
+                   tla->wMinorVerNum,
+                   tla->lcid,
+                   tla->syskind);
+    typelib->ReleaseTLibAttr(tla);
+  }
+  return hr;
+}
+
+HRESULT UtilGetXUACompatContentValue(const std::wstring& html_string,
+                                     std::wstring* content_value) {
+  if (!content_value) {
+    return E_POINTER;
+  }
+
+  // Fail fast if the string X-UA-Compatible isn't in html_string
+  if (StringToLowerASCII(html_string).find(kXUACompatValue) ==
+      std::wstring::npos) {
+    return E_FAIL;
+  }
+
+  HTMLScanner scanner(html_string.c_str());
+
+  // Build the list of meta tags that occur before the body tag is hit.
+  HTMLScanner::StringRangeList tag_list;
+  scanner.GetTagsByName(kMetaTag, &tag_list, kBodyTag);
+
+  // Search the list of meta tags for one with an http-equiv="X-UA-Compatible"
+  // attribute.
+  HTMLScanner::StringRange attribute;
+  std::string search_attribute_ascii(WideToASCII(kXUACompatValue));
+  HTMLScanner::StringRangeList::const_iterator tag_list_iter(tag_list.begin());
+  for (; tag_list_iter != tag_list.end(); tag_list_iter++) {
+    if (!tag_list_iter->GetTagAttribute(kHttpEquivAttribName, &attribute)) {
+      continue;
+    }
+
+    // We found an http-equiv meta tag, check its value using the ascii
+    // case-insensitive comparison method.
+    if (!attribute.LowerCaseEqualsASCII(search_attribute_ascii.c_str())) {
+      continue;
+    }
+
+    // We found our X-UA-Compatible meta tag so look for and extract
+    // the value of the content attribute.
+    if (!tag_list_iter->GetTagAttribute(kContentAttribName, &attribute)) {
+      continue;
+    }
+
+    // Found the content string, copy and return.
+    content_value->assign(attribute.Copy());
+    return S_OK;
+  }
+
+  return E_FAIL;
+}
+
+bool AppendSuffixToChannelName(std::wstring* string,
+                               const std::wstring& channel_name,
+                               const std::wstring& suffix) {
+  size_t pos = string->find(channel_name);
+  // Append the suffix only if we find the channel name.
+  if (pos != std::wstring::npos) {
+    pos += channel_name.size();
+    // Append the suffix only to the channel name only if the name is not
+    // already followed by suffix.
+    if (string->find(suffix, pos) != pos) {
+      string->insert(pos, suffix);
+      return true;
+    }
+  }
+  return false;
+}
+
+bool RemoveSuffixFromChannelName(std::wstring* string,
+                                 const std::wstring& channel_name,
+                                 const std::wstring& suffix) {
+  std::wstring decorated_channel(channel_name + suffix);
+  size_t pos = string->find(decorated_channel);
+  // TODO(robertshield): Remove the suffix iff the suffix is the last thing in
+  // the string or is followed by another suffix that starts with '-'.
+  if (pos != std::wstring::npos) {
+    pos += channel_name.size();
+    string->erase(pos, suffix.size());
+    return true;
+  }
+  return false;
+}
+
+HRESULT UtilUpdateOmahaConfig(bool add_cf_suffix) {
+  HKEY reg_root = HKEY_LOCAL_MACHINE;
+
+  RegKey key;
+  std::wstring ap_key_value;
+  std::wstring reg_key(google_update::kRegPathClientState);
+  reg_key.append(L"\\");
+  reg_key.append(google_update::kChromeGuid);
+  if (!key.Open(reg_root, reg_key.c_str(), KEY_READ | KEY_WRITE) ||
+      !key.ReadValue(google_update::kRegApField, &ap_key_value)) {
+    // Can't read the Omaha config.
+    return REGDB_E_READREGDB;
+  }
+
+  HRESULT result = S_OK;
+  // We've read the key in, try and modify it then write it back.
+  if (add_cf_suffix && AppendSuffixToChannelName(&ap_key_value,
+                                                 kDevChannelName,
+                                                 kChromeFrameOmahaSuffix)) {
+    if (!key.WriteValue(google_update::kRegApField, ap_key_value.c_str())) {
+      DLOG(ERROR) << "Failed to add suffix to omaha ap key value.";
+      result = REGDB_E_WRITEREGDB;
+    }
+  } else if (!add_cf_suffix &&
+             RemoveSuffixFromChannelName(&ap_key_value,
+                                         kDevChannelName,
+                                         kChromeFrameOmahaSuffix)) {
+    if (!key.WriteValue(google_update::kRegApField, ap_key_value.c_str())) {
+      DLOG(ERROR) << "Failed to remove suffix from omaha ap key value.";
+      result = REGDB_E_WRITEREGDB;
+    }
+  } else {
+    // Getting here means that no modifications needed to be made.
+    result = S_FALSE;
+  }
+
+  return result;
+}
+
+std::wstring GetResourceString(int resource_id) {
+  std::wstring resource_string;
+  HMODULE this_module = reinterpret_cast<HMODULE>(&__ImageBase);
+  const ATLSTRINGRESOURCEIMAGE* image = AtlGetStringResourceImage(
+      this_module, resource_id);
+  if (image) {
+    resource_string.assign(image->achString, image->nLength);
+  } else {
+    NOTREACHED() << "Unable to find resource id " << resource_id;
+  }
+  return resource_string;
+}
+
+void DisplayVersionMismatchWarning(HWND parent,
+                                   const std::string& server_version) {
+  // Obtain the current module version.
+  FileVersionInfo* file_version_info =
+      FileVersionInfo::CreateFileVersionInfoForCurrentModule();
+  DCHECK(file_version_info);
+  std::wstring version_string(file_version_info->file_version());
+  std::wstring wide_server_version;
+  if (server_version.empty()) {
+    wide_server_version = GetResourceString(IDS_VERSIONUNKNOWN);
+  } else {
+    wide_server_version = ASCIIToWide(server_version);
+  }
+  std::wstring title = GetResourceString(IDS_VERSIONMISMATCH_HEADER);
+  std::wstring message;
+  SStringPrintf(&message, GetResourceString(IDS_VERSIONMISMATCH).c_str(),
+                wide_server_version.c_str(), version_string.c_str());
+
+  ::MessageBox(parent, message.c_str(), title.c_str(), MB_OK);
+}
+
+std::string CreateJavascript(const std::string& function_name,
+                             const std::string args) {
+  std::string script_string = "javascript:";
+  script_string += function_name + "(";
+  if (!args.empty()) {
+    script_string += "'";
+    script_string += args;
+    script_string += "'";
+  }
+  script_string += ")";
+  return script_string;
+}
+
+AddRefModule::AddRefModule() {
+  // TODO(tommi): Override the module's Lock/Unlock methods to call
+  //  npapi::SetValue(NPPVpluginKeepLibraryInMemory) and keep the dll loaded
+  //  while the module's refcount is > 0.  Only do this when we're being
+  //  used as an NPAPI module.
+  _pAtlModule->Lock();
+}
+
+
+AddRefModule::~AddRefModule() {
+  _pAtlModule->Unlock();
+}
+
+namespace {
+const char kIEImageName[] = "iexplore.exe";
+const char kFirefoxImageName[] = "firefox.exe";
+const char kOperaImageName[] = "opera.exe";
+}  // namespace
+
+std::wstring GetHostProcessName(bool include_extension) {
+  FilePath exe;
+  if (PathService::Get(base::FILE_EXE, &exe))
+    exe = exe.BaseName();
+  if (!include_extension) {
+    exe = exe.RemoveExtension();
+  }
+  return exe.ToWStringHack();
+}
+
+BrowserType GetBrowserType() {
+  static BrowserType browser_type = BROWSER_INVALID;
+
+  if (browser_type == BROWSER_INVALID) {
+    std::wstring exe(GetHostProcessName(true));
+    if (!exe.empty()) {
+      std::wstring::const_iterator begin = exe.begin();
+      std::wstring::const_iterator end = exe.end();
+      if (LowerCaseEqualsASCII(begin, end, kIEImageName)) {
+        browser_type = BROWSER_IE;
+      } else if (LowerCaseEqualsASCII(begin, end, kFirefoxImageName)) {
+        browser_type = BROWSER_FIREFOX;
+      } else if (LowerCaseEqualsASCII(begin, end, kOperaImageName)) {
+        browser_type = BROWSER_OPERA;
+      } else {
+        browser_type = BROWSER_UNKNOWN;
+      }
+    } else {
+      NOTREACHED();
+    }
+  }
+
+  return browser_type;
+}
+
+IEVersion GetIEVersion() {
+  static IEVersion ie_version = IE_INVALID;
+
+  if (ie_version == IE_INVALID) {
+    wchar_t exe_path[MAX_PATH];
+    HMODULE mod = GetModuleHandle(NULL);
+    GetModuleFileName(mod, exe_path, arraysize(exe_path) - 1);
+    std::wstring exe_name(file_util::GetFilenameFromPath(exe_path));
+    if (!LowerCaseEqualsASCII(exe_name, kIEImageName)) {
+      ie_version = NON_IE;
+    } else {
+      uint32 high = 0;
+      uint32 low  = 0;
+      if (GetModuleVersion(mod, &high, &low)) {
+        switch (HIWORD(high)) {
+          case 6:
+            ie_version = IE_6;
+            break;
+          case 7:
+            ie_version = IE_7;
+            break;
+          default:
+            ie_version = HIWORD(high) >= 8 ? IE_8 : IE_UNSUPPORTED;
+            break;
+        }
+      } else {
+        NOTREACHED() << "Can't get IE version";
+      }
+    }
+  }
+
+  return ie_version;
+}
+
+bool IsIEInPrivate() {
+  typedef BOOL (WINAPI* IEIsInPrivateBrowsingPtr)();
+  bool incognito_mode = false;
+  HMODULE h = GetModuleHandle(L"ieframe.dll");
+  if (h) {
+    IEIsInPrivateBrowsingPtr IsInPrivate =
+        reinterpret_cast<IEIsInPrivateBrowsingPtr>(GetProcAddress(h,
+        "IEIsInPrivateBrowsing"));
+    if (IsInPrivate) {
+      incognito_mode = !!IsInPrivate();
+    }
+  }
+
+  return incognito_mode;
+}
+
+bool GetModuleVersion(HMODULE module, uint32* high, uint32* low) {
+  DCHECK(module != NULL)
+      << "Please use GetModuleHandle(NULL) to get the process name";
+  DCHECK(high);
+
+  bool ok = false;
+
+  HRSRC res = FindResource(module,
+      reinterpret_cast<const wchar_t*>(VS_VERSION_INFO), RT_VERSION);
+  if (res) {
+    HGLOBAL res_data = LoadResource(module, res);
+    DWORD version_resource_size = SizeofResource(module, res);
+    const void* readonly_resource_data = LockResource(res_data);
+    if (readonly_resource_data && version_resource_size) {
+      // Copy data as VerQueryValue tries to modify the data. This causes
+      // exceptions and heap corruption errors if debugger is attached.
+      scoped_ptr<char> data(new char[version_resource_size]);
+      memcpy(data.get(), readonly_resource_data, version_resource_size);
+      if (data.get()) {
+        VS_FIXEDFILEINFO* ver_info = NULL;
+        UINT info_size = 0;
+        if (VerQueryValue(data.get(), L"\\",
+                          reinterpret_cast<void**>(&ver_info), &info_size)) {
+          *high = ver_info->dwFileVersionMS;
+          if (low != NULL)
+            *low = ver_info->dwFileVersionLS;
+          ok = true;
+        }
+
+        UnlockResource(res_data);
+      }
+      FreeResource(res_data);
+    }
+  }
+
+  return ok;
+}
+
+namespace {
+
+const int kMaxSubmenuDepth = 10;
+
+// Copies original_menu and returns the copy. The caller is responsible for
+// closing the returned HMENU. This does not currently copy over bitmaps
+// (e.g. hbmpChecked, hbmpUnchecked or hbmpItem), so checkmarks, radio buttons,
+// and custom icons won't work.
+// It also copies over submenus up to a maximum depth of kMaxSubMenuDepth.
+//
+// TODO(robertshield): Add support for the bitmap fields if need be.
+HMENU UtilCloneContextMenuImpl(HMENU original_menu, int depth) {
+  DCHECK(IsMenu(original_menu));
+
+  if (depth >= kMaxSubmenuDepth)
+    return NULL;
+
+  HMENU new_menu = CreatePopupMenu();
+  int item_count = GetMenuItemCount(original_menu);
+  if (item_count <= 0) {
+    NOTREACHED();
+  } else {
+    for (int i = 0; i < item_count; i++) {
+      MENUITEMINFO item_info = { 0 };
+      item_info.cbSize = sizeof(MENUITEMINFO);
+      item_info.fMask = MIIM_ID | MIIM_STRING | MIIM_FTYPE |
+                        MIIM_STATE | MIIM_DATA | MIIM_SUBMENU |
+                        MIIM_CHECKMARKS | MIIM_BITMAP;
+
+      // Call GetMenuItemInfo a first time to obtain the buffer size for
+      // the label.
+      if (GetMenuItemInfo(original_menu, i, TRUE, &item_info)) {
+        item_info.cch++;  // Increment this as per MSDN
+        std::vector<wchar_t> buffer(item_info.cch, 0);
+        item_info.dwTypeData = &buffer[0];
+
+        // Call GetMenuItemInfo a second time with dwTypeData set to a buffer
+        // of a correct size to get the label.
+        GetMenuItemInfo(original_menu, i, TRUE, &item_info);
+
+        // Clone any submenus. Within reason.
+        if (item_info.hSubMenu) {
+          HMENU new_submenu = UtilCloneContextMenuImpl(item_info.hSubMenu,
+                                                       depth + 1);
+          item_info.hSubMenu = new_submenu;
+        }
+
+        // Now insert the item into the new menu.
+        InsertMenuItem(new_menu, i, TRUE, &item_info);
+      }
+    }
+  }
+  return new_menu;
+}
+
+}  // namespace
+
+HMENU UtilCloneContextMenu(HMENU original_menu) {
+  return UtilCloneContextMenuImpl(original_menu, 0);
+}
+
+std::string ResolveURL(const std::string& document,
+                       const std::string& relative) {
+  if (document.empty()) {
+    return GURL(relative).spec();
+  } else {
+    return GURL(document).Resolve(relative).spec();
+  }
+}
+
+bool HaveSameOrigin(const std::string& url1, const std::string& url2) {
+  GURL a(url1), b(url2);
+  bool ret;
+  if (a.is_valid() != b.is_valid()) {
+    // Either (but not both) url is invalid, so they can't match.
+    ret = false;
+  } else if (!a.is_valid()) {
+    // Both URLs are invalid (see first check).  Just check if the opaque
+    // strings match exactly.
+    ret = url1.compare(url2) == 0;
+  } else if (a.GetOrigin() != b.GetOrigin()) {
+    // The origins don't match.
+    ret = false;
+  } else {
+    // we have a match.
+    ret = true;
+  }
+
+  return ret;
+}
+
+int GetConfigInt(int default_value, const wchar_t* value_name) {
+  int ret = default_value;
+  RegKey config_key;
+  if (config_key.Open(HKEY_CURRENT_USER, kChromeFrameConfigKey,
+                      KEY_QUERY_VALUE)) {
+    int value = FALSE;
+    if (config_key.ReadValueDW(value_name, reinterpret_cast<DWORD*>(&value))) {
+      ret = value;
+    }
+  }
+
+  return ret;
+}
+
+bool GetConfigBool(bool default_value, const wchar_t* value_name) {
+  DWORD value = GetConfigInt(default_value, value_name);
+  return (value != FALSE);
+}
+
+bool IsOptInUrl(const wchar_t* url) {
+  RegKey config_key;
+  if (!config_key.Open(HKEY_CURRENT_USER, kChromeFrameConfigKey, KEY_READ))
+    return false;
+
+  RegistryValueIterator optin_urls_list(config_key.Handle(),
+                                        kChromeFrameOptinUrlsKey);
+  while (optin_urls_list.Valid()) {
+    if (MatchPattern(url, optin_urls_list.Name()))
+      return true;
+    ++optin_urls_list;
+  }
+
+  return false;
+}
+
+HRESULT GetUrlFromMoniker(IMoniker* moniker, IBindCtx* bind_context,
+                          std::wstring* url) {
+  if (!moniker || !url) {
+    NOTREACHED();
+    return E_INVALIDARG;
+  }
+
+  ScopedComPtr<IBindCtx> temp_bind_context;
+  if (!bind_context) {
+    CreateBindCtx(0, temp_bind_context.Receive());
+    bind_context = temp_bind_context;
+  }
+
+  CComHeapPtr<WCHAR> display_name;
+  HRESULT hr = moniker->GetDisplayName(bind_context, NULL, &display_name);
+  if (display_name)
+    *url = display_name;
+
+  return hr;
+}
+
+bool IsValidUrlScheme(const std::wstring& url) {
+  if (url.empty())
+    return false;
+
+  GURL crack_url(url);
+
+  if (crack_url.SchemeIs("http") || crack_url.SchemeIs("https") ||
+      crack_url.SchemeIs("about") || crack_url.SchemeIs("view-source"))
+    return true;
+
+  if (StartsWith(url, kChromeAttachExternalTabPrefix, false))
+    return true;
+
+  return false;
+}
+
+// TODO(robertshield): Register and use Chrome's PathProviders.
+// - Note that this function is used by unit tests as well to override
+//   PathService paths, so please test when addressing todo.
+bool GetUserProfileBaseDirectory(std::wstring* path) {
+  DCHECK(path);
+  wchar_t path_buffer[MAX_PATH * 4];
+  path_buffer[0] = 0;
+  // TODO(robertshield): Ideally we should use SHGetFolderLocation and then
+  // get a path via PIDL.
+  HRESULT hr = SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL,
+                               SHGFP_TYPE_CURRENT, path_buffer);
+
+  if (SUCCEEDED(hr)) {
+    *path = path_buffer;
+#if defined(GOOGLE_CHROME_BUILD)
+    file_util::AppendToPath(path, FILE_PATH_LITERAL("Google"));
+#endif
+    file_util::AppendToPath(path, chrome::kBrowserAppName);
+    file_util::AppendToPath(path, chrome::kUserDataDirname);
+    return true;
+  }
+
+  return false;
+}
diff --git a/chrome_frame/utils.h b/chrome_frame/utils.h
new file mode 100644
index 0000000..80b9a53
--- /dev/null
+++ b/chrome_frame/utils.h
@@ -0,0 +1,227 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_UTILS_H_
+#define CHROME_FRAME_UTILS_H_
+
+#include <atlbase.h>
+#include <string>
+
+#include "base/basictypes.h"
+
+// utils.h : Various utility functions and classes
+
+extern const wchar_t kChromeContentPrefix[];
+extern const wchar_t kChromeProtocolPrefix[];
+
+// This function is very similar to the AtlRegisterTypeLib function except
+// that it takes a parameter that specifies whether to register the typelib
+// for the current user only or on a machine-wide basis
+// Refer to the MSDN documentation for AtlRegisterTypeLib for a description of
+// the arguments
+HRESULT UtilRegisterTypeLib(HINSTANCE tlb_instance,
+                            LPCOLESTR index,
+                            bool for_current_user_only);
+
+// This function is very similar to the AtlUnRegisterTypeLib function except
+// that it takes a parameter that specifies whether to unregister the typelib
+// for the current user only or on a machine-wide basis
+// Refer to the MSDN documentation for AtlUnRegisterTypeLib for a description
+// of the arguments
+HRESULT UtilUnRegisterTypeLib(HINSTANCE tlb_instance,
+                              LPCOLESTR index,
+                              bool for_current_user_only);
+
+HRESULT UtilRegisterTypeLib(LPCWSTR typelib_path, bool for_current_user_only);
+
+HRESULT UtilUnRegisterTypeLib(LPCWSTR typelib_path, bool for_current_user_only);
+
+HRESULT UtilRegisterTypeLib(ITypeLib* typelib,
+                            LPCWSTR typelib_path,
+                            LPCWSTR help_dir,
+                            bool for_current_user_only);
+
+HRESULT UtilUnRegisterTypeLib(ITypeLib* typelib,
+                              bool for_current_user_only);
+
+// Given an HTML fragment, this function looks for the
+// <meta http-equiv="X-UA-Compatible"> tag and extracts the value of the
+// "content" attribute
+// This method will currently return a false positive if the tag appears
+// inside a string in a <SCRIPT> block.
+HRESULT UtilGetXUACompatContentValue(const std::wstring& html_string,
+                                     std::wstring* content_value);
+
+
+// Appends |suffix| to the substring |channel_name| of |string| iff
+// the first instance of |channel_name| in |string| is not already followed by
+// |suffix|.
+// Returns true if |string| was modified.
+bool AppendSuffixToChannelName(std::wstring* string,
+                               const std::wstring& channel_name,
+                               const std::wstring& suffix);
+
+// Removes |suffix| from |string| if |string| contains |channel_name| followed
+// by |suffix|.
+// Returns true if |string| was modified.
+bool RemoveSuffixFromChannelName(std::wstring* string,
+                                 const std::wstring& channel_name,
+                                 const std::wstring& suffix);
+
+// Looks for and alters if found the Omaha configuration for Chrome in the
+// registry. This changes the auto-update release channel to prevent installed
+// builds of Chrome that include Chrome Frame from getting replaced by
+// Chrome updates without it.
+// Adds the Chrome Frame suffix if add_cf_suffix is true, removes it
+// otherwise.
+// Returns S_OK if the Chrome Omaha configuration was found and updated.
+// Returns S_FALSE if the configuration was found but didn't need updating.
+// Returns REGDB_E_READREGDB if the Chrome Omaha key could not be read.
+// Returns REGDB_E_WRITEREGDB if the Chrome Omaha key could not be written.
+HRESULT UtilUpdateOmahaConfig(bool add_cf_suffix);
+
+// Returns a string from ChromeFrame's string table by resource. Must be
+// provided with a valid resource id.
+std::wstring GetResourceString(int resource_id);
+
+// Displays a message box indicating that there was a version mismatch between
+// ChromeFrame and the running instance of Chrome.
+// server_version is the version of the running instance of Chrome.
+void DisplayVersionMismatchWarning(HWND parent,
+                                   const std::string& server_version);
+
+// This class provides a base implementation for ATL modules which want to
+// perform all their registration under HKCU. This class overrides the
+// RegisterServer and UnregisterServer methods and registers the type libraries
+// under HKCU (the rest of the registation is made under HKCU by changing the
+// appropriate .RGS files)
+template < class BaseAtlModule >
+class AtlPerUserModule : public BaseAtlModule {
+  public:
+  HRESULT RegisterServer(BOOL reg_typelib = FALSE,
+                         const CLSID* clsid = NULL) throw() {
+    HRESULT hr = BaseAtlModule::RegisterServer(FALSE, clsid);
+    if (FAILED(hr)) {
+      return hr;
+    }
+    if (reg_typelib)  {
+      hr = UtilRegisterTypeLib(_AtlComModule.m_hInstTypeLib, NULL, false);
+    }
+    return hr;
+  }
+
+  HRESULT UnregisterServer(BOOL unreg_typelib,
+                           const CLSID* clsid = NULL) throw() {
+    HRESULT hr = BaseAtlModule::UnregisterServer(FALSE, clsid);
+    if (FAILED(hr)) {
+      return hr;
+    }
+    if (unreg_typelib)  {
+      hr = UtilUnRegisterTypeLib(_AtlComModule.m_hInstTypeLib, NULL, false);
+    }
+    return hr;
+  }
+};
+
+// Creates a javascript statement for execution from the function name and
+// arguments passed in.
+std::string CreateJavascript(const std::string& function_name,
+                             const std::string args);
+
+// Use to prevent the DLL from being unloaded while there are still living
+// objects with outstanding references.
+class AddRefModule {
+ public:
+  AddRefModule();
+  ~AddRefModule();
+};
+
+// Retrieves the executable name of the process hosting us. If
+// |include_extension| is false, then we strip the extension from the name.
+std::wstring GetHostProcessName(bool include_extension);
+
+typedef enum BrowserType {
+  BROWSER_INVALID = -1,
+  BROWSER_UNKNOWN,
+  BROWSER_IE,
+  BROWSER_FIREFOX,
+  BROWSER_OPERA,
+};
+
+BrowserType GetBrowserType();
+
+typedef enum IEVersion {
+  IE_INVALID,
+  NON_IE,
+  IE_UNSUPPORTED,
+  IE_6,
+  IE_7,
+  IE_8,
+};
+
+// To get the IE version when Chrome Frame is hosted in IE.  Make sure that
+// the hosting browser is IE before calling this function, otherwise NON_IE
+// will be returned.
+IEVersion GetIEVersion();
+
+// Retrieves the file version from a module handle without extra round trips
+// to the disk (as happens with the regular GetFileVersionInfo API).
+//
+// @param module A handle to the module for which to retrieve the version info.
+// @param high On successful return holds the most significant part of the
+// file version.  Must be non-null.
+// @param low On successful return holds the least significant part of the
+// file version.  May be NULL.
+// @returns true if the version info was successfully retrieved.
+bool GetModuleVersion(HMODULE module, uint32* high, uint32* low);
+
+// Return if the IEXPLORE is in private mode. The IEIsInPrivateBrowsing() checks
+// whether current process is IEXPLORE.
+bool IsIEInPrivate();
+
+// Creates a copy of a menu. We need this when original menu comes from
+// a process with higher integrity.
+HMENU UtilCloneContextMenu(HMENU original_menu);
+
+// Uses GURL internally to append 'relative' to 'document'
+std::string ResolveURL(const std::string& document,
+                       const std::string& relative);
+
+// Returns true iff the two urls have the same scheme, same host and same port.
+bool HaveSameOrigin(const std::string& url1, const std::string& url2);
+
+// Get a boolean configuration value from registry.
+bool GetConfigBool(bool default_value, const wchar_t* value_name);
+
+// Gets an integer configuration value from the registry.
+int GetConfigInt(int default_value, const wchar_t* value_name);
+
+// Check if this url is opting into Chrome Frame based on static settings.
+bool IsOptInUrl(const wchar_t* url);
+
+// A shortcut for QueryService
+template <typename T>
+HRESULT DoQueryService(const CLSID& class_id, IUnknown* unk, T** service) {
+  if (!unk)
+    return E_INVALIDARG;
+  ScopedComPtr<IServiceProvider> service_provider;
+  HRESULT hr = service_provider.QueryFrom(unk);
+  if (!service_provider)
+    return hr;
+
+  return service_provider->QueryService(class_id, service);
+}
+
+// Get url (display name) from a moniker, |bind_context| is optional
+HRESULT GetUrlFromMoniker(IMoniker* moniker, IBindCtx* bind_context,
+                          std::wstring* url);
+
+// Returns true if the URL passed in is something which can be handled by
+// Chrome. If this function returns false then we should fail the navigation.
+bool IsValidUrlScheme(const std::wstring& url);
+
+// This returns the base directory in which to store user profiles.
+bool GetUserProfileBaseDirectory(std::wstring* path);
+
+#endif  // CHROME_FRAME_UTILS_H_
diff --git a/chrome_frame/vectored_handler-impl.h b/chrome_frame/vectored_handler-impl.h
new file mode 100644
index 0000000..f850641
--- /dev/null
+++ b/chrome_frame/vectored_handler-impl.h
@@ -0,0 +1,106 @@
+// Copyright (c) 2009 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.

+

+#ifndef CHROME_FRAME_VECTORED_HANDLER_IMPL_H_

+#define CHROME_FRAME_VECTORED_HANDLER_IMPL_H_

+#include "chrome_frame/vectored_handler.h"

+

+#if defined(_M_IX86)

+typedef struct _EXCEPTION_REGISTRATION_RECORD {

+  struct _EXCEPTION_REGISTRATION_RECORD* Next;

+  PVOID Handler;

+} EXCEPTION_REGISTRATION_RECORD;

+#define EXCEPTION_CHAIN_END ((struct _EXCEPTION_REGISTRATION_RECORD*)-1)

+#else

+#error only x86 is supported for now.

+#endif

+

+

+// VEH handler flags settings.

+// These are grabbed from winnt.h for PocketPC.

+// Only EXCEPTION_NONCONTINUABLE in defined in "regular" winnt.h

+// #define EXCEPTION_NONCONTINUABLE 0x1    // Noncontinuable exception

+#define EXCEPTION_UNWINDING 0x2         // Unwind is in progress

+#define EXCEPTION_EXIT_UNWIND 0x4       // Exit unwind is in progress

+#define EXCEPTION_STACK_INVALID 0x8     // Stack out of limits or unaligned

+#define EXCEPTION_NESTED_CALL 0x10      // Nested exception handler call

+#define EXCEPTION_TARGET_UNWIND 0x20    // Target unwind in progress

+#define EXCEPTION_COLLIDED_UNWIND 0x40  // Collided exception handler call

+

+#define EXCEPTION_UNWIND (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND | \

+    EXCEPTION_TARGET_UNWIND | EXCEPTION_COLLIDED_UNWIND)

+

+#define IS_UNWINDING(Flag)  (((Flag) & EXCEPTION_UNWIND) != 0)

+#define IS_DISPATCHING(Flag)  (((Flag) & EXCEPTION_UNWIND) == 0)

+#define IS_TARGET_UNWIND(Flag)  ((Flag) & EXCEPTION_TARGET_UNWIND)

+// End of grabbed section

+

+template <class E>

+LONG WINAPI VectoredHandlerT<E>::VectoredHandler(

+    EXCEPTION_POINTERS* exceptionInfo) {

+  // TODO(stoyan): Consider reentrancy

+  const DWORD exceptionCode = exceptionInfo->ExceptionRecord->ExceptionCode;

+

+  // Not interested in non-error exceptions. In this category falls exceptions

+  // like:

+  // 0x40010006 - OutputDebugStringA. Seen when no debugger is attached

+  //              (otherwise debugger swallows the exception and prints

+  //              the string).

+  // 0x406D1388 - DebuggerProbe. Used by debug CRT - for example see source

+  //              code of isatty(). Used to name a thread as well.

+  // RPC_E_DISCONNECTED and Co. - COM IPC non-fatal warnings

+  // STATUS_BREAKPOINT and Co. - Debugger related breakpoints

+

+  if ((exceptionCode & ERROR_SEVERITY_ERROR) != ERROR_SEVERITY_ERROR) {

+    return ExceptionContinueSearch;

+  }

+

+  ++VectoredHandlerT<E>::g_exceptions_seen;

+

+  // TODO(stoyan): Check whether exception address is inbetween

+  // [IsBadReadPtr, IsBadReadPtr + 0xXX]

+

+  const DWORD exceptionFlags = exceptionInfo->ExceptionRecord->ExceptionFlags;

+  // I don't think VEH is called on unwind. Just to be safe.

+  if (IS_DISPATCHING(exceptionFlags)) {

+    if (ModuleHasInstalledSEHFilter())

+      return ExceptionContinueSearch;

+

+    if (E::IsOurModule(exceptionInfo->ExceptionRecord->ExceptionAddress)) {

+      E::WriteDump(exceptionInfo);

+      return ExceptionContinueSearch;

+    }

+

+    // See whether our module is somewhere in the call stack.

+    void* back_trace[max_back_trace] = {0};

+    // Skip RtlCaptureStackBackTrace and VectoredHandler itself.

+    DWORD captured = E::RtlCaptureStackBackTrace(2, max_back_trace - 2, 

+                                                 &back_trace[0], NULL);

+    for (DWORD i = 0; i < captured; ++i) {

+     if (E::IsOurModule(back_trace[i])) {

+       E::WriteDump(exceptionInfo);

+       return ExceptionContinueSearch;

+     }

+    }

+  }

+

+  return ExceptionContinueSearch;

+}

+

+template <class E>

+BOOL VectoredHandlerT<E>::ModuleHasInstalledSEHFilter() {

+  EXCEPTION_REGISTRATION_RECORD* RegistrationFrame = E::RtlpGetExceptionList();

+  // TODO(stoyan): Add the stack limits check and some sanity checks like

+  // decreasing addresses of registration records

+  while (RegistrationFrame != EXCEPTION_CHAIN_END) {

+    if (E::IsOurModule(RegistrationFrame->Handler)) {

+      return TRUE;

+    }

+

+    RegistrationFrame = RegistrationFrame->Next;

+  }

+

+  return FALSE;

+}

+#endif  // CHROME_FRAME_VECTORED_HANDLER_IMPL_H_

diff --git a/chrome_frame/vectored_handler.h b/chrome_frame/vectored_handler.h
new file mode 100644
index 0000000..7351b6c
--- /dev/null
+++ b/chrome_frame/vectored_handler.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2009 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.

+

+#ifndef CHROME_FRAME_VECTORED_HANDLER_H_

+#define CHROME_FRAME_VECTORED_HANDLER_H_

+

+// Base class for VectoredHandlerT just to hold some members (independent of

+// template parameter)

+class VectoredHandlerBase {

+ public: 

+  // For RtlCaptureStackBackTrace MSDN says:

+  // Windows Server 2003 and Windows XP:  The sum of the FramesToSkip and

+  // FramesToCapture parameters must be less than 64.

+  // In practice (on XPSP2) it has to be less than 63, hence leaving us with

+  // max back trace of 62.

+  static const DWORD max_back_trace = 62;

+  static unsigned long g_exceptions_seen;

+ protected:

+  static void* g_handler;

+};

+

+DECLSPEC_SELECTANY void* VectoredHandlerBase::g_handler;

+DECLSPEC_SELECTANY unsigned long VectoredHandlerBase::g_exceptions_seen;

+

+// The E class is supposed to provide external/API functions. Using template

+// make testability easier. It shall confirm the following concept/archetype:

+// void* Register(PVECTORED_EXCEPTION_HANDLER,

+//                const void* module_start, const void* module_end)

+// Registers Vectored Exception Handler, non-unittest implementation shall call

+// ::AddVectoredExceptionHandler Win32 API

+// ULONG Unregister(void*) - ::RemoveVectoredExceptionHandler Win32 API

+// int IsOurModule(const void* address) -

+// void WriteDump(EXCEPTION_POINTERS*) - 

+// WORD RtlCaptureStackBackTrace(..) - same as Win32 API

+// EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() - same as Win32 API

+// You may want to derive own External class by deriving from

+// VEHExternalBase helper (see below).

+// Create dump policy:

+// 1. Scan SEH chain, if there is a handler/filter that belongs to our

+//    module - assume we expect this one and hence do nothing here.

+// 2. If the address of the exception is in our module - create dump.

+// 3. If our module is in somewhere in callstack - create dump.

+template <class E>

+class VectoredHandlerT : public VectoredHandlerBase {

+ public:

+  static void* Register(const void* module_start, const void* module_end) {

+    g_exceptions_seen = 0;

+    g_handler = E::Register(&VectoredHandler, module_start, module_end);

+    return g_handler;

+  }

+

+  static ULONG Unregister() {

+    if (g_handler)

+      return E::Unregister(g_handler);

+    return 0;

+  }

+

+  static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* exceptionInfo);

+ private:

+  static BOOL ModuleHasInstalledSEHFilter();

+};

+

+// Handy class supposed to act as a base class for classes used as template

+// parameter of VectoredHandlerT<E>

+class VEHTraitsBase {

+ public:

+  static const void* g_module_start;

+  static const void* g_module_end;

+

+  static inline int IsOurModule(const void* address) {

+    return (g_module_start <= address && address < g_module_end);

+  }

+

+  static inline void SetModule(const void* module_start,

+                               const void* module_end) {

+    g_module_start = module_start;

+    g_module_end = module_end;

+  }

+};

+

+DECLSPEC_SELECTANY const void* VEHTraitsBase::g_module_start;

+DECLSPEC_SELECTANY const void* VEHTraitsBase::g_module_end;

+

+class Win32VEHTraits;

+typedef class VectoredHandlerT<Win32VEHTraits> VectoredHandler;

+#endif  // CHROME_FRAME_VECTORED_HANDLER_H_

diff --git a/chrome_frame/vtable_patch_manager.cc b/chrome_frame/vtable_patch_manager.cc
new file mode 100644
index 0000000..5f15158
--- /dev/null
+++ b/chrome_frame/vtable_patch_manager.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2009 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.
+
+#include "chrome_frame/vtable_patch_manager.h"
+
+#include "base/logging.h"
+
+#include "chrome_frame/function_stub.h"
+
+namespace vtable_patch {
+
+// Convenient definition of a VTABLE
+typedef PROC* Vtable;
+
+// Returns a pointer to the VTable of a COM interface.
+// @param unknown [in] The pointer of the COM interface.
+inline Vtable GetIFVTable(void* unknown) {
+  return reinterpret_cast<Vtable>(*reinterpret_cast<void**>(unknown));
+}
+
+HRESULT PatchInterfaceMethods(void* unknown, MethodPatchInfo* patches) {
+  // Do some sanity checking of the input arguments.
+  if (NULL == unknown || NULL == patches) {
+    NOTREACHED();
+    return E_INVALIDARG;
+  }
+
+  Vtable vtable = GetIFVTable(unknown);
+  DCHECK(vtable);
+
+  for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) {
+    PROC original_fn = vtable[it->index_];
+    FunctionStub* stub = FunctionStub::FromCode(original_fn);
+    if (stub != NULL) {
+      DLOG(ERROR) << "attempt to patch a function that's already patched";
+      DCHECK(stub->absolute_target() ==
+             reinterpret_cast<uintptr_t>(it->method_)) <<
+             "patching the same method multiple times with different hooks?";
+      continue;
+    }
+
+    stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(original_fn),
+                                it->method_);
+    if (!stub) {
+      NOTREACHED();
+      return E_OUTOFMEMORY;
+    } else {
+      DWORD protect = 0;
+      if (::VirtualProtect(&vtable[it->index_], sizeof(PROC),
+                           PAGE_EXECUTE_READWRITE, &protect)) {
+        it->stub_ = stub;  // save the stub
+        vtable[it->index_] = stub->code();
+        ::VirtualProtect(&vtable[it->index_], sizeof(PROC), protect,
+                         &protect);
+      } else {
+        NOTREACHED();
+      }
+    }
+  }
+
+  return S_OK;
+}
+
+HRESULT UnpatchInterfaceMethods(MethodPatchInfo* patches) {
+  for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) {
+    if (it->stub_) {
+      DCHECK(it->stub_->absolute_target() == 
+          reinterpret_cast<uintptr_t>(it->method_));
+      // Modify the stub to just jump directly to the original function.
+      it->stub_->BypassStub(reinterpret_cast<void*>(it->stub_->argument()));
+      it->stub_ = NULL;
+      // Leave the stub in memory so that we won't break any possible chains.
+    } else {
+      DLOG(WARNING) << "attempt to unpatch a function that wasn't patched";
+    }
+  }
+
+  return S_OK;
+}
+
+}  // namespace vtable_patch
diff --git a/chrome_frame/vtable_patch_manager.h b/chrome_frame/vtable_patch_manager.h
new file mode 100644
index 0000000..944b9ef5
--- /dev/null
+++ b/chrome_frame/vtable_patch_manager.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_COMMON_VTABLE_PATCH_MANAGER_H_
+#define CHROME_FRAME_COMMON_VTABLE_PATCH_MANAGER_H_
+
+#include <windows.h>
+
+struct FunctionStub;
+// This namespace provides methods to patch VTable methods of COM interfaces.
+namespace vtable_patch {
+
+// This structure represents information about one VTable method.
+// We allocate an array of these structures per VTable that we patch to
+// remember the original method. We also use this structure to actually
+// describe the VTable patch functions
+struct MethodPatchInfo {
+  int index_;
+  PROC method_;
+  FunctionStub* stub_;
+};
+
+// Patches methods in the passed in COM interface. The indexes of the
+// methods to patch and the actual patch functions are described in the
+// array pointed to by patches.
+// @param[in] unknown  The pointer of the COM interface to patch
+// @param[in] patches  An array of MethodPatchInfo structures describing
+//  the methods to patch and the patch functions.
+//  The last entry of patches must have index_ set to -1.
+HRESULT PatchInterfaceMethods(void* unknown, MethodPatchInfo* patches);
+
+// Using the patch info provided in |patches| the function goes through the
+// list of patched methods and modifies thunks so that they no longer point
+// to a hook method but rather go straight through to the original target.
+// The thunk itself is not destroyed to support chaining.
+// @param[in] patches  An array of MethodPatchInfo structures describing
+//  the methods to patch and the patch functions.
+//  The last entry of patches must have index_ set to -1.
+HRESULT UnpatchInterfaceMethods(MethodPatchInfo* patches);
+
+}  // namespace vtable_patch
+
+// Begins the declaration of a VTable patch
+// @param IFName The name of the interface to patch
+#define BEGIN_VTABLE_PATCHES(IFName) \
+    vtable_patch::MethodPatchInfo IFName##_PatchInfo[] = {
+
+// Defines a single method patch in a VTable
+// @param index The index of the method to patch
+// @param PatchFunction The patch function
+#define VTABLE_PATCH_ENTRY(index, PatchFunction) {\
+      index, \
+      reinterpret_cast<PROC>(PatchFunction), \
+      NULL, \
+    },
+
+// Ends the declaration of a VTable patch by adding an entry with
+// index set to -1.
+#define END_VTABLE_PATCHES() \
+      -1, NULL, NULL \
+    };
+
+#endif // CHROME_FRAME_COMMON_VTABLE_PATCH_MANAGER_H_