Add the host resolver cache to the new net internals page.

BUG=37421

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43998 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/dom_ui/net_internals_ui.cc b/chrome/browser/dom_ui/net_internals_ui.cc
index d33b17f5a..a932d75 100644
--- a/chrome/browser/dom_ui/net_internals_ui.cc
+++ b/chrome/browser/dom_ui/net_internals_ui.cc
@@ -23,6 +23,10 @@
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/url_constants.h"
 #include "net/base/escape.h"
+#include "net/base/host_resolver_impl.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/sys_addrinfo.h"
 #include "net/proxy/proxy_service.h"
 #include "net/url_request/url_request_context.h"
 
@@ -33,6 +37,18 @@
   return Int64ToString((t - base::TimeTicks()).InMilliseconds());
 }
 
+// Returns the HostCache for |context|'s primary HostResolver, or NULL if
+// there is none.
+net::HostCache* GetHostResolverCache(URLRequestContext* context) {
+  net::HostResolverImpl* host_resolver_impl =
+      context->host_resolver()->GetAsHostResolverImpl();
+
+  if (!host_resolver_impl)
+    return NULL;
+
+  return host_resolver_impl->cache();
+}
+
 // TODO(eroman): Bootstrap the net-internals page using the passively logged
 //               data.
 
@@ -132,6 +148,8 @@
   void OnReloadProxySettings(const Value* value);
   void OnGetBadProxies(const Value* value);
   void OnClearBadProxies(const Value* value);
+  void OnGetHostResolverCache(const Value* value);
+  void OnClearHostResolverCache(const Value* value);
 
   // ChromeNetLog::Observer implementation:
   virtual void OnAddEntry(const net::NetLog::Entry& entry);
@@ -279,6 +297,10 @@
       proxy_->CreateCallback(&IOThreadImpl::OnGetBadProxies));
   dom_ui_->RegisterMessageCallback("clearBadProxies",
       proxy_->CreateCallback(&IOThreadImpl::OnClearBadProxies));
+  dom_ui_->RegisterMessageCallback("getHostResolverCache",
+      proxy_->CreateCallback(&IOThreadImpl::OnGetHostResolverCache));
+  dom_ui_->RegisterMessageCallback("clearHostResolverCache",
+      proxy_->CreateCallback(&IOThreadImpl::OnClearHostResolverCache));
 }
 
 void NetInternalsMessageHandler::CallJavascriptFunction(
@@ -417,6 +439,7 @@
   // Notify the client of the basic proxy data.
   OnGetProxySettings(NULL);
   OnGetBadProxies(NULL);
+  OnGetHostResolverCache(NULL);
 }
 
 void NetInternalsMessageHandler::IOThreadImpl::OnGetProxySettings(
@@ -481,6 +504,77 @@
   OnGetBadProxies(NULL);
 }
 
+void NetInternalsMessageHandler::IOThreadImpl::OnGetHostResolverCache(
+    const Value* value) {
+
+  net::HostCache* cache =
+      GetHostResolverCache(context_getter_->GetURLRequestContext());
+
+  if (!cache) {
+    CallJavascriptFunction(L"g_browser.receivedHostResolverCache", NULL);
+    return;
+  }
+
+  DictionaryValue* dict = new DictionaryValue();
+
+  dict->SetInteger(L"capacity", static_cast<int>(cache->max_entries()));
+  dict->SetInteger(
+      L"ttl_success_ms",
+      static_cast<int>(cache->success_entry_ttl().InMilliseconds()));
+  dict->SetInteger(
+      L"ttl_failure_ms",
+      static_cast<int>(cache->failure_entry_ttl().InMilliseconds()));
+
+  ListValue* entry_list = new ListValue();
+
+  for (net::HostCache::EntryMap::const_iterator it =
+       cache->entries().begin();
+       it != cache->entries().end();
+       ++it) {
+    const net::HostCache::Key& key = it->first;
+    const net::HostCache::Entry* entry = it->second.get();
+
+    DictionaryValue* entry_dict = new DictionaryValue();
+
+    entry_dict->SetString(L"hostname", key.hostname);
+    entry_dict->SetInteger(L"address_family",
+        static_cast<int>(key.address_family));
+    entry_dict->SetString(L"expiration", TickCountToString(entry->expiration));
+
+    if (entry->error != net::OK) {
+      entry_dict->SetInteger(L"error", entry->error);
+    } else {
+      // Append all of the resolved addresses.
+      ListValue* address_list = new ListValue();
+      const struct addrinfo* current_address = entry->addrlist.head();
+      while (current_address) {
+        address_list->Append(Value::CreateStringValue(
+            net::NetAddressToString(current_address)));
+        current_address = current_address->ai_next;
+      }
+      entry_dict->Set(L"addresses", address_list);
+    }
+
+    entry_list->Append(entry_dict);
+  }
+
+  dict->Set(L"entries", entry_list);
+
+  CallJavascriptFunction(L"g_browser.receivedHostResolverCache", dict);
+}
+
+void NetInternalsMessageHandler::IOThreadImpl::OnClearHostResolverCache(
+    const Value* value) {
+  net::HostCache* cache =
+      GetHostResolverCache(context_getter_->GetURLRequestContext());
+
+  if (cache)
+    cache->clear();
+
+  // Cause the renderer to be notified of the new values.
+  OnGetHostResolverCache(NULL);
+}
+
 void NetInternalsMessageHandler::IOThreadImpl::OnAddEntry(
     const net::NetLog::Entry& entry) {
   DCHECK(is_observing_log_);
diff --git a/chrome/browser/resources/net_internals/dnsview.js b/chrome/browser/resources/net_internals/dnsview.js
new file mode 100644
index 0000000..b1f7d2f
--- /dev/null
+++ b/chrome/browser/resources/net_internals/dnsview.js
@@ -0,0 +1,82 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * This view displays information on the host resolver:
+ *
+ *   - Shows the current host cache contents.
+ *   - Has a button to clear the host cache.
+ *   - Shows the parameters used to construct the host cache (capacity, ttl).
+ *
+ *  @constructor
+ */
+function DnsView(mainBoxId,
+                 cacheTbodyId,
+                 clearCacheButtonId,
+                 capacitySpanId,
+                 ttlSuccessSpanId,
+                 ttlFailureSpanId) {
+  DivView.call(this, mainBoxId);
+
+  // Hook up the UI components.
+  this.cacheTbody_ = document.getElementById(cacheTbodyId);
+  this.capacitySpan_ = document.getElementById(capacitySpanId);
+  this.ttlSuccessSpan_ = document.getElementById(ttlSuccessSpanId);
+  this.ttlFailureSpan_ = document.getElementById(ttlFailureSpanId);
+
+  var clearCacheButton = document.getElementById(clearCacheButtonId);
+  clearCacheButton.onclick =
+      g_browser.sendClearHostResolverCache.bind(g_browser);
+
+  // Register to receive changes to the host resolver cache.
+  g_browser.addHostResolverCacheObserver(this);
+}
+
+inherits(DnsView, DivView);
+
+DnsView.prototype.onHostResolverCacheChanged = function(hostResolverCache) {
+  // Clear the existing values.
+  this.capacitySpan_.innerHTML = '';
+  this.ttlSuccessSpan_.innerHTML = '';
+  this.ttlFailureSpan_.innerHTML = '';
+  this.cacheTbody_.innerHTML = '';
+
+  // No cache.
+  if (!hostResolverCache)
+    return;
+
+  // Fill in the basic cache information.
+  addTextNode(this.capacitySpan_, hostResolverCache.capacity);
+  addTextNode(this.ttlSuccessSpan_, hostResolverCache.ttl_success_ms);
+  addTextNode(this.ttlFailureSpan_, hostResolverCache.ttl_failure_ms);
+
+  // Fill in the cache contents table.
+  for (var i = 0; i < hostResolverCache.entries.length; ++i) {
+    var e = hostResolverCache.entries[i];
+    var tr = addNode(this.cacheTbody_, 'tr');
+
+    var hostnameCell = addNode(tr, 'td');
+    addTextNode(hostnameCell, e.hostname);
+
+    var familyCell = addNode(tr, 'td');
+    addTextNode(familyCell, e.address_family);
+
+    var addressesCell = addNode(tr, 'td');
+
+    if (e.error != undefined) {
+      addTextNode(addressesCell, 'error: ' + e.error);
+    } else {
+      for (var j = 0; j < e.addresses.length; ++j) {
+        var address = e.addresses[j];
+        if (j != 0)
+          addNode(addressesCell, 'br');
+        addTextNode(addressesCell, address);
+      }
+    }
+
+    var expiresDate = g_browser.convertTimeTicksToDate(e.expiration);
+    var expiresCell = addNode(tr, 'td');
+    addTextNode(expiresCell, expiresDate.toLocaleString());
+  }
+};
diff --git a/chrome/browser/resources/net_internals/index.html b/chrome/browser/resources/net_internals/index.html
index d8c9046..301eb16 100644
--- a/chrome/browser/resources/net_internals/index.html
+++ b/chrome/browser/resources/net_internals/index.html
@@ -10,6 +10,7 @@
     <script src="view.js"></script>
     <script src="tabswitcherview.js"></script>
     <script src="main.js"></script>
+    <script src="dnsview.js"></script>
     <script src="requestsview.js"></script>
     <script src="detailsview.js"></script>
     <script src="sourceentry.js"></script>
@@ -54,8 +55,32 @@
         <tbody id=badProxiesTableBody></tbody>
       </table>
     </div>
+    <!-- Host resolver info -->
+    <div id=dnsTabContent>
+      <h4>
+        Host resolver cache
+        <input type=button value="Clear host cache" id=clearHostResolverCache />
+      </h4>
+      <ul>
+        <li>Capacity: <span id=hostResolverCacheCapacity></span></li>
+        <li>Time to live (ms) for success entries:
+            <span id=hostResolverCacheTTLSuccess></span></li>
+        <li>Time to live (ms) for failure entries:
+            <span id=hostResolverCacheTTLFailure></span></li>
+      </ul>
+
+      <table border=1>
+        <thead>
+          <th>Hostname</th>
+          <th>Family</th>
+          <th>Addresses</th>
+          <th>Expires</th>
+        </thead>
+        <tbody id=hostResolverCacheTbody>
+        </tbody>
+      </table>
+    </div>
     <!-- Sections TODO -->
-    <div id=dnsTabContent>TODO: display dns information (outstanding jobs, host cache).</div>
     <div id=socketsTabContent>TODO: display socket information (outstanding connect jobs)</div>
     <div id=httpCacheTabContent>TODO: display http cache information (disk cache navigator)</div>
 
diff --git a/chrome/browser/resources/net_internals/main.js b/chrome/browser/resources/net_internals/main.js
index d4c0c11..3ab30eb 100644
--- a/chrome/browser/resources/net_internals/main.js
+++ b/chrome/browser/resources/net_internals/main.js
@@ -50,6 +50,14 @@
                                 "badProxiesTableBody",
                                 "clearBadProxies");
 
+  // Create a view which will display information on the host resolver.
+  var dnsView = new DnsView("dnsTabContent",
+                            "hostResolverCacheTbody",
+                            "clearHostResolverCache",
+                            "hostResolverCacheCapacity",
+                            "hostResolverCacheTTLSuccess",
+                            "hostResolverCacheTTLFailure");
+
   // Create a view which lets you tab between the different sub-views.
   var categoryTabSwitcher =
       new TabSwitcherView(new DivView('categoryTabHandles'));
@@ -57,7 +65,7 @@
   // Populate the main tabs.
   categoryTabSwitcher.addTab('requestsTab', requestsView, false);
   categoryTabSwitcher.addTab('proxyTab', proxyView, false);
-  categoryTabSwitcher.addTab('dnsTab', new DivView('dnsTabContent'), false);
+  categoryTabSwitcher.addTab('dnsTab', dnsView, false);
   categoryTabSwitcher.addTab('socketsTab', new DivView('socketsTabContent'),
                              false);
   categoryTabSwitcher.addTab('httpCacheTab',
@@ -104,10 +112,11 @@
   this.logObservers_ = [];
   this.proxySettingsObservers_ = [];
   this.badProxiesObservers_ = [];
+  this.hostResolverCacheObservers_ = [];
 
-  // Map from observer method name (i.e. 'onProxySettingsChanged', 'onBadProxiesChanged')
-  // to the previously received data for that type. Used to tell if the data has
-  // actually changed since we last polled it.
+  // Map from observer method name (i.e. 'onProxySettingsChanged',
+  // 'onBadProxiesChanged') to the previously received data for that type. Used
+  // to tell if the data has actually changed since we last polled it.
   this.prevPollData_ = {};
 }
 
@@ -144,10 +153,19 @@
   chrome.send('getBadProxies');
 };
 
+BrowserBridge.prototype.sendGetHostResolverCache = function() {
+  // The browser will call receivedHostResolverCache on completion.
+  chrome.send('getHostResolverCache');
+};
+
 BrowserBridge.prototype.sendClearBadProxies = function() {
   chrome.send('clearBadProxies');
 };
 
+BrowserBridge.prototype.sendClearHostResolverCache = function() {
+  chrome.send('clearHostResolverCache');
+};
+
 //------------------------------------------------------------------------------
 // Messages received from the browser
 //------------------------------------------------------------------------------
@@ -161,11 +179,13 @@
   LogEventType = constantsMap;
 };
 
-BrowserBridge.prototype.receivedLogEventPhaseConstants = function(constantsMap) {
+BrowserBridge.prototype.receivedLogEventPhaseConstants =
+function(constantsMap) {
   LogEventPhase = constantsMap;
 };
 
-BrowserBridge.prototype.receivedLogSourceTypeConstants = function(constantsMap) {
+BrowserBridge.prototype.receivedLogSourceTypeConstants =
+function(constantsMap) {
   LogSourceType = constantsMap;
 };
 
@@ -187,6 +207,13 @@
       this.badProxiesObservers_, 'onBadProxiesChanged', badProxies);
 };
 
+BrowserBridge.prototype.receivedHostResolverCache =
+function(hostResolverCache) {
+  this.dispatchToObserversFromPoll_(
+      this.hostResolverCacheObservers_, 'onHostResolverCacheChanged',
+      hostResolverCache);
+};
+
 //------------------------------------------------------------------------------
 
 /**
@@ -228,6 +255,16 @@
 };
 
 /**
+ * Adds a listener of the host resolver cache. |observer| will be called back
+ * when data is received, through:
+ *
+ *   observer.onHostResolverCacheChanged(hostResolverCache)
+ */
+BrowserBridge.prototype.addHostResolverCacheObserver = function(observer) {
+  this.hostResolverCacheObservers_.push(observer);
+};
+
+/**
  * The browser gives us times in terms of "time ticks" in milliseconds.
  * This function converts the tick count to a Date() object.
  *
@@ -244,8 +281,12 @@
 };
 
 BrowserBridge.prototype.doPolling_ = function() {
+  // TODO(eroman): Optimize this by using a separate polling frequency for the
+  // data consumed by the currently active view. Everything else can be on a low
+  // frequency poll since it won't impact the display.
   this.sendGetProxySettings();
   this.sendGetBadProxies();
+  this.sendGetHostResolverCache();
 };
 
 /**
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index e73c611..7c328e8 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -3203,6 +3203,7 @@
           'destination': '<(PRODUCT_DIR)/resources/net_internals',
           'files': [
             'browser/resources/net_internals/detailsview.js',
+            'browser/resources/net_internals/dnsview.js',
             'browser/resources/net_internals/index.html',
             'browser/resources/net_internals/loggrouper.js',
             'browser/resources/net_internals/logviewpainter.js',