Sdch view for net-internals

BUG=

R=mmenke, rdsmith, jwd

TEST=NetInternalsTest.netInternalsTourTabs
     NetInternalsTest.netInternalsExport*/Import*
     NetInternalsTest.netInternalsSdchView*

Committed: https://crrev.com/fe89e5a5a23f3323201d3586b2ec77174f042158
Cr-Commit-Position: refs/heads/master@{#302546}

Review URL: https://codereview.chromium.org/423813002

Cr-Commit-Position: refs/heads/master@{#304159}
diff --git a/chrome/browser/net/chrome_sdch_policy.cc b/chrome/browser/net/chrome_sdch_policy.cc
index 16fdb20..8ab62b5 100644
--- a/chrome/browser/net/chrome_sdch_policy.cc
+++ b/chrome/browser/net/chrome_sdch_policy.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "net/base/sdch_manager.h"
+#include "net/base/sdch_net_log_params.h"
 
 ChromeSdchPolicy::ChromeSdchPolicy(net::SdchManager* sdch_manager,
                                    net::URLRequestContext* context)
@@ -24,8 +25,16 @@
 }
 
 void ChromeSdchPolicy::OnDictionaryFetched(const std::string& dictionary_text,
-                                           const GURL& dictionary_url) {
-  manager_->AddSdchDictionary(dictionary_text, dictionary_url);
+                                           const GURL& dictionary_url,
+                                           const net::BoundNetLog& net_log) {
+  net::SdchProblemCode rv =
+      manager_->AddSdchDictionary(dictionary_text, dictionary_url);
+  if (rv != net::SDCH_OK) {
+    net::SdchManager::SdchErrorRecovery(rv);
+    net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR,
+                     base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback,
+                                rv, dictionary_url, true));
+  }
 }
 
 void ChromeSdchPolicy::OnGetDictionary(net::SdchManager* manager,
diff --git a/chrome/browser/net/chrome_sdch_policy.h b/chrome/browser/net/chrome_sdch_policy.h
index 046eeef..5e24906 100644
--- a/chrome/browser/net/chrome_sdch_policy.h
+++ b/chrome/browser/net/chrome_sdch_policy.h
@@ -28,7 +28,8 @@
   ~ChromeSdchPolicy() override;
 
   void OnDictionaryFetched(const std::string& dictionary_text,
-                           const GURL& dictionary_url);
+                           const GURL& dictionary_url,
+                           const net::BoundNetLog& net_log);
 
   // SdchObserver implementation.
   void OnGetDictionary(net::SdchManager* manager,
diff --git a/chrome/browser/resources/net_internals/browser_bridge.js b/chrome/browser/resources/net_internals/browser_bridge.js
index 09ce829b..8a90b98 100644
--- a/chrome/browser/resources/net_internals/browser_bridge.js
+++ b/chrome/browser/resources/net_internals/browser_bridge.js
@@ -52,6 +52,7 @@
     this.addNetInfoPollableDataHelper('spdyAlternateProtocolMappings',
                                       'onSpdyAlternateProtocolMappingsChanged');
     this.addNetInfoPollableDataHelper('quicInfo', 'onQuicInfoChanged');
+    this.addNetInfoPollableDataHelper('sdchInfo', 'onSdchInfoChanged');
     this.addNetInfoPollableDataHelper('httpCacheInfo',
                                       'onHttpCacheInfoChanged');
 
@@ -631,6 +632,17 @@
     },
 
     /**
+     * Adds a listener of SDCH information. |observer| will be called
+     * back when data is received, through:
+     *
+     *   observer.onSdchInfoChanged(sdchInfo)
+     */
+    addSdchInfoObserver: function(observer, ignoreWhenUnchanged) {
+      this.pollableDataHelpers_.sdchInfo.addObserver(
+          observer, ignoreWhenUnchanged);
+    },
+
+    /**
      * If |force| is true, calls all startUpdate functions.  Otherwise, just
      * runs updates with active observers.
      */
diff --git a/chrome/browser/resources/net_internals/index.html b/chrome/browser/resources/net_internals/index.html
index db186f7..87800ef 100644
--- a/chrome/browser/resources/net_internals/index.html
+++ b/chrome/browser/resources/net_internals/index.html
@@ -46,6 +46,7 @@
       <include src="waterfall_view.html">
       <include src="timeline_view.html">
       <include src="logs_view.html">
+      <include src="sdch_view.html">
       <include src="chromeos_view.html">
       <include src="cros_log_visualizer_view.html">
     </div>
diff --git a/chrome/browser/resources/net_internals/index.js b/chrome/browser/resources/net_internals/index.js
index 990a773..5056732fb 100644
--- a/chrome/browser/resources/net_internals/index.js
+++ b/chrome/browser/resources/net_internals/index.js
@@ -49,6 +49,7 @@
 <include src="prerender_view.js">
 <include src="chromeos_view.js">
 <include src="bandwidth_view.js">
+<include src="sdch_view.js">
 <include src="cros_log_visualizer_view.js">
 <include src="cros_log_entry.js">
 <include src="cros_log_visualizer.js" >
diff --git a/chrome/browser/resources/net_internals/log_view_painter.js b/chrome/browser/resources/net_internals/log_view_painter.js
index 069a5a9..38b70f81 100644
--- a/chrome/browser/resources/net_internals/log_view_painter.js
+++ b/chrome/browser/resources/net_internals/log_view_painter.js
@@ -87,7 +87,7 @@
   }
 
   return tablePrinter;
-}
+};
 
 /**
  * Adds a new row to the given TablePrinter, and adds five cells containing
@@ -350,6 +350,12 @@
     return;
   }
 
+  if (key == 'sdch_problem_code' && typeof value == 'number') {
+    var valueStr = value + ' (' + sdchProblemCodeToString(value) + ')';
+    out.writeArrowKeyValue(key, valueStr);
+    return;
+  }
+
   // Otherwise just default to JSON formatting of the value.
   out.writeArrowKeyValue(key, JSON.stringify(value));
 }
@@ -558,7 +564,7 @@
 
   entry.params.headers = entry.params.headers.map(stripCookieOrLoginInfo);
   return entry;
-}
+};
 
 /**
  * Outputs the request header parameters of |entry| to |out|.
diff --git a/chrome/browser/resources/net_internals/main.js b/chrome/browser/resources/net_internals/main.js
index 05e9e7b..5975e83 100644
--- a/chrome/browser/resources/net_internals/main.js
+++ b/chrome/browser/resources/net_internals/main.js
@@ -21,6 +21,7 @@
 var CertStatusFlag = null;
 var LoadState = null;
 var AddressFamily = null;
+var SdchProblemCode = null;
 
 /**
  * Dictionary of all constants, used for saving log files.
@@ -192,6 +193,7 @@
       addTab(SocketsView);
       addTab(SpdyView);
       addTab(QuicView);
+      addTab(SdchView);
       addTab(HttpCacheView);
       addTab(ModulesView);
       addTab(TestView);
@@ -311,6 +313,7 @@
   QuicRstStreamError = Constants.quicRstStreamError;
   AddressFamily = Constants.addressFamily;
   LoadState = Constants.loadState;
+  SdchProblemCode = Constants.sdchProblemCode;
   // certStatusFlag may not be present when loading old log Files
   if (typeof(Constants.certStatusFlag) == 'object')
     CertStatusFlag = Constants.certStatusFlag;
@@ -386,3 +389,15 @@
   // Strip that prefix since it is redundant and only clutters the output.
   return str.replace(/^ADDRESS_FAMILY_/, '');
 }
+
+/**
+ * Returns the name for sdchProblemCode.
+ *
+ * Example: sdchProblemCodeToString(5) should return
+ * "DECODE_BODY_ERROR".
+ * @param {number} sdchProblemCode The SDCH problem code.
+ * @return {string} The name of the given problem code.
+ */
+function sdchProblemCodeToString(sdchProblemCode) {
+  return getKeyWithValue(SdchProblemCode, sdchProblemCode);
+}
diff --git a/chrome/browser/resources/net_internals/sdch_view.html b/chrome/browser/resources/net_internals/sdch_view.html
new file mode 100644
index 0000000..2cbfca5
--- /dev/null
+++ b/chrome/browser/resources/net_internals/sdch_view.html
@@ -0,0 +1,73 @@
+<div id=sdch-view-tab-content class=content-box>
+  <ul style="margin-top:0">
+    <li>SDCH Enabled:
+      <span jscontent="!!sdch_enabled" id=sdch-view-sdch-enabled></span>
+    </li>
+    <li>Secure Scheme Support Enabled:
+      <span jscontent="!!secure_scheme_support"
+            id=sdch-view-secure-scheme-support></span>
+    </li>
+  </ul>
+
+  <p>SDCH Errors:
+    <a href="#events&q=type:URL_REQUEST%20SDCH_DECODING_ERROR"
+       style="padding-right:2em">Decoding</a>
+    <a href="#events&q=type:URL_REQUEST%20SDCH_DICTIONARY_ERROR">Dictionary</a>
+  </p>
+
+  <p>
+    <a href="#events&q=type:URL_REQUEST%20SDCH_DICTIONARY_FETCH">
+      SDCH Dictionary Fetches
+    </a>
+  </p>
+
+  <p>
+    <a href="#events&q=type:URL_REQUEST%20Avail-Dictionary">
+      SDCH Requests
+    </a>
+  </p>
+
+  <h4>
+    Dictionaries loaded: <span jscontent="dictionaries.length"></span>
+  </h4>
+  <table class="styled-table">
+    <thead>
+      <tr>
+        <th>Domain</th>
+        <th>Path</th>
+        <th>Ports</th>
+        <th>Server Hash</th>
+        <th>Client Hash</th>
+        <th>Url</th>
+      </tr>
+    </thead>
+    <tbody id=sdch-view-dictionaries-body>
+      <tr jsselect="dictionaries">
+        <td jscontent="domain"></td>
+        <td jscontent="path"></td>
+        <td jscontent="$this.ports ? $this.ports.join(', ') : ''"></td>
+        <td jscontent="server_hash"></td>
+        <td jscontent="client_hash"></td>
+        <td jscontent="url"></td>
+      </tr>
+    </tbody>
+  </table>
+
+  <h4>Blacklisted domains</h4>
+  <table class="styled-table">
+    <thead>
+      <tr>
+        <th>Domain</th>
+        <th>Reason</th>
+        <th>Tries to back off</th>
+      </tr>
+    </thead>
+    <tbody id=sdch-view-blacklist-body>
+      <tr jsselect="blacklisted">
+        <td jscontent="domain"></td>
+        <td jscontent="sdchProblemCodeToString(reason)"></td>
+        <td jscontent="tries"></td>
+      </tr>
+    </tbody>
+  </table>
+</div>
diff --git a/chrome/browser/resources/net_internals/sdch_view.js b/chrome/browser/resources/net_internals/sdch_view.js
new file mode 100644
index 0000000..f42ec46
--- /dev/null
+++ b/chrome/browser/resources/net_internals/sdch_view.js
@@ -0,0 +1,60 @@
+// Copyright 2014 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 related to SDCH.
+ *
+ * Shows loaded dictionaries, blacklisted domains and SDCH errors.
+ */
+var SdchView = (function() {
+  'use strict';
+
+  // We inherit from DivView.
+  var superClass = DivView;
+
+  /**
+   * @constructor
+   */
+  function SdchView() {
+    assertFirstConstructorCall(SdchView);
+
+    // Call superclass's constructor.
+    superClass.call(this, SdchView.MAIN_BOX_ID);
+
+    // Register to receive changes to the SDCH info.
+    g_browser.addSdchInfoObserver(this, false);
+  }
+
+  SdchView.TAB_ID = 'tab-handle-sdch';
+  SdchView.TAB_NAME = 'SDCH';
+  SdchView.TAB_HASH = '#sdch';
+
+  // IDs for special HTML elements in sdch_view.html
+  SdchView.MAIN_BOX_ID = 'sdch-view-tab-content';
+  SdchView.SDCH_ENABLED_SPAN_ID = 'sdch-view-sdch-enabled';
+  SdchView.SECURE_SCHEME_SUPPORT_SPAN_ID = 'sdch-view-secure-scheme-support';
+  SdchView.BLACKLIST_TBODY_ID = 'sdch-view-blacklist-body';
+  SdchView.DICTIONARIES_TBODY_ID = 'sdch-view-dictionaries-body';
+
+  cr.addSingletonGetter(SdchView);
+
+  SdchView.prototype = {
+    // Inherit the superclass's methods.
+    __proto__: superClass.prototype,
+
+    onLoadLogFinish: function(data) {
+      return this.onSdchInfoChanged(data.sdchInfo);
+    },
+
+    onSdchInfoChanged: function(sdchInfo) {
+      if (!sdchInfo || typeof(sdchInfo.sdch_enabled) === 'undefined')
+        return false;
+      var input = new JsEvalContext(sdchInfo);
+      jstProcess(input, $(SdchView.MAIN_BOX_ID));
+      return true;
+    },
+  };
+
+  return SdchView;
+})();
diff --git a/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc b/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc
index b406128..6070db0 100644
--- a/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc
@@ -350,5 +350,10 @@
   if (test_server_started_)
     return true;
   test_server_started_ = test_server()->Start();
+
+  // Sample domain for SDCH-view test. Dictionaries for localhost/127.0.0.1
+  // are forbidden.
+  host_resolver()->AddRule("testdomain.com", "127.0.0.1");
+  host_resolver()->AddRule("sub.testdomain.com", "127.0.0.1");
   return test_server_started_;
 }
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index dba56c7..90b9fb7 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -797,6 +797,7 @@
       'test/data/webui/net_internals/test_view.js',
       'test/data/webui/net_internals/timeline_view.js',
       'test/data/webui/net_internals/waterfall_view.js',
+      'test/data/webui/net_internals/sdch_view.js',
       'test/data/webui/ntp4.js',
       'test/data/webui/ntp4_browsertest.cc',
       'test/data/webui/ntp4_browsertest.h',
diff --git a/chrome/test/data/sdch/base-page.html b/chrome/test/data/sdch/base-page.html
new file mode 100644
index 0000000..fadcc62
--- /dev/null
+++ b/chrome/test/data/sdch/base-page.html
@@ -0,0 +1,13 @@
+<html>
+<title>Base page containing iframe with SDCH-encoded content</title>
+<body>
+  <iframe></iframe>
+  <script>
+    var iframe = document.querySelector('iframe');
+    var re = /[&?]iframe_url=([^&?#]*)/;
+    var result = re.exec(document.location.search);
+    iframe.src = 'http://sub.testdomain.com:' + document.location.port +
+                 decodeURI(result[1]);
+  </script>
+</body>
+</html>
diff --git a/chrome/test/data/sdch/dict b/chrome/test/data/sdch/dict
new file mode 100644
index 0000000..6d5a4c8
--- /dev/null
+++ b/chrome/test/data/sdch/dict
@@ -0,0 +1,4 @@
+Domain: sub.testdomain.com
+Path: /
+
+This is a dictionary to test SDCH pages.
diff --git a/chrome/test/data/sdch/non-html b/chrome/test/data/sdch/non-html
new file mode 100644
index 0000000..37e4d21
--- /dev/null
+++ b/chrome/test/data/sdch/non-html
Binary files differ
diff --git a/chrome/test/data/sdch/non-html.mock-http-headers b/chrome/test/data/sdch/non-html.mock-http-headers
new file mode 100644
index 0000000..11453b9
--- /dev/null
+++ b/chrome/test/data/sdch/non-html.mock-http-headers
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Type: text/plain
+Content-Encoding: sdch
diff --git a/chrome/test/data/sdch/non-sdch.html b/chrome/test/data/sdch/non-sdch.html
new file mode 100644
index 0000000..668799d
--- /dev/null
+++ b/chrome/test/data/sdch/non-sdch.html
@@ -0,0 +1,4 @@
+<html>
+<title>This page is not SDCH-encoded although the server says it is</title>
+<body></body>
+</html>
diff --git a/chrome/test/data/sdch/non-sdch.html.mock-http-headers b/chrome/test/data/sdch/non-sdch.html.mock-http-headers
new file mode 100644
index 0000000..6a0d34a6
--- /dev/null
+++ b/chrome/test/data/sdch/non-sdch.html.mock-http-headers
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
+Content-Encoding: sdch
diff --git a/chrome/test/data/sdch/page.html b/chrome/test/data/sdch/page.html
new file mode 100644
index 0000000..7e9d75a
--- /dev/null
+++ b/chrome/test/data/sdch/page.html
@@ -0,0 +1,4 @@
+<html>
+<title>Just a page that will advertise SDCH dictionary</title>
+<body></body>
+</html>
diff --git a/chrome/test/data/sdch/page.html.mock-http-headers b/chrome/test/data/sdch/page.html.mock-http-headers
new file mode 100644
index 0000000..1ea90b8
--- /dev/null
+++ b/chrome/test/data/sdch/page.html.mock-http-headers
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
+Get-Dictionary: /files/sdch/dict
diff --git a/chrome/test/data/webui/net_internals/log_util.js b/chrome/test/data/webui/net_internals/log_util.js
index 43b32b4..02ba179 100644
--- a/chrome/test/data/webui/net_internals/log_util.js
+++ b/chrome/test/data/webui/net_internals/log_util.js
@@ -167,6 +167,7 @@
     sockets: true,
     spdy: true,
     quic: true,
+    sdch: true,
     httpCache: true,
     modules: true,
     tests: false,
@@ -198,6 +199,7 @@
     sockets: false,
     spdy: false,
     quic: false,
+    sdch: false,
     httpCache: false,
     modules: false,
     tests: false,
diff --git a/chrome/test/data/webui/net_internals/main.js b/chrome/test/data/webui/net_internals/main.js
index bc846cb..7966ce6 100644
--- a/chrome/test/data/webui/net_internals/main.js
+++ b/chrome/test/data/webui/net_internals/main.js
@@ -32,6 +32,7 @@
     sockets: true,
     spdy: true,
     quic: true,
+    sdch: true,
     httpCache: true,
     modules: true,
     tests: true,
diff --git a/chrome/test/data/webui/net_internals/net_internals_test.js b/chrome/test/data/webui/net_internals/net_internals_test.js
index 3417095..9fa8870 100644
--- a/chrome/test/data/webui/net_internals/net_internals_test.js
+++ b/chrome/test/data/webui/net_internals/net_internals_test.js
@@ -294,6 +294,7 @@
       sockets: SocketsView.TAB_ID,
       spdy: SpdyView.TAB_ID,
       quic: QuicView.TAB_ID,
+      sdch: SdchView.TAB_ID,
       httpCache: HttpCacheView.TAB_ID,
       modules: ModulesView.TAB_ID,
       tests: TestView.TAB_ID,
diff --git a/chrome/test/data/webui/net_internals/sdch_view.js b/chrome/test/data/webui/net_internals/sdch_view.js
new file mode 100644
index 0000000..cba9eed
--- /dev/null
+++ b/chrome/test/data/webui/net_internals/sdch_view.js
@@ -0,0 +1,223 @@
+// Copyright 2014 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 test fixture.
+GEN_INCLUDE(['net_internals_test.js']);
+
+// Anonymous namespace
+(function() {
+
+// Path to the page containing iframe. Iframe is used to load sdch-related
+// content from the different origin. Otherwise favicon requests for the main
+// page domain would spoil SDCH blacklists counters making test behavior hardly
+// predicatble.
+var BASE_PATH = 'files/sdch/base-page.html?iframe_url=';
+
+/**
+ * Checks the display on the SDCH tab against the information it should be
+ * displaying.
+ * @param {object} sdchInfo Results from a sdch manager info query.
+ */
+function checkDisplay(sdchInfo) {
+  expectEquals(sdchInfo.sdch_enabled,
+               $(SdchView.SDCH_ENABLED_SPAN_ID).innerText === 'true');
+  expectEquals(sdchInfo.secure_scheme_support,
+               $(SdchView.SECURE_SCHEME_SUPPORT_SPAN_ID).innerText === 'true');
+  NetInternalsTest.checkTbodyRows(SdchView.BLACKLIST_TBODY_ID,
+                                  sdchInfo.blacklisted.length);
+  NetInternalsTest.checkTbodyRows(SdchView.DICTIONARIES_TBODY_ID,
+                                  sdchInfo.dictionaries.length);
+
+  // Rather than check the exact string in every position, just make sure every
+  // entry does not have 'undefined' anywhere and certain entries are not empty,
+  // which should find a fair number of potential output errors.
+  for (var row = 0; row < sdchInfo.blacklisted.length; ++row) {
+    for (var column = 0; column < 3; ++column) {
+      var text = NetInternalsTest.getTbodyText(
+          SdchView.BLACKLIST_TBODY_ID, row, column);
+      expectNotEquals(text, '');
+      expectFalse(/undefined/i.test(text));
+    }
+  }
+
+
+  for (var row = 0; row < sdchInfo.dictionaries.length; ++row) {
+    for (var column = 0; column < 6; ++column) {
+      var text = NetInternalsTest.getTbodyText(
+          SdchView.DICTIONARIES_TBODY_ID, row, column);
+      expectFalse(/undefined/i.test(text));
+      if (column === 0) {
+        // At least Domain cell should not be empty.
+        expectNotEquals(text, '');
+      }
+    }
+  }
+}
+
+/**
+ * A Task that loads provided page and waits for the SDCH dictionary to be
+ * downloaded. The page response headers should provide Get-Dictionary header.
+ * @extends {NetInternalsTest.Task}
+ */
+function LoadSdchDictionaryTask() {
+  NetInternalsTest.Task.call(this);
+}
+
+LoadSdchDictionaryTask.prototype = {
+  __proto__: NetInternalsTest.Task.prototype,
+
+  /**
+   * Navigates to the page and starts waiting to receive the results from
+   * the browser process.
+   */
+  start: function(url) {
+    g_browser.addSdchInfoObserver(this, false)
+    NetInternalsTest.switchToView('sdch');
+    // 127.0.0.1 is not allowed to be an SDCH domain, use test domain.
+    url = url.replace('127.0.0.1', 'testdomain.com');
+    this.url_ = url;
+    chrome.send('loadPage', [url]);
+  },
+
+  /**
+   * Callback from the BrowserBridge. Checks if |sdchInfo| has the SDCH
+   * dictionary info for the dictionary the page has advertised. If so,
+   * validates it and completes the task.  If not, continues running.
+   * @param {object} sdchInfo Results of a SDCH manager info query.
+   */
+  onSdchInfoChanged: function(sdchInfo) {
+    if (this.isDone())
+      return;
+
+    checkDisplay(sdchInfo);
+
+    if (sdchInfo.dictionaries.length > 0) {
+      var testDict = sdchInfo.dictionaries.filter(function(dictionary) {
+        return dictionary.domain === 'sub.testdomain.com';
+      });
+      if (testDict.length === 0)
+        return;
+
+      expectEquals(1, testDict.length);
+      var dict = testDict[0];
+      expectEquals('/', dict.path);
+      expectTrue(dict.url.indexOf('/files/sdch/dict') !== -1);
+
+      var tableId = SdchView.DICTIONARIES_TBODY_ID;
+      var domain = NetInternalsTest.getTbodyText(tableId, 0, 0);
+      var path = NetInternalsTest.getTbodyText(tableId, 0, 1);
+      var url = NetInternalsTest.getTbodyText(tableId, 0, 5);
+
+      expectEquals(dict.domain, domain);
+      expectEquals(dict.path, path);
+      expectEquals(dict.url, url);
+
+      this.onTaskDone(this.url_);
+    }
+  }
+};
+
+/**
+ * A Task that loads provided page and waits for its domain to appear in SDCH
+ * blacklist with the specified reason.
+ * @param {string} reason Blacklist reason we're waiting for.
+ * @extends {NetInternalsTest.Task}
+ */
+function LoadPageWithDecodeErrorTask(reason) {
+  NetInternalsTest.Task.call(this);
+  this.reason_ = reason;
+}
+
+LoadPageWithDecodeErrorTask.prototype = {
+  __proto__: NetInternalsTest.Task.prototype,
+
+  /**
+   * Navigates to the page and starts waiting to receive the results from
+   * the browser process.
+   */
+  start: function(url) {
+    g_browser.addSdchInfoObserver(this, false)
+    NetInternalsTest.switchToView('sdch');
+    // 127.0.0.1 is not allowed to be an SDCH domain, so we need another one.
+    url = url.replace('127.0.0.1', 'testdomain.com');
+    chrome.send('loadPage', [url]);
+  },
+
+  /**
+   * Callback from the BrowserBridge. Checks if |sdchInfo.blacklisted| contains
+   * the test domain with the reason specified on creation. If so, validates it
+   * and completes the task.  If not, continues running.
+   * @param {object} sdchInfo Results of SDCH manager info query.
+   */
+  onSdchInfoChanged: function(sdchInfo) {
+    if (this.isDone())
+      return;
+
+    checkDisplay(sdchInfo);
+
+    if (sdchInfo.blacklisted.length > 0) {
+      var testDomains = sdchInfo.blacklisted.filter(function(entry) {
+        return entry.domain === 'sub.testdomain.com';
+      });
+      if (testDomains.length === 0)
+        return;
+
+      expectEquals(1, testDomains.length);
+      var entry = testDomains[0];
+      expectEquals(this.reason_, sdchProblemCodeToString(entry.reason));
+      var tableId = SdchView.BLACKLIST_TBODY_ID;
+      var domain = NetInternalsTest.getTbodyText(tableId, 0, 0);
+      var reason = NetInternalsTest.getTbodyText(tableId, 0, 1);
+      expectEquals(entry.domain, domain);
+      expectEquals(this.reason_, reason);
+      this.onTaskDone();
+    }
+  }
+};
+
+/**
+ * Load a page, which results in downloading a SDCH dictionary. Make sure its
+ * data is displayed.
+ */
+TEST_F('NetInternalsTest', 'netInternalsSdchViewFetchDictionary', function() {
+  var taskQueue = new NetInternalsTest.TaskQueue(true);
+  taskQueue.addTask(
+      new NetInternalsTest.GetTestServerURLTask(
+          BASE_PATH + encodeURI('/files/sdch/page.html')));
+  taskQueue.addTask(new LoadSdchDictionaryTask());
+  taskQueue.run();
+});
+
+/**
+ * Load a page, get the dictionary for it, and get decoding error to see
+ * the blacklist in action.
+ */
+TEST_F('NetInternalsTest', 'netInternalsSdchViewBlacklistMeta', function() {
+  var taskQueue = new NetInternalsTest.TaskQueue(true);
+  taskQueue.addTask(
+      new NetInternalsTest.GetTestServerURLTask(
+          BASE_PATH + encodeURI('/files/sdch/page.html')));
+  taskQueue.addTask(new LoadSdchDictionaryTask());
+  taskQueue.addTask(
+      new NetInternalsTest.GetTestServerURLTask(
+          BASE_PATH + encodeURI('/files/sdch/non-html')));
+  taskQueue.addTask(
+      new LoadPageWithDecodeErrorTask('META_REFRESH_UNSUPPORTED'));
+  taskQueue.run();
+});
+
+/**
+ * Load a page, which is said to be SDCH-encoded, though we don't expect it.
+ */
+TEST_F('NetInternalsTest', 'netInternalsSdchViewBlacklistNonSdch', function() {
+  var taskQueue = new NetInternalsTest.TaskQueue(true);
+  taskQueue.addTask(
+      new NetInternalsTest.GetTestServerURLTask(
+          BASE_PATH + encodeURI('/files/sdch/non-sdch.html')));
+  taskQueue.addTask(
+      new LoadPageWithDecodeErrorTask('PASSING_THROUGH_NON_SDCH'));
+  taskQueue.run();
+});
+
+})();  // Anonymous namespace
diff --git a/net/base/net_info_source_list.h b/net/base/net_info_source_list.h
index be3019e..107840b 100644
--- a/net/base/net_info_source_list.h
+++ b/net/base/net_info_source_list.h
@@ -20,3 +20,4 @@
 NET_INFO_SOURCE(SPDY_ALT_PROTO_MAPPINGS, "spdyAlternateProtocolMappings",
                                                                          1 << 7)
 NET_INFO_SOURCE(HTTP_CACHE, "httpCacheInfo",                             1 << 8)
+NET_INFO_SOURCE(SDCH, "sdchInfo",                                        1 << 9)
diff --git a/net/base/net_log_event_type_list.h b/net/base/net_log_event_type_list.h
index 6dfe8c6..af7c290 100644
--- a/net/base/net_log_event_type_list.h
+++ b/net/base/net_log_event_type_list.h
@@ -2363,3 +2363,38 @@
 // This event is created (in a source of the same name) when the internal DNS
 // resolver creates a UDP socket to check for global IPv6 connectivity.
 EVENT_TYPE(IPV6_REACHABILITY_CHECK)
+
+// ------------------------------------------------------------------------
+// SDCH
+// ------------------------------------------------------------------------
+
+// This event is created when some problem occurs during sdch-encoded resource
+// handling. It contains the following parameters:
+//   {
+//     "sdch_problem_code": <SDCH problem code>,
+//     "net_error": <Always ERR_FAILED, present just to indicate this is a
+//                   failure>,
+//   }
+EVENT_TYPE(SDCH_DECODING_ERROR)
+
+// This event is created when SdchFilter initialization fails due to the
+// response corruption. It contains the following parameters:
+//   {
+//     "cause": <Response corruption detection cause>,
+//     "cached": <True if response was read from cache>,
+//   }
+EVENT_TYPE(SDCH_RESPONSE_CORRUPTION_DETECTION)
+
+// This event is created when some problem occurs during sdch dictionary fetch.
+// It contains the following parameters:
+//   {
+//     "dictionary_url": <Dictionary url>,
+//     "sdch_problem_code": <SDCH problem code>,
+//     "net_error": <Only present on unexpected errors. Always ERR_FAILED when
+//                   present. Used to indicate this is a real failure>,
+//   }
+EVENT_TYPE(SDCH_DICTIONARY_ERROR)
+
+// This event is created when SdchDictionaryFetcher starts fetch.  It contains
+// no parameters.
+EVENT_TYPE(SDCH_DICTIONARY_FETCH)
diff --git a/net/base/net_log_util.cc b/net/base/net_log_util.cc
index b8816ba..a15e8998c 100644
--- a/net/base/net_log_util.cc
+++ b/net/base/net_log_util.cc
@@ -15,6 +15,7 @@
 #include "net/base/load_states.h"
 #include "net/base/net_errors.h"
 #include "net/base/net_log.h"
+#include "net/base/sdch_manager.h"
 #include "net/disk_cache/disk_cache.h"
 #include "net/dns/host_cache.h"
 #include "net/dns/host_resolver.h"
@@ -66,6 +67,14 @@
 #undef NET_ERROR
 };
 
+const StringToConstant kSdchProblems[] = {
+#define SDCH_PROBLEM_CODE(label, value) \
+  { #label, value }                     \
+  ,
+#include "net/base/sdch_problem_code_list.h"
+#undef SDCH_PROBLEM_CODE
+};
+
 const char* NetInfoSourceToString(NetInfoSource source) {
   switch (source) {
     #define NET_INFO_SOURCE(label, string, value) \
@@ -187,6 +196,17 @@
     constants_dict->Set("quicRstStreamError", dict);
   }
 
+  // Add information on the relationship between SDCH problem codes and their
+  // symbolic names.
+  {
+    base::DictionaryValue* dict = new base::DictionaryValue();
+
+    for (size_t i = 0; i < arraysize(kSdchProblems); i++)
+      dict->SetInteger(kSdchProblems[i].name, kSdchProblems[i].constant);
+
+    constants_dict->Set("sdchProblemCode", dict);
+  }
+
   // Information about the relationship between event phase enums and their
   // symbolic names.
   {
@@ -455,6 +475,17 @@
                        info_dict);
   }
 
+  if (info_sources & NET_INFO_SDCH) {
+    base::Value* info_dict;
+    SdchManager* sdch_manager = context->sdch_manager();
+    if (sdch_manager) {
+      info_dict = sdch_manager->SdchInfoToValue();
+    } else {
+      info_dict = new base::DictionaryValue();
+    }
+    net_info_dict->Set(NetInfoSourceToString(NET_INFO_SDCH), info_dict);
+  }
+
   return net_info_dict.Pass();
 }
 
diff --git a/net/base/sdch_manager.cc b/net/base/sdch_manager.cc
index 1285ad9..af5e6ee3 100644
--- a/net/base/sdch_manager.cc
+++ b/net/base/sdch_manager.cc
@@ -9,6 +9,7 @@
 #include "base/metrics/histogram.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "base/values.h"
 #include "crypto/sha2.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "net/base/sdch_observer.h"
@@ -78,7 +79,8 @@
 SdchManager::Dictionary::~Dictionary() {
 }
 
-bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) {
+SdchProblemCode SdchManager::Dictionary::CanAdvertise(
+    const GURL& target_url) const {
   /* The specific rules of when a dictionary should be advertised in an
      Avail-Dictionary header are modeled after the rules for cookie scoping. The
      terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A
@@ -95,28 +97,28 @@
      url scheme.
     */
   if (!DomainMatch(target_url, domain_))
-    return false;
+    return SDCH_DICTIONARY_FOUND_HAS_WRONG_DOMAIN;
   if (!ports_.empty() && 0 == ports_.count(target_url.EffectiveIntPort()))
-    return false;
+    return SDCH_DICTIONARY_FOUND_HAS_WRONG_PORT_LIST;
   if (path_.size() && !PathMatch(target_url.path(), path_))
-    return false;
+    return SDCH_DICTIONARY_FOUND_HAS_WRONG_PATH;
   if (!SdchManager::secure_scheme_supported() && target_url.SchemeIsSecure())
-    return false;
+    return SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME;
   if (target_url.SchemeIsSecure() != url_.SchemeIsSecure())
-    return false;
+    return SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME;
   if (base::Time::Now() > expiration_)
-    return false;
-  return true;
+    return SDCH_DICTIONARY_FOUND_EXPIRED;
+  return SDCH_OK;
 }
 
 //------------------------------------------------------------------------------
 // Security functions restricting loads and use of dictionaries.
 
 // static
-bool SdchManager::Dictionary::CanSet(const std::string& domain,
-                                     const std::string& path,
-                                     const std::set<int>& ports,
-                                     const GURL& dictionary_url) {
+SdchProblemCode SdchManager::Dictionary::CanSet(const std::string& domain,
+                                                const std::string& path,
+                                                const std::set<int>& ports,
+                                                const GURL& dictionary_url) {
   /*
   A dictionary is invalid and must not be stored if any of the following are
   true:
@@ -135,20 +137,17 @@
   // and hence the conservative approach is to not allow any redirects (if there
   // were any... then don't allow the dictionary to be set).
 
-  if (domain.empty()) {
-    SdchErrorRecovery(DICTIONARY_MISSING_DOMAIN_SPECIFIER);
-    return false;  // Domain is required.
-  }
+  if (domain.empty())
+    return SDCH_DICTIONARY_MISSING_DOMAIN_SPECIFIER;  // Domain is required.
+
   if (registry_controlled_domains::GetDomainAndRegistry(
-        domain,
-        registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES).empty()) {
-    SdchErrorRecovery(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN);
-    return false;  // domain was a TLD.
+          domain, registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)
+          .empty()) {
+    return SDCH_DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN;  // domain was a TLD.
   }
-  if (!Dictionary::DomainMatch(dictionary_url, domain)) {
-    SdchErrorRecovery(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL);
-    return false;
-  }
+
+  if (!Dictionary::DomainMatch(dictionary_url, domain))
+    return SDCH_DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL;
 
   std::string referrer_url_host = dictionary_url.host();
   size_t postfix_domain_index = referrer_url_host.rfind(domain);
@@ -156,23 +155,20 @@
   if (referrer_url_host.size() == postfix_domain_index + domain.size()) {
     // It is a postfix... so check to see if there's a dot in the prefix.
     size_t end_of_host_index = referrer_url_host.find_first_of('.');
-    if (referrer_url_host.npos != end_of_host_index  &&
+    if (referrer_url_host.npos != end_of_host_index &&
         end_of_host_index < postfix_domain_index) {
-      SdchErrorRecovery(DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX);
-      return false;
+      return SDCH_DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX;
     }
   }
 
-  if (!ports.empty()
-      && 0 == ports.count(dictionary_url.EffectiveIntPort())) {
-    SdchErrorRecovery(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL);
-    return false;
-  }
-  return true;
+  if (!ports.empty() && 0 == ports.count(dictionary_url.EffectiveIntPort()))
+    return SDCH_DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL;
+
+  return SDCH_OK;
 }
 
-// static
-bool SdchManager::Dictionary::CanUse(const GURL& referring_url) {
+SdchProblemCode SdchManager::Dictionary::CanUse(
+    const GURL& referring_url) const {
   /*
     1. The request URL's host name domain-matches the Domain attribute of the
       dictionary.
@@ -184,39 +180,30 @@
     HTTPS support AND the dictionary acquisition scheme matches the target
      url scheme.
   */
-  if (!DomainMatch(referring_url, domain_)) {
-    SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_DOMAIN);
-    return false;
-  }
-  if (!ports_.empty()
-      && 0 == ports_.count(referring_url.EffectiveIntPort())) {
-    SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST);
-    return false;
-  }
-  if (path_.size() && !PathMatch(referring_url.path(), path_)) {
-    SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PATH);
-    return false;
-  }
-  if (!SdchManager::secure_scheme_supported() &&
-      referring_url.SchemeIsSecure()) {
-    SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME);
-    return false;
-  }
-  if (referring_url.SchemeIsSecure() != url_.SchemeIsSecure()) {
-    SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME);
-    return false;
-  }
+  if (!DomainMatch(referring_url, domain_))
+    return SDCH_DICTIONARY_FOUND_HAS_WRONG_DOMAIN;
+
+  if (!ports_.empty() && 0 == ports_.count(referring_url.EffectiveIntPort()))
+    return SDCH_DICTIONARY_FOUND_HAS_WRONG_PORT_LIST;
+
+  if (path_.size() && !PathMatch(referring_url.path(), path_))
+    return SDCH_DICTIONARY_FOUND_HAS_WRONG_PATH;
+
+  if (!SdchManager::secure_scheme_supported() && referring_url.SchemeIsSecure())
+    return SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME;
+
+  if (referring_url.SchemeIsSecure() != url_.SchemeIsSecure())
+    return SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME;
 
   // TODO(jar): Remove overly restrictive failsafe test (added per security
   // review) when we have a need to be more general.
-  if (!referring_url.SchemeIsHTTPOrHTTPS()) {
-    SdchErrorRecovery(ATTEMPT_TO_DECODE_NON_HTTP_DATA);
-    return false;
-  }
+  if (!referring_url.SchemeIsHTTPOrHTTPS())
+    return SDCH_ATTEMPT_TO_DECODE_NON_HTTP_DATA;
 
-  return true;
+  return SDCH_OK;
 }
 
+// static
 bool SdchManager::Dictionary::PathMatch(const std::string& path,
                                         const std::string& restriction) {
   /*  Must be either:
@@ -268,8 +255,9 @@
 }
 
 // static
-void SdchManager::SdchErrorRecovery(ProblemCodes problem) {
-  UMA_HISTOGRAM_ENUMERATION("Sdch3.ProblemCodes_4", problem, MAX_PROBLEM_CODE);
+void SdchManager::SdchErrorRecovery(SdchProblemCode problem) {
+  UMA_HISTOGRAM_ENUMERATION("Sdch3.ProblemCodes_5", problem,
+                            SDCH_MAX_PROBLEM_CODE);
 }
 
 // static
@@ -283,7 +271,7 @@
 }
 
 void SdchManager::BlacklistDomain(const GURL& url,
-                                  ProblemCodes blacklist_reason) {
+                                  SdchProblemCode blacklist_reason) {
   SetAllowLatencyExperiment(url, false);
 
   BlacklistInfo* blacklist_info =
@@ -304,7 +292,7 @@
 }
 
 void SdchManager::BlacklistDomainForever(const GURL& url,
-                                         ProblemCodes blacklist_reason) {
+                                         SdchProblemCode blacklist_reason) {
   SetAllowLatencyExperiment(url, false);
 
   BlacklistInfo* blacklist_info =
@@ -322,7 +310,7 @@
   BlacklistInfo* blacklist_info = &blacklisted_domains_[
       base::StringToLowerASCII(domain)];
   blacklist_info->count = 0;
-  blacklist_info->reason = MIN_PROBLEM_CODE;
+  blacklist_info->reason = SDCH_OK;
 }
 
 int SdchManager::BlackListDomainCount(const std::string& domain) {
@@ -341,49 +329,53 @@
   return blacklisted_domains_[domain_lower].exponential_count;
 }
 
-bool SdchManager::IsInSupportedDomain(const GURL& url) {
+SdchProblemCode SdchManager::IsInSupportedDomain(const GURL& url) {
   DCHECK(thread_checker_.CalledOnValidThread());
   if (!g_sdch_enabled_ )
-    return false;
+    return SDCH_DISABLED;
 
   if (!secure_scheme_supported() && url.SchemeIsSecure())
-    return false;
+    return SDCH_SECURE_SCHEME_NOT_SUPPORTED;
 
   if (blacklisted_domains_.empty())
-    return true;
+    return SDCH_OK;
 
   DomainBlacklistInfo::iterator it =
       blacklisted_domains_.find(base::StringToLowerASCII(url.host()));
   if (blacklisted_domains_.end() == it || it->second.count == 0)
-    return true;
+    return SDCH_OK;
 
   UMA_HISTOGRAM_ENUMERATION("Sdch3.BlacklistReason", it->second.reason,
-                            MAX_PROBLEM_CODE);
-  SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET);
+                            SDCH_MAX_PROBLEM_CODE);
 
   int count = it->second.count - 1;
   if (count > 0) {
     it->second.count = count;
   } else {
     it->second.count = 0;
-    it->second.reason = MIN_PROBLEM_CODE;
+    it->second.reason = SDCH_OK;
   }
 
-  return false;
+  return SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET;
 }
 
-void SdchManager::OnGetDictionary(const GURL& request_url,
-                                  const GURL& dictionary_url) {
-  if (!CanFetchDictionary(request_url, dictionary_url))
-    return;
+SdchProblemCode SdchManager::OnGetDictionary(const GURL& request_url,
+                                             const GURL& dictionary_url) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  SdchProblemCode rv = CanFetchDictionary(request_url, dictionary_url);
+  if (rv != SDCH_OK)
+    return rv;
 
   FOR_EACH_OBSERVER(SdchObserver,
                     observers_,
                     OnGetDictionary(this, request_url, dictionary_url));
+
+  return SDCH_OK;
 }
 
-bool SdchManager::CanFetchDictionary(const GURL& referring_url,
-                                     const GURL& dictionary_url) const {
+SdchProblemCode SdchManager::CanFetchDictionary(
+    const GURL& referring_url,
+    const GURL& dictionary_url) const {
   DCHECK(thread_checker_.CalledOnValidThread());
   /* The user agent may retrieve a dictionary from the dictionary URL if all of
      the following are true:
@@ -397,41 +389,40 @@
   // Item (1) above implies item (2).  Spec should be updated.
   // I take "host name match" to be "is identical to"
   if (referring_url.host() != dictionary_url.host() ||
-      referring_url.scheme() != dictionary_url.scheme()) {
-    SdchErrorRecovery(DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST);
-    return false;
-  }
-  if (!secure_scheme_supported() && referring_url.SchemeIsSecure()) {
-    SdchErrorRecovery(DICTIONARY_SELECTED_FOR_SSL);
-    return false;
-  }
+      referring_url.scheme() != dictionary_url.scheme())
+    return SDCH_DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST;
+
+  if (!secure_scheme_supported() && referring_url.SchemeIsSecure())
+    return SDCH_DICTIONARY_SELECTED_FOR_SSL;
 
   // TODO(jar): Remove this failsafe conservative hack which is more restrictive
   // than current SDCH spec when needed, and justified by security audit.
-  if (!referring_url.SchemeIsHTTPOrHTTPS()) {
-    SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP);
-    return false;
-  }
+  if (!referring_url.SchemeIsHTTPOrHTTPS())
+    return SDCH_DICTIONARY_SELECTED_FROM_NON_HTTP;
 
-  return true;
+  return SDCH_OK;
 }
 
-void SdchManager::GetVcdiffDictionary(
+SdchProblemCode SdchManager::GetVcdiffDictionary(
     const std::string& server_hash,
     const GURL& referring_url,
     scoped_refptr<Dictionary>* dictionary) {
   DCHECK(thread_checker_.CalledOnValidThread());
   *dictionary = NULL;
   DictionaryMap::iterator it = dictionaries_.find(server_hash);
-  if (it == dictionaries_.end()) {
-    return;
-  }
+  if (it == dictionaries_.end())
+    return SDCH_DICTIONARY_HASH_NOT_FOUND;
+
   scoped_refptr<Dictionary> matching_dictionary = it->second;
-  if (!IsInSupportedDomain(referring_url))
-    return;
-  if (!matching_dictionary->CanUse(referring_url))
-    return;
-  *dictionary = matching_dictionary;
+
+  SdchProblemCode rv = IsInSupportedDomain(referring_url);
+  if (rv != SDCH_OK)
+    return rv;
+
+  rv = matching_dictionary->CanUse(referring_url);
+  if (rv == SDCH_OK)
+    *dictionary = matching_dictionary;
+  return rv;
 }
 
 // TODO(jar): If we have evictions from the dictionaries_, then we need to
@@ -443,9 +434,11 @@
   int count = 0;
   for (DictionaryMap::iterator it = dictionaries_.begin();
        it != dictionaries_.end(); ++it) {
-    if (!IsInSupportedDomain(target_url))
+    SdchProblemCode rv = IsInSupportedDomain(target_url);
+    if (rv != SDCH_OK)
       continue;
-    if (!it->second->CanAdvertise(target_url))
+
+    if (it->second->CanAdvertise(target_url) != SDCH_OK)
       continue;
     ++count;
     if (!list->empty())
@@ -490,7 +483,7 @@
   ExperimentSet::iterator it = allow_latency_experiment_.find(url.host());
   if (allow_latency_experiment_.end() == it)
     return;  // It was already erased, or never allowed.
-  SdchErrorRecovery(LATENCY_TEST_DISALLOWED);
+  SdchErrorRecovery(SDCH_LATENCY_TEST_DISALLOWED);
   allow_latency_experiment_.erase(it);
 }
 
@@ -502,31 +495,27 @@
   observers_.RemoveObserver(observer);
 }
 
-void SdchManager::AddSdchDictionary(const std::string& dictionary_text,
+SdchProblemCode SdchManager::AddSdchDictionary(
+    const std::string& dictionary_text,
     const GURL& dictionary_url) {
   DCHECK(thread_checker_.CalledOnValidThread());
   std::string client_hash;
   std::string server_hash;
   GenerateHash(dictionary_text, &client_hash, &server_hash);
-  if (dictionaries_.find(server_hash) != dictionaries_.end()) {
-    SdchErrorRecovery(DICTIONARY_ALREADY_LOADED);
-    return;                             // Already loaded.
-  }
+  if (dictionaries_.find(server_hash) != dictionaries_.end())
+    return SDCH_DICTIONARY_ALREADY_LOADED;  // Already loaded.
 
   std::string domain, path;
   std::set<int> ports;
   base::Time expiration(base::Time::Now() + base::TimeDelta::FromDays(30));
 
-  if (dictionary_text.empty()) {
-    SdchErrorRecovery(DICTIONARY_HAS_NO_TEXT);
-    return;                             // Missing header.
-  }
+  if (dictionary_text.empty())
+    return SDCH_DICTIONARY_HAS_NO_TEXT;  // Missing header.
 
   size_t header_end = dictionary_text.find("\n\n");
-  if (std::string::npos == header_end) {
-    SdchErrorRecovery(DICTIONARY_HAS_NO_HEADER);
-    return;                             // Missing header.
-  }
+  if (std::string::npos == header_end)
+    return SDCH_DICTIONARY_HAS_NO_HEADER;  // Missing header.
+
   size_t line_start = 0;  // Start of line being parsed.
   while (1) {
     size_t line_end = dictionary_text.find('\n', line_start);
@@ -534,10 +523,9 @@
     DCHECK_LE(line_end, header_end);
 
     size_t colon_index = dictionary_text.find(':', line_start);
-    if (std::string::npos == colon_index) {
-      SdchErrorRecovery(DICTIONARY_HEADER_LINE_MISSING_COLON);
-      return;                         // Illegal line missing a colon.
-    }
+    if (std::string::npos == colon_index)
+      return SDCH_DICTIONARY_HEADER_LINE_MISSING_COLON;  // Illegal line missing
+                                                         // a colon.
 
     if (colon_index > line_end)
       break;
@@ -556,7 +544,7 @@
         path = value;
       } else if (name == "format-version") {
         if (value != "1.0")
-          return;
+          return SDCH_DICTIONARY_UNSUPPORTED_VERSION;
       } else if (name == "max-age") {
         int64 seconds;
         base::StringToInt64(value, &seconds);
@@ -578,24 +566,23 @@
   GURL dictionary_url_normalized(dictionary_url);
   StripTrailingDot(&dictionary_url_normalized);
 
-  if (!IsInSupportedDomain(dictionary_url_normalized))
-    return;
+  SdchProblemCode rv = IsInSupportedDomain(dictionary_url_normalized);
+  if (rv != SDCH_OK)
+    return rv;
 
-  if (!Dictionary::CanSet(domain, path, ports, dictionary_url_normalized))
-    return;
+  rv = Dictionary::CanSet(domain, path, ports, dictionary_url_normalized);
+  if (rv != SDCH_OK)
+    return rv;
 
   // TODO(jar): Remove these hacks to preclude a DOS attack involving piles of
   // useless dictionaries.  We should probably have a cache eviction plan,
   // instead of just blocking additions.  For now, with the spec in flux, it
   // is probably not worth doing eviction handling.
-  if (kMaxDictionarySize < dictionary_text.size()) {
-    SdchErrorRecovery(DICTIONARY_IS_TOO_LARGE);
-    return;
-  }
-  if (kMaxDictionaryCount <= dictionaries_.size()) {
-    SdchErrorRecovery(DICTIONARY_COUNT_EXCEEDED);
-    return;
-  }
+  if (kMaxDictionarySize < dictionary_text.size())
+    return SDCH_DICTIONARY_IS_TOO_LARGE;
+
+  if (kMaxDictionaryCount <= dictionaries_.size())
+    return SDCH_DICTIONARY_COUNT_EXCEEDED;
 
   UMA_HISTOGRAM_COUNTS("Sdch3.Dictionary size loaded", dictionary_text.size());
   DVLOG(1) << "Loaded dictionary with client hash " << client_hash
@@ -605,7 +592,7 @@
                      dictionary_url_normalized, domain,
                      path, expiration, ports);
   dictionaries_[server_hash] = dictionary;
-  return;
+  return SDCH_OK;
 }
 
 // static
@@ -618,4 +605,46 @@
   std::replace(output->begin(), output->end(), '/', '_');
 }
 
+base::Value* SdchManager::SdchInfoToValue() const {
+  base::DictionaryValue* value = new base::DictionaryValue();
+
+  value->SetBoolean("sdch_enabled", sdch_enabled());
+  value->SetBoolean("secure_scheme_support", secure_scheme_supported());
+
+  base::ListValue* entry_list = new base::ListValue();
+  for (DictionaryMap::const_iterator it = dictionaries_.begin();
+       it != dictionaries_.end(); ++it) {
+    base::DictionaryValue* entry_dict = new base::DictionaryValue();
+    entry_dict->SetString("url", it->second->url().spec());
+    entry_dict->SetString("client_hash", it->second->client_hash());
+    entry_dict->SetString("domain", it->second->domain());
+    entry_dict->SetString("path", it->second->path());
+    base::ListValue* port_list = new base::ListValue();
+    for (std::set<int>::const_iterator port_it = it->second->ports().begin();
+         port_it != it->second->ports().end(); ++port_it) {
+      port_list->AppendInteger(*port_it);
+    }
+    entry_dict->Set("ports", port_list);
+    entry_dict->SetString("server_hash", it->first);
+    entry_list->Append(entry_dict);
+  }
+  value->Set("dictionaries", entry_list);
+
+  entry_list = new base::ListValue();
+  for (DomainBlacklistInfo::const_iterator it = blacklisted_domains_.begin();
+       it != blacklisted_domains_.end(); ++it) {
+    if (it->second.count == 0)
+      continue;
+    base::DictionaryValue* entry_dict = new base::DictionaryValue();
+    entry_dict->SetString("domain", it->first);
+    if (it->second.count != INT_MAX)
+      entry_dict->SetInteger("tries", it->second.count);
+    entry_dict->SetInteger("reason", it->second.reason);
+    entry_list->Append(entry_dict);
+  }
+  value->Set("blacklisted", entry_list);
+
+  return value;
+}
+
 }  // namespace net
diff --git a/net/base/sdch_manager.h b/net/base/sdch_manager.h
index ff157e5..016978a 100644
--- a/net/base/sdch_manager.h
+++ b/net/base/sdch_manager.h
@@ -16,8 +16,13 @@
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
 #include "net/base/net_export.h"
+#include "net/base/sdch_problem_codes.h"
 #include "url/gurl.h"
 
+namespace base {
+class Value;
+}
+
 namespace net {
 
 class SdchObserver;
@@ -35,106 +40,6 @@
 // module) to decompress data.
 class NET_EXPORT SdchManager {
  public:
-  // A list of errors that appeared and were either resolved, or used to turn
-  // off sdch encoding.
-  enum ProblemCodes {
-    MIN_PROBLEM_CODE,
-
-    // Content-encoding correction problems.
-    ADDED_CONTENT_ENCODING = 1,
-    FIXED_CONTENT_ENCODING = 2,
-    FIXED_CONTENT_ENCODINGS = 3,
-
-    // Content decoding errors.
-    DECODE_HEADER_ERROR = 4,
-    DECODE_BODY_ERROR = 5,
-
-    // More content-encoding correction problems.
-    OPTIONAL_GUNZIP_ENCODING_ADDED = 6,
-
-    // Content encoding correction when we're not even tagged as HTML!?!
-    BINARY_ADDED_CONTENT_ENCODING = 7,
-    BINARY_FIXED_CONTENT_ENCODING = 8,
-    BINARY_FIXED_CONTENT_ENCODINGS = 9,
-
-    // Dictionary selection for use problems.
-    DICTIONARY_FOUND_HAS_WRONG_DOMAIN = 10,
-    DICTIONARY_FOUND_HAS_WRONG_PORT_LIST = 11,
-    DICTIONARY_FOUND_HAS_WRONG_PATH = 12,
-    DICTIONARY_FOUND_HAS_WRONG_SCHEME = 13,
-    DICTIONARY_HASH_NOT_FOUND = 14,
-    DICTIONARY_HASH_MALFORMED = 15,
-
-    // Dictionary saving problems.
-    DICTIONARY_HAS_NO_HEADER = 20,
-    DICTIONARY_HEADER_LINE_MISSING_COLON = 21,
-    DICTIONARY_MISSING_DOMAIN_SPECIFIER = 22,
-    DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN = 23,
-    DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL = 24,
-    DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL = 25,
-    DICTIONARY_HAS_NO_TEXT = 26,
-    DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX = 27,
-
-    // Dictionary loading problems.
-    DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST = 30,
-    DICTIONARY_SELECTED_FOR_SSL = 31,
-    DICTIONARY_ALREADY_LOADED = 32,
-    DICTIONARY_SELECTED_FROM_NON_HTTP = 33,
-    DICTIONARY_IS_TOO_LARGE= 34,
-    DICTIONARY_COUNT_EXCEEDED = 35,
-    DICTIONARY_ALREADY_SCHEDULED_TO_DOWNLOAD = 36,
-    DICTIONARY_ALREADY_TRIED_TO_DOWNLOAD = 37,
-    DICTIONARY_FETCH_READ_FAILED = 38,
-
-    // Failsafe hack.
-    ATTEMPT_TO_DECODE_NON_HTTP_DATA = 40,
-
-
-    // Content-Encoding problems detected, with no action taken.
-    MULTIENCODING_FOR_NON_SDCH_REQUEST = 50,
-    SDCH_CONTENT_ENCODE_FOR_NON_SDCH_REQUEST = 51,
-
-    // Dictionary manager issues.
-    DOMAIN_BLACKLIST_INCLUDES_TARGET = 61,
-
-    // Problematic decode recovery methods.
-    META_REFRESH_RECOVERY = 70,            // Dictionary not found.
-    // defunct =  71, // Almost the same as META_REFRESH_UNSUPPORTED.
-    // defunct = 72,  // Almost the same as CACHED_META_REFRESH_UNSUPPORTED.
-    // defunct = 73,  // PASSING_THROUGH_NON_SDCH plus
-                      // RESPONSE_TENTATIVE_SDCH in ../filter/sdch_filter.cc.
-    META_REFRESH_UNSUPPORTED = 74,         // Unrecoverable error.
-    CACHED_META_REFRESH_UNSUPPORTED = 75,  // As above, but pulled from cache.
-    PASSING_THROUGH_NON_SDCH = 76,  // Tagged sdch but missing dictionary-hash.
-    INCOMPLETE_SDCH_CONTENT = 77,   // Last window was not completely decoded.
-    PASS_THROUGH_404_CODE = 78,     // URL not found message passing through.
-
-    // This next report is very common, and not really an error scenario, but
-    // it exercises the error recovery logic.
-    PASS_THROUGH_OLD_CACHED = 79,   // Back button got pre-SDCH cached content.
-
-    // Common decoded recovery methods.
-    META_REFRESH_CACHED_RECOVERY = 80,  // Probably startup tab loading.
-    // defunct = 81, // Now tracked by ResponseCorruptionDetectionCause histo.
-
-    // Non SDCH problems, only accounted for to make stat counting complete
-    // (i.e., be able to be sure all dictionary advertisements are accounted
-    // for).
-
-    UNFLUSHED_CONTENT = 90,    // Possible error in filter chaining.
-    // defunct = 91,           // MISSING_TIME_STATS (Should never happen.)
-    CACHE_DECODED = 92,        // No timing stats recorded.
-    // defunct = 93,           // OVER_10_MINUTES (No timing stats recorded.)
-    UNINITIALIZED = 94,        // Filter never even got initialized.
-    PRIOR_TO_DICTIONARY = 95,  // We hadn't even parsed a dictionary selector.
-    DECODE_ERROR = 96,         // Something went wrong during decode.
-
-    // Problem during the latency test.
-    LATENCY_TEST_DISALLOWED = 100,  // SDCH now failing, but it worked before!
-
-    MAX_PROBLEM_CODE  // Used to bound histogram.
-  };
-
   // Use the following static limits to block DOS attacks until we implement
   // a cached dictionary evicition strategy.
   static const size_t kMaxDictionarySize;
@@ -167,19 +72,25 @@
 
     const GURL& url() const { return url_; }
     const std::string& client_hash() const { return client_hash_; }
+    const std::string& domain() const { return domain_; }
+    const std::string& path() const { return path_; }
+    const base::Time& expiration() const { return expiration_; }
+    const std::set<int>& ports() const { return ports_; }
 
     // Security method to check if we can advertise this dictionary for use
     // if the |target_url| returns SDCH compressed data.
-    bool CanAdvertise(const GURL& target_url);
+    SdchProblemCode CanAdvertise(const GURL& target_url) const;
 
     // Security methods to check if we can establish a new dictionary with the
     // given data, that arrived in response to get of dictionary_url.
-    static bool CanSet(const std::string& domain, const std::string& path,
-                       const std::set<int>& ports, const GURL& dictionary_url);
+    static SdchProblemCode CanSet(const std::string& domain,
+                                  const std::string& path,
+                                  const std::set<int>& ports,
+                                  const GURL& dictionary_url);
 
     // Security method to check if we can use a dictionary to decompress a
     // target that arrived with a reference to this dictionary.
-    bool CanUse(const GURL& referring_url);
+    SdchProblemCode CanUse(const GURL& referring_url) const;
 
     // Compare paths to see if they "match" for dictionary use.
     static bool PathMatch(const std::string& path,
@@ -188,7 +99,6 @@
     // Compare domains to see if the "match" for dictionary use.
     static bool DomainMatch(const GURL& url, const std::string& restriction);
 
-
     // The actual text of the dictionary.
     std::string text_;
 
@@ -218,7 +128,7 @@
   void ClearData();
 
   // Record stats on various errors.
-  static void SdchErrorRecovery(ProblemCodes problem);
+  static void SdchErrorRecovery(SdchProblemCode problem);
 
   // Enables or disables SDCH compression.
   static void EnableSdchSupport(bool enabled);
@@ -237,11 +147,12 @@
   // Used when filter errors are found from a given domain, but it is plausible
   // that the cause is temporary (such as application startup, where cached
   // entries are used, but a dictionary is not yet loaded).
-  void BlacklistDomain(const GURL& url, ProblemCodes blacklist_reason);
+  void BlacklistDomain(const GURL& url, SdchProblemCode blacklist_reason);
 
   // Used when SEVERE filter errors are found from a given domain, to prevent
   // further use of SDCH on that domain.
-  void BlacklistDomainForever(const GURL& url, ProblemCodes blacklist_reason);
+  void BlacklistDomainForever(const GURL& url,
+                              SdchProblemCode blacklist_reason);
 
   // Unit test only, this function resets enabling of sdch, and clears the
   // blacklist.
@@ -260,11 +171,12 @@
   // supported domain (i.e., not blacklisted, and either the specific supported
   // domain, or all domains were assumed supported). If it is blacklist, reduce
   // by 1 the number of times it will be reported as blacklisted.
-  bool IsInSupportedDomain(const GURL& url);
+  SdchProblemCode IsInSupportedDomain(const GURL& url);
 
   // Send out appropriate events notifying observers that a Get-Dictionary
   // header has been seen.
-  void OnGetDictionary(const GURL& request_url, const GURL& dictionary_url);
+  SdchProblemCode OnGetDictionary(const GURL& request_url,
+                                  const GURL& dictionary_url);
 
   // Find the vcdiff dictionary (the body of the sdch dictionary that appears
   // after the meta-data headers like Domain:...) with the given |server_hash|
@@ -272,9 +184,12 @@
   // be sure the returned |dictionary| can be used for decoding content supplied
   // in response to a request for |referring_url|.
   // Return null in |dictionary| if there is no matching legal dictionary.
-  void GetVcdiffDictionary(const std::string& server_hash,
-                           const GURL& referring_url,
-                           scoped_refptr<Dictionary>* dictionary);
+  // Returns SDCH_OK if dictionary is not found, SDCH(-over-https) is disabled,
+  // or if matching legal dictionary exists. Otherwise returns the
+  // corresponding problem code.
+  SdchProblemCode GetVcdiffDictionary(const std::string& server_hash,
+                                      const GURL& referring_url,
+                                      scoped_refptr<Dictionary>* dictionary);
 
   // Get list of available (pre-cached) dictionaries that we have already loaded
   // into memory. The list is a comma separated list of (client) hashes per
@@ -295,12 +210,16 @@
 
   void SetAllowLatencyExperiment(const GURL& url, bool enable);
 
+  base::Value* SdchInfoToValue() const;
+
   // Add an SDCH dictionary to our list of availible
   // dictionaries. This addition will fail if addition is illegal
   // (data in the dictionary is not acceptable from the
   // dictionary_url; dictionary already added, etc.).
-  void AddSdchDictionary(const std::string& dictionary_text,
-                         const GURL& dictionary_url);
+  // Returns SDCH_OK if the addition was successfull, and corresponding error
+  // code otherwise.
+  SdchProblemCode AddSdchDictionary(const std::string& dictionary_text,
+                                    const GURL& dictionary_url);
 
   // Registration for events generated by the SDCH subsystem.
   void AddObserver(SdchObserver* observer);
@@ -308,24 +227,20 @@
 
  private:
   struct BlacklistInfo {
-    BlacklistInfo()
-        : count(0),
-          exponential_count(0),
-          reason(MIN_PROBLEM_CODE) {}
+    BlacklistInfo() : count(0), exponential_count(0), reason(SDCH_OK) {}
 
-    int count;                   // # of times to refuse SDCH advertisement.
-    int exponential_count;       // Current exponential backoff ratchet.
-    ProblemCodes reason;         // Why domain was blacklisted.
-
+    int count;               // # of times to refuse SDCH advertisement.
+    int exponential_count;   // Current exponential backoff ratchet.
+    SdchProblemCode reason;  // Why domain was blacklisted.
   };
   typedef std::map<std::string, BlacklistInfo> DomainBlacklistInfo;
   typedef std::set<std::string> ExperimentSet;
 
   // Determines whether a "Get-Dictionary" header is legal (dictionary
   // url has appropriate relationship to referrer url) in the SDCH
-  // protocol.  Return true if fetch is legal.
-  bool CanFetchDictionary(const GURL& referring_url,
-                          const GURL& dictionary_url) const;
+  // protocol.  Return SDCH_OK if fetch is legal.
+  SdchProblemCode CanFetchDictionary(const GURL& referring_url,
+                                     const GURL& dictionary_url) const;
 
   // A map of dictionaries info indexed by the hash that the server provides.
   typedef std::map<std::string, scoped_refptr<Dictionary> > DictionaryMap;
@@ -340,6 +255,7 @@
   // A simple implementation of a RFC 3548 "URL safe" base64 encoder.
   static void UrlSafeBase64Encode(const std::string& input,
                                   std::string* output);
+
   DictionaryMap dictionaries_;
 
   // List domains where decode failures have required disabling sdch.
diff --git a/net/base/sdch_manager_unittest.cc b/net/base/sdch_manager_unittest.cc
index d986879..c20be58 100644
--- a/net/base/sdch_manager_unittest.cc
+++ b/net/base/sdch_manager_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/logging.h"
 #include "base/memory/scoped_ptr.h"
+#include "net/base/net_log.h"
 #include "net/base/sdch_manager.h"
 #include "net/base/sdch_observer.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -73,14 +74,7 @@
   // failure.
   bool AddSdchDictionary(const std::string& dictionary_text,
                          const GURL& gurl) {
-    std::string list;
-    sdch_manager_->GetAvailDictionaryList(gurl, &list);
-    sdch_manager_->AddSdchDictionary(dictionary_text, gurl);
-    std::string list2;
-    sdch_manager_->GetAvailDictionaryList(gurl, &list2);
-
-    // The list of hashes should change iff the addition succeeds.
-    return (list != list2);
+    return sdch_manager_->AddSdchDictionary(dictionary_text, gurl) == SDCH_OK;
   }
 
  private:
@@ -105,31 +99,34 @@
   GURL google_url("http://www.google.com");
 
   SdchManager::EnableSdchSupport(false);
-  EXPECT_FALSE(sdch_manager()->IsInSupportedDomain(google_url));
+  EXPECT_EQ(SDCH_DISABLED, sdch_manager()->IsInSupportedDomain(google_url));
   SdchManager::EnableSdchSupport(true);
-  EXPECT_TRUE(sdch_manager()->IsInSupportedDomain(google_url));
+  EXPECT_EQ(SDCH_OK, sdch_manager()->IsInSupportedDomain(google_url));
 }
 
 TEST_F(SdchManagerTest, DomainBlacklisting) {
   GURL test_url("http://www.test.com");
   GURL google_url("http://www.google.com");
 
-  sdch_manager()->BlacklistDomain(test_url, SdchManager::MIN_PROBLEM_CODE);
-  EXPECT_FALSE(sdch_manager()->IsInSupportedDomain(test_url));
-  EXPECT_TRUE(sdch_manager()->IsInSupportedDomain(google_url));
+  sdch_manager()->BlacklistDomain(test_url, SDCH_OK);
+  EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET,
+            sdch_manager()->IsInSupportedDomain(test_url));
+  EXPECT_EQ(SDCH_OK, sdch_manager()->IsInSupportedDomain(google_url));
 
-  sdch_manager()->BlacklistDomain(google_url, SdchManager::MIN_PROBLEM_CODE);
-  EXPECT_FALSE(sdch_manager()->IsInSupportedDomain(google_url));
+  sdch_manager()->BlacklistDomain(google_url, SDCH_OK);
+  EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET,
+            sdch_manager()->IsInSupportedDomain(google_url));
 }
 
 TEST_F(SdchManagerTest, DomainBlacklistingCaseSensitivity) {
   GURL test_url("http://www.TesT.com");
   GURL test2_url("http://www.tEst.com");
 
-  EXPECT_TRUE(sdch_manager()->IsInSupportedDomain(test_url));
-  EXPECT_TRUE(sdch_manager()->IsInSupportedDomain(test2_url));
-  sdch_manager()->BlacklistDomain(test_url, SdchManager::MIN_PROBLEM_CODE);
-  EXPECT_FALSE(sdch_manager()->IsInSupportedDomain(test2_url));
+  EXPECT_EQ(SDCH_OK, sdch_manager()->IsInSupportedDomain(test_url));
+  EXPECT_EQ(SDCH_OK, sdch_manager()->IsInSupportedDomain(test2_url));
+  sdch_manager()->BlacklistDomain(test_url, SDCH_OK);
+  EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET,
+            sdch_manager()->IsInSupportedDomain(test2_url));
 }
 
 TEST_F(SdchManagerTest, BlacklistingReset) {
@@ -139,7 +136,7 @@
   sdch_manager()->ClearBlacklistings();
   EXPECT_EQ(sdch_manager()->BlackListDomainCount(domain), 0);
   EXPECT_EQ(sdch_manager()->BlacklistDomainExponential(domain), 0);
-  EXPECT_TRUE(sdch_manager()->IsInSupportedDomain(gurl));
+  EXPECT_EQ(SDCH_OK, sdch_manager()->IsInSupportedDomain(gurl));
 }
 
 TEST_F(SdchManagerTest, BlacklistingSingleBlacklist) {
@@ -147,14 +144,15 @@
   std::string domain(gurl.host());
   sdch_manager()->ClearBlacklistings();
 
-  sdch_manager()->BlacklistDomain(gurl, SdchManager::MIN_PROBLEM_CODE);
+  sdch_manager()->BlacklistDomain(gurl, SDCH_OK);
   EXPECT_EQ(sdch_manager()->BlackListDomainCount(domain), 1);
   EXPECT_EQ(sdch_manager()->BlacklistDomainExponential(domain), 1);
 
   // Check that any domain lookup reduces the blacklist counter.
-  EXPECT_FALSE(sdch_manager()->IsInSupportedDomain(gurl));
+  EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET,
+            sdch_manager()->IsInSupportedDomain(gurl));
   EXPECT_EQ(sdch_manager()->BlackListDomainCount(domain), 0);
-  EXPECT_TRUE(sdch_manager()->IsInSupportedDomain(gurl));
+  EXPECT_EQ(SDCH_OK, sdch_manager()->IsInSupportedDomain(gurl));
 }
 
 TEST_F(SdchManagerTest, BlacklistingExponential) {
@@ -164,18 +162,19 @@
 
   int exponential = 1;
   for (int i = 1; i < 100; ++i) {
-    sdch_manager()->BlacklistDomain(gurl, SdchManager::MIN_PROBLEM_CODE);
+    sdch_manager()->BlacklistDomain(gurl, SDCH_OK);
     EXPECT_EQ(sdch_manager()->BlacklistDomainExponential(domain), exponential);
 
     EXPECT_EQ(sdch_manager()->BlackListDomainCount(domain), exponential);
-    EXPECT_FALSE(sdch_manager()->IsInSupportedDomain(gurl));
+    EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET,
+              sdch_manager()->IsInSupportedDomain(gurl));
     EXPECT_EQ(sdch_manager()->BlackListDomainCount(domain), exponential - 1);
 
     // Simulate a large number of domain checks (which eventually remove the
     // blacklisting).
     sdch_manager()->ClearDomainBlacklisting(domain);
     EXPECT_EQ(sdch_manager()->BlackListDomainCount(domain), 0);
-    EXPECT_TRUE(sdch_manager()->IsInSupportedDomain(gurl));
+    EXPECT_EQ(SDCH_OK, sdch_manager()->IsInSupportedDomain(gurl));
 
     // Predict what exponential backoff will be.
     exponential = 1 + 2 * exponential;
@@ -246,7 +245,8 @@
   std::string client_hash;
   std::string server_hash;
   sdch_manager()->GenerateHash(dictionary_text, &client_hash, &server_hash);
-  sdch_manager()->GetVcdiffDictionary(server_hash, target_url, &dictionary);
+  EXPECT_EQ(SDCH_OK, sdch_manager()->GetVcdiffDictionary(
+                         server_hash, target_url, &dictionary));
   EXPECT_TRUE(dictionary.get() != NULL);
 }
 
@@ -269,7 +269,9 @@
   std::string client_hash;
   std::string server_hash;
   sdch_manager()->GenerateHash(dictionary_text, &client_hash, &server_hash);
-  sdch_manager()->GetVcdiffDictionary(server_hash, target_url, &dictionary);
+  EXPECT_EQ(SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME,
+            sdch_manager()->GetVcdiffDictionary(server_hash, target_url,
+                                                &dictionary));
   EXPECT_TRUE(dictionary.get() == NULL);
 }
 
@@ -292,7 +294,9 @@
   std::string client_hash;
   std::string server_hash;
   sdch_manager()->GenerateHash(dictionary_text, &client_hash, &server_hash);
-  sdch_manager()->GetVcdiffDictionary(server_hash, target_url, &dictionary);
+  EXPECT_EQ(SDCH_DICTIONARY_FOUND_HAS_WRONG_SCHEME,
+            sdch_manager()->GetVcdiffDictionary(server_hash, target_url,
+                                                &dictionary));
   EXPECT_TRUE(dictionary.get() == NULL);
 }
 
@@ -516,10 +520,10 @@
   EXPECT_TRUE(AddSdchDictionary(dictionary_text_1,
                                 GURL("http://" + dictionary_domain_1)));
   scoped_refptr<SdchManager::Dictionary> dictionary;
-  sdch_manager()->GetVcdiffDictionary(
-      server_hash_1,
-      GURL("http://" + dictionary_domain_1 + "/random_url"),
-      &dictionary);
+  EXPECT_EQ(SDCH_OK, sdch_manager()->GetVcdiffDictionary(
+                         server_hash_1,
+                         GURL("http://" + dictionary_domain_1 + "/random_url"),
+                         &dictionary));
   EXPECT_TRUE(dictionary.get());
 
   second_manager.AddSdchDictionary(
@@ -530,16 +534,18 @@
       &dictionary);
   EXPECT_TRUE(dictionary.get());
 
-  sdch_manager()->GetVcdiffDictionary(
-      server_hash_2,
-      GURL("http://" + dictionary_domain_2 + "/random_url"),
-      &dictionary);
+  EXPECT_EQ(
+      SDCH_DICTIONARY_HASH_NOT_FOUND,
+      sdch_manager()->GetVcdiffDictionary(
+          server_hash_2, GURL("http://" + dictionary_domain_2 + "/random_url"),
+          &dictionary));
   EXPECT_FALSE(dictionary.get());
 
-  second_manager.GetVcdiffDictionary(
-      server_hash_1,
-      GURL("http://" + dictionary_domain_1 + "/random_url"),
-      &dictionary);
+  EXPECT_EQ(
+      SDCH_DICTIONARY_HASH_NOT_FOUND,
+      second_manager.GetVcdiffDictionary(
+          server_hash_1, GURL("http://" + dictionary_domain_1 + "/random_url"),
+          &dictionary));
   EXPECT_FALSE(dictionary.get());
 }
 
@@ -549,14 +555,15 @@
 
   bool expect_https_support = true;
 
-  EXPECT_TRUE(sdch_manager()->IsInSupportedDomain(url));
-  EXPECT_EQ(expect_https_support,
-            sdch_manager()->IsInSupportedDomain(secure_url));
+  SdchProblemCode expected_code =
+      expect_https_support ? SDCH_OK : SDCH_SECURE_SCHEME_NOT_SUPPORTED;
+
+  EXPECT_EQ(SDCH_OK, sdch_manager()->IsInSupportedDomain(url));
+  EXPECT_EQ(expected_code, sdch_manager()->IsInSupportedDomain(secure_url));
 
   SdchManager::EnableSecureSchemeSupport(!expect_https_support);
-  EXPECT_TRUE(sdch_manager()->IsInSupportedDomain(url));
-  EXPECT_NE(expect_https_support,
-            sdch_manager()->IsInSupportedDomain(secure_url));
+  EXPECT_EQ(SDCH_OK, sdch_manager()->IsInSupportedDomain(url));
+  EXPECT_NE(expected_code, sdch_manager()->IsInSupportedDomain(secure_url));
 }
 
 TEST_F(SdchManagerTest, ClearDictionaryData) {
@@ -572,25 +579,26 @@
   EXPECT_TRUE(AddSdchDictionary(dictionary_text,
                                 GURL("http://" + dictionary_domain)));
   scoped_refptr<SdchManager::Dictionary> dictionary;
-  sdch_manager()->GetVcdiffDictionary(
-      server_hash,
-      GURL("http://" + dictionary_domain + "/random_url"),
-      &dictionary);
+  EXPECT_EQ(SDCH_OK, sdch_manager()->GetVcdiffDictionary(
+                         server_hash,
+                         GURL("http://" + dictionary_domain + "/random_url"),
+                         &dictionary));
   EXPECT_TRUE(dictionary.get());
 
-  sdch_manager()->BlacklistDomain(GURL(blacklist_url),
-                                  SdchManager::MIN_PROBLEM_CODE);
-  EXPECT_FALSE(sdch_manager()->IsInSupportedDomain(blacklist_url));
+  sdch_manager()->BlacklistDomain(GURL(blacklist_url), SDCH_OK);
+  EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET,
+            sdch_manager()->IsInSupportedDomain(blacklist_url));
 
   sdch_manager()->ClearData();
 
   dictionary = NULL;
-  sdch_manager()->GetVcdiffDictionary(
-      server_hash,
-      GURL("http://" + dictionary_domain + "/random_url"),
-      &dictionary);
+  EXPECT_EQ(
+      SDCH_DICTIONARY_HASH_NOT_FOUND,
+      sdch_manager()->GetVcdiffDictionary(
+          server_hash, GURL("http://" + dictionary_domain + "/random_url"),
+          &dictionary));
   EXPECT_FALSE(dictionary.get());
-  EXPECT_TRUE(sdch_manager()->IsInSupportedDomain(blacklist_url));
+  EXPECT_EQ(SDCH_OK, sdch_manager()->IsInSupportedDomain(blacklist_url));
 }
 
 TEST_F(SdchManagerTest, GetDictionaryNotification) {
diff --git a/net/base/sdch_net_log_params.cc b/net/base/sdch_net_log_params.cc
new file mode 100644
index 0000000..02e0ad7
--- /dev/null
+++ b/net/base/sdch_net_log_params.cc
@@ -0,0 +1,34 @@
+// Copyright 2014 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 "net/base/sdch_net_log_params.h"
+
+#include "base/values.h"
+#include "net/base/net_errors.h"
+#include "url/gurl.h"
+
+namespace net {
+
+base::Value* NetLogSdchResourceProblemCallback(SdchProblemCode problem,
+                                               NetLog::LogLevel log_level) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetInteger("sdch_problem_code", problem);
+  dict->SetInteger("net_error", ERR_FAILED);
+  return dict;
+}
+
+base::Value* NetLogSdchDictionaryFetchProblemCallback(
+    SdchProblemCode problem,
+    const GURL& url,
+    bool is_error,
+    NetLog::LogLevel log_level) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetInteger("sdch_problem_code", problem);
+  dict->SetString("dictionary_url", url.spec());
+  if (is_error)
+    dict->SetInteger("net_error", ERR_FAILED);
+  return dict;
+}
+
+}  // namespace net
diff --git a/net/base/sdch_net_log_params.h b/net/base/sdch_net_log_params.h
new file mode 100644
index 0000000..7421a0a
--- /dev/null
+++ b/net/base/sdch_net_log_params.h
@@ -0,0 +1,32 @@
+// Copyright 2014 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 NET_BASE_SDCH_NET_LOG_PARAMS_H_
+#define NET_BASE_SDCH_NET_LOG_PARAMS_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/sdch_problem_codes.h"
+
+class GURL;
+
+namespace net {
+
+NET_EXPORT base::Value* NetLogSdchResourceProblemCallback(
+    SdchProblemCode problem,
+    NetLog::LogLevel log_level);
+
+// If |is_error| is false, "net_error" field won't be added to the JSON and the
+// event won't be painted red in the netlog.
+NET_EXPORT base::Value* NetLogSdchDictionaryFetchProblemCallback(
+    SdchProblemCode problem,
+    const GURL& url,
+    bool is_error,
+    NetLog::LogLevel log_level);
+
+}  // namespace net
+
+#endif  // NET_BASE_SDCH_NET_LOG_PARAMS_H_
diff --git a/net/base/sdch_problem_code_list.h b/net/base/sdch_problem_code_list.h
new file mode 100644
index 0000000..4efc17c
--- /dev/null
+++ b/net/base/sdch_problem_code_list.h
@@ -0,0 +1,127 @@
+// Copyright 2014 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 intentionally does not have header guards, it's included
+// inside a macro to generate enum values.
+
+// This file contains list of sdch-related problem codes.
+// No error.
+SDCH_PROBLEM_CODE(OK, 0)
+
+// Content-encoding correction problems.
+SDCH_PROBLEM_CODE(ADDED_CONTENT_ENCODING, 1)
+SDCH_PROBLEM_CODE(FIXED_CONTENT_ENCODING, 2)
+SDCH_PROBLEM_CODE(FIXED_CONTENT_ENCODINGS, 3)
+
+// Content decoding errors.
+SDCH_PROBLEM_CODE(DECODE_HEADER_ERROR, 4)
+SDCH_PROBLEM_CODE(DECODE_BODY_ERROR, 5)
+
+// More content-encoding correction problems.
+SDCH_PROBLEM_CODE(OPTIONAL_GUNZIP_ENCODING_ADDED, 6)
+
+// Content encoding correction when we're not even tagged as HTML!?!
+SDCH_PROBLEM_CODE(BINARY_ADDED_CONTENT_ENCODING, 7)
+SDCH_PROBLEM_CODE(BINARY_FIXED_CONTENT_ENCODING, 8)
+SDCH_PROBLEM_CODE(BINARY_FIXED_CONTENT_ENCODINGS, 9)
+
+// Dictionary selection for use problems.
+SDCH_PROBLEM_CODE(DICTIONARY_FOUND_HAS_WRONG_DOMAIN, 10)
+SDCH_PROBLEM_CODE(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST, 11)
+SDCH_PROBLEM_CODE(DICTIONARY_FOUND_HAS_WRONG_PATH, 12)
+SDCH_PROBLEM_CODE(DICTIONARY_FOUND_HAS_WRONG_SCHEME, 13)
+SDCH_PROBLEM_CODE(DICTIONARY_HASH_NOT_FOUND, 14)
+SDCH_PROBLEM_CODE(DICTIONARY_HASH_MALFORMED, 15)
+SDCH_PROBLEM_CODE(DICTIONARY_FOUND_EXPIRED, 16)
+
+// Dictionary saving problems.
+SDCH_PROBLEM_CODE(DICTIONARY_HAS_NO_HEADER, 20)
+SDCH_PROBLEM_CODE(DICTIONARY_HEADER_LINE_MISSING_COLON, 21)
+SDCH_PROBLEM_CODE(DICTIONARY_MISSING_DOMAIN_SPECIFIER, 22)
+SDCH_PROBLEM_CODE(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN, 23)
+SDCH_PROBLEM_CODE(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL, 24)
+SDCH_PROBLEM_CODE(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL, 25)
+SDCH_PROBLEM_CODE(DICTIONARY_HAS_NO_TEXT, 26)
+SDCH_PROBLEM_CODE(DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX, 27)
+SDCH_PROBLEM_CODE(DICTIONARY_UNSUPPORTED_VERSION, 28)
+
+// Dictionary loading problems.
+SDCH_PROBLEM_CODE(DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST, 30)
+SDCH_PROBLEM_CODE(DICTIONARY_SELECTED_FOR_SSL, 31)
+SDCH_PROBLEM_CODE(DICTIONARY_ALREADY_LOADED, 32)
+SDCH_PROBLEM_CODE(DICTIONARY_SELECTED_FROM_NON_HTTP, 33)
+SDCH_PROBLEM_CODE(DICTIONARY_IS_TOO_LARGE, 34)
+SDCH_PROBLEM_CODE(DICTIONARY_COUNT_EXCEEDED, 35)
+// defunct = 36, // DICTIONARY_PREVIOUSLY_SCHEDULED_TO_DOWNLOAD used instead.
+// defunct = 37, // DICTIONARY_PREVIOUSLY_SCHEDULED_TO_DOWNLOAD used instead.
+SDCH_PROBLEM_CODE(DICTIONARY_FETCH_READ_FAILED, 38)
+SDCH_PROBLEM_CODE(DICTIONARY_PREVIOUSLY_SCHEDULED_TO_DOWNLOAD, 39)
+
+// Failsafe hack.
+SDCH_PROBLEM_CODE(ATTEMPT_TO_DECODE_NON_HTTP_DATA, 40)
+
+// Content-Encoding problems detected, with no action taken.
+SDCH_PROBLEM_CODE(MULTIENCODING_FOR_NON_SDCH_REQUEST, 50)
+SDCH_PROBLEM_CODE(SDCH_CONTENT_ENCODE_FOR_NON_SDCH_REQUEST, 51)
+
+// Dictionary manager issues.
+SDCH_PROBLEM_CODE(DOMAIN_BLACKLIST_INCLUDES_TARGET, 61)
+
+// Problematic decode recovery methods.
+// Dictionary not found.
+SDCH_PROBLEM_CODE(META_REFRESH_RECOVERY, 70)
+// defunct =  71, // Almost the same as META_REFRESH_UNSUPPORTED.
+// defunct = 72,  // Almost the same as CACHED_META_REFRESH_UNSUPPORTED.
+// defunct = 73,  // PASSING_THROUGH_NON_SDCH plus DISCARD_TENTATIVE_SDCH.
+// Unrecoverable error.
+SDCH_PROBLEM_CODE(META_REFRESH_UNSUPPORTED, 74)
+// As above, but pulled from cache.
+SDCH_PROBLEM_CODE(CACHED_META_REFRESH_UNSUPPORTED, 75)
+// Tagged sdch but missing dictionary-hash.
+SDCH_PROBLEM_CODE(PASSING_THROUGH_NON_SDCH, 76)
+// Last window was not completely decoded.
+SDCH_PROBLEM_CODE(INCOMPLETE_SDCH_CONTENT, 77)
+// URL not found message passing through.
+SDCH_PROBLEM_CODE(PASS_THROUGH_404_CODE, 78)
+
+// This next report is very common, and not really an error scenario, but
+// it exercises the error recovery logic.
+// Back button got pre-SDCH cached content.
+SDCH_PROBLEM_CODE(PASS_THROUGH_OLD_CACHED, 79)
+
+// Common decoded recovery methods.
+// Probably startup tab loading.
+SDCH_PROBLEM_CODE(META_REFRESH_CACHED_RECOVERY, 80)
+// defunct = 81, // Now tracked by ResponseCorruptionDetectionCause histo.
+
+// Non SDCH problems, only accounted for to make stat counting complete
+// (i.e., be able to be sure all dictionary advertisements are accounted
+// for).
+// Possible error in filter chaining.
+SDCH_PROBLEM_CODE(UNFLUSHED_CONTENT, 90)
+// defunct = 91,           // MISSING_TIME_STATS (Should never happen.)
+// No timing stats recorded.
+SDCH_PROBLEM_CODE(CACHE_DECODED, 92)
+// defunct = 93,           // OVER_10_MINUTES (No timing stats recorded.)
+// Filter never even got initialized.
+SDCH_PROBLEM_CODE(UNINITIALIZED, 94)
+// We hadn't even parsed a dictionary selector.
+SDCH_PROBLEM_CODE(PRIOR_TO_DICTIONARY, 95)
+// Something went wrong during decode.
+SDCH_PROBLEM_CODE(DECODE_ERROR, 96)
+
+// Problem during the latency test.
+// SDCH now failing, but it worked before!
+SDCH_PROBLEM_CODE(LATENCY_TEST_DISALLOWED, 100)
+
+// General SDCH problems.
+// SDCH is disabled.
+SDCH_PROBLEM_CODE(DISABLED, 105)
+// SDCH over https is disabled.
+SDCH_PROBLEM_CODE(SECURE_SCHEME_NOT_SUPPORTED, 106)
+
+// Used to bound histogram.
+SDCH_PROBLEM_CODE(MAX_PROBLEM_CODE, 110)
+
+// These values are not used in histograms.
diff --git a/net/base/sdch_problem_codes.h b/net/base/sdch_problem_codes.h
new file mode 100644
index 0000000..da979fc
--- /dev/null
+++ b/net/base/sdch_problem_codes.h
@@ -0,0 +1,20 @@
+// Copyright 2014 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 NET_BASE_SDCH_PROBLEM_CODES_H_
+#define NET_BASE_SDCH_PROBLEM_CODES_H_
+
+namespace net {
+
+// A list of errors that appeared and were either resolved, or used to turn
+// off sdch encoding.
+enum SdchProblemCode {
+#define SDCH_PROBLEM_CODE(label, value) SDCH_##label = value,
+#include "net/base/sdch_problem_code_list.h"
+#undef SDCH_PROBLEM_CODE
+};
+
+}  // namespace net
+
+#endif  // NET_BASE_SDCH_PROBLEM_CODES_H_
diff --git a/net/filter/filter.cc b/net/filter/filter.cc
index c8a4740..c9a56bd 100644
--- a/net/filter/filter.cc
+++ b/net/filter/filter.cc
@@ -28,11 +28,14 @@
 #include "net/base/filename_util_unsafe.h"
 #include "net/base/io_buffer.h"
 #include "net/base/mime_util.h"
+#include "net/base/sdch_net_log_params.h"
 #include "net/filter/gzip_filter.h"
 #include "net/filter/sdch_filter.h"
 #include "net/url_request/url_request_context.h"
 #include "url/gurl.h"
 
+namespace net {
+
 namespace {
 
 // Filter types (using canonical lower case only):
@@ -53,9 +56,15 @@
 // Buffer size allocated when de-compressing data.
 const int kFilterBufSize = 32 * 1024;
 
-}  // namespace
+void LogSdchProblem(const FilterContext& filter_context,
+                    SdchProblemCode problem) {
+  SdchManager::SdchErrorRecovery(problem);
+  filter_context.GetNetLog().AddEvent(
+      NetLog::TYPE_SDCH_DECODING_ERROR,
+      base::Bind(&NetLogSdchResourceProblemCallback, problem));
+}
 
-namespace net {
+}  // namespace
 
 FilterContext::~FilterContext() {
 }
@@ -233,13 +242,12 @@
     // It was not an SDCH request, so we'll just record stats.
     if (1 < encoding_types->size()) {
       // Multiple filters were intended to only be used for SDCH (thus far!)
-      SdchManager::SdchErrorRecovery(
-          SdchManager::MULTIENCODING_FOR_NON_SDCH_REQUEST);
+      LogSdchProblem(filter_context, SDCH_MULTIENCODING_FOR_NON_SDCH_REQUEST);
     }
     if ((1 == encoding_types->size()) &&
         (FILTER_TYPE_SDCH == encoding_types->front())) {
-        SdchManager::SdchErrorRecovery(
-            SdchManager::SDCH_CONTENT_ENCODE_FOR_NON_SDCH_REQUEST);
+      LogSdchProblem(filter_context,
+                     SDCH_SDCH_CONTENT_ENCODE_FOR_NON_SDCH_REQUEST);
     }
     return;
   }
@@ -259,8 +267,7 @@
     // no-op pass through filter if it doesn't get gzip headers where expected.
     if (1 == encoding_types->size()) {
       encoding_types->push_back(FILTER_TYPE_GZIP_HELPING_SDCH);
-      SdchManager::SdchErrorRecovery(
-          SdchManager::OPTIONAL_GUNZIP_ENCODING_ADDED);
+      LogSdchProblem(filter_context, SDCH_OPTIONAL_GUNZIP_ENCODING_ADDED);
     }
     return;
   }
@@ -294,14 +301,11 @@
     // Suspicious case: Advertised dictionary, but server didn't use sdch, and
     // we're HTML tagged.
     if (encoding_types->empty()) {
-      SdchManager::SdchErrorRecovery(
-          SdchManager::ADDED_CONTENT_ENCODING);
+      LogSdchProblem(filter_context, SDCH_ADDED_CONTENT_ENCODING);
     } else if (1 == encoding_types->size()) {
-      SdchManager::SdchErrorRecovery(
-          SdchManager::FIXED_CONTENT_ENCODING);
+      LogSdchProblem(filter_context, SDCH_FIXED_CONTENT_ENCODING);
     } else {
-      SdchManager::SdchErrorRecovery(
-          SdchManager::FIXED_CONTENT_ENCODINGS);
+      LogSdchProblem(filter_context, SDCH_FIXED_CONTENT_ENCODINGS);
     }
   } else {
     // Remarkable case!?!  We advertised an SDCH dictionary, content-encoding
@@ -313,14 +317,11 @@
     // start with "text/html" for some other reason??  We'll report this as a
     // fixup to a binary file, but it probably really is text/html (some how).
     if (encoding_types->empty()) {
-      SdchManager::SdchErrorRecovery(
-          SdchManager::BINARY_ADDED_CONTENT_ENCODING);
+      LogSdchProblem(filter_context, SDCH_BINARY_ADDED_CONTENT_ENCODING);
     } else if (1 == encoding_types->size()) {
-      SdchManager::SdchErrorRecovery(
-          SdchManager::BINARY_FIXED_CONTENT_ENCODING);
+      LogSdchProblem(filter_context, SDCH_BINARY_FIXED_CONTENT_ENCODING);
     } else {
-      SdchManager::SdchErrorRecovery(
-          SdchManager::BINARY_FIXED_CONTENT_ENCODINGS);
+      LogSdchProblem(filter_context, SDCH_BINARY_FIXED_CONTENT_ENCODINGS);
     }
   }
 
diff --git a/net/filter/filter.h b/net/filter/filter.h
index 37f55e2..13489ef 100644
--- a/net/filter/filter.h
+++ b/net/filter/filter.h
@@ -60,8 +60,9 @@
 
 namespace net {
 
-class URLRequestContext;
+class BoundNetLog;
 class IOBuffer;
+class URLRequestContext;
 
 //------------------------------------------------------------------------------
 // Define an interface class that allows access to contextual information
@@ -122,6 +123,9 @@
   // The following method forces the context to emit a specific set of
   // statistics as selected by the argument.
   virtual void RecordPacketStats(StatisticSelector statistic) const = 0;
+
+  // The BoundNetLog of the associated request.
+  virtual const BoundNetLog& GetNetLog() const = 0;
 };
 
 //------------------------------------------------------------------------------
diff --git a/net/filter/mock_filter_context.cc b/net/filter/mock_filter_context.cc
index e1e4793..35ab9ee 100644
--- a/net/filter/mock_filter_context.cc
+++ b/net/filter/mock_filter_context.cc
@@ -66,4 +66,8 @@
   return context_.get();
 }
 
+const BoundNetLog& MockFilterContext::GetNetLog() const {
+  return net_log_;
+}
+
 }  // namespace net
diff --git a/net/filter/mock_filter_context.h b/net/filter/mock_filter_context.h
index 8150e8b..41c9e9e 100644
--- a/net/filter/mock_filter_context.h
+++ b/net/filter/mock_filter_context.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/memory/scoped_ptr.h"
+#include "net/base/net_log.h"
 #include "net/filter/filter.h"
 #include "url/gurl.h"
 
@@ -73,6 +74,8 @@
 
   void RecordPacketStats(StatisticSelector statistic) const override {}
 
+  const BoundNetLog& GetNetLog() const override;
+
  private:
   int buffer_size_;
   std::string mime_type_;
@@ -85,6 +88,7 @@
   bool ok_to_call_get_url_;
   int response_code_;
   scoped_ptr<URLRequestContext> context_;
+  BoundNetLog net_log_;
 
   DISALLOW_COPY_AND_ASSIGN(MockFilterContext);
 };
diff --git a/net/filter/sdch_filter.cc b/net/filter/sdch_filter.cc
index df72367..fea0597 100644
--- a/net/filter/sdch_filter.cc
+++ b/net/filter/sdch_filter.cc
@@ -11,7 +11,9 @@
 
 #include "base/logging.h"
 #include "base/metrics/histogram.h"
+#include "base/values.h"
 #include "net/base/sdch_manager.h"
+#include "net/base/sdch_net_log_params.h"
 #include "net/url_request/url_request_context.h"
 
 #include "sdch/open-vcdiff/src/google/vcdecoder.h"
@@ -50,6 +52,43 @@
   RESPONSE_MAX,
 };
 
+const char* ResponseCorruptionDetectionCauseToString(
+    ResponseCorruptionDetectionCause cause) {
+  const char* cause_string = "<unknown>";
+  switch (cause) {
+    case RESPONSE_NONE:
+      cause_string = "NONE";
+    case RESPONSE_404:
+      cause_string = "404";
+    case RESPONSE_NOT_200:
+      cause_string = "NOT_200";
+    case RESPONSE_OLD_UNENCODED:
+      cause_string = "OLD_UNENCODED";
+    case RESPONSE_TENTATIVE_SDCH:
+      cause_string = "TENTATIVE_SDCH";
+    case RESPONSE_NO_DICTIONARY:
+      cause_string = "NO_DICTIONARY";
+    case RESPONSE_CORRUPT_SDCH:
+      cause_string = "CORRUPT_SDCH";
+    case RESPONSE_ENCODING_LIE:
+      cause_string = "ENCODING_LIE";
+    case RESPONSE_MAX:
+      cause_string = "<Error: max enum value>";
+  }
+  return cause_string;
+}
+
+base::Value* NetLogSdchResponseCorruptionDetectionCallback(
+    ResponseCorruptionDetectionCause cause,
+    bool cached,
+    NetLog::LogLevel log_level) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetString("cause", ResponseCorruptionDetectionCauseToString(cause));
+  dict->SetBoolean("cached", cached);
+  dict->SetInteger("net_error", ERR_FAILED);
+  return dict;
+}
+
 }  // namespace
 
 SdchFilter::SdchFilter(const FilterContext& filter_context)
@@ -84,12 +123,12 @@
   if (vcdiff_streaming_decoder_.get()) {
     if (!vcdiff_streaming_decoder_->FinishDecoding()) {
       decoding_status_ = DECODING_ERROR;
-      SdchManager::SdchErrorRecovery(SdchManager::INCOMPLETE_SDCH_CONTENT);
+      LogSdchProblem(SDCH_INCOMPLETE_SDCH_CONTENT);
       // Make it possible for the user to hit reload, and get non-sdch content.
       // Note this will "wear off" quickly enough, and is just meant to assure
       // in some rare case that the user is not stuck.
       url_request_context_->sdch_manager()->BlacklistDomain(
-          url_, SdchManager::INCOMPLETE_SDCH_CONTENT);
+          url_, SDCH_INCOMPLETE_SDCH_CONTENT);
       UMA_HISTOGRAM_COUNTS("Sdch3.PartialBytesIn",
            static_cast<int>(filter_context_.GetByteReadCount()));
       UMA_HISTOGRAM_COUNTS("Sdch3.PartialVcdiffIn", source_bytes_);
@@ -99,7 +138,7 @@
 
   if (!dest_buffer_excess_.empty()) {
     // Filter chaining error, or premature teardown.
-    SdchManager::SdchErrorRecovery(SdchManager::UNFLUSHED_CONTENT);
+    LogSdchProblem(SDCH_UNFLUSHED_CONTENT);
     UMA_HISTOGRAM_COUNTS("Sdch3.UnflushedBytesIn",
          static_cast<int>(filter_context_.GetByteReadCount()));
     UMA_HISTOGRAM_COUNTS("Sdch3.UnflushedBufferSize",
@@ -111,7 +150,7 @@
   if (filter_context_.IsCachedContent()) {
     // Not a real error, but it is useful to have this tally.
     // TODO(jar): Remove this stat after SDCH stability is validated.
-    SdchManager::SdchErrorRecovery(SdchManager::CACHE_DECODED);
+    LogSdchProblem(SDCH_CACHE_DECODED);
     return;  // We don't need timing stats, and we aready got ratios.
   }
 
@@ -135,15 +174,15 @@
       return;
     }
     case DECODING_UNINITIALIZED: {
-      SdchManager::SdchErrorRecovery(SdchManager::UNINITIALIZED);
+      LogSdchProblem(SDCH_UNINITIALIZED);
       return;
     }
     case WAITING_FOR_DICTIONARY_SELECTION: {
-      SdchManager::SdchErrorRecovery(SdchManager::PRIOR_TO_DICTIONARY);
+      LogSdchProblem(SDCH_PRIOR_TO_DICTIONARY);
       return;
     }
     case DECODING_ERROR: {
-      SdchManager::SdchErrorRecovery(SdchManager::DECODE_ERROR);
+      LogSdchProblem(SDCH_DECODE_ERROR);
       return;
     }
     case META_REFRESH_RECOVERY: {
@@ -210,7 +249,7 @@
         // We could be more generous, but for now, only a "NOT FOUND" code will
         // cause a pass through.  All other bad codes will fall into a
         // meta-refresh.
-        SdchManager::SdchErrorRecovery(SdchManager::PASS_THROUGH_404_CODE);
+        LogSdchProblem(SDCH_PASS_THROUGH_404_CODE);
         cause = RESPONSE_404;
         decoding_status_ = PASS_THROUGH;
       } else if (filter_context_.GetResponseCode() != 200) {
@@ -220,7 +259,7 @@
                  && !dictionary_hash_is_plausible_) {
         // We must have hit the back button, and gotten content that was fetched
         // before we *really* advertised SDCH and a dictionary.
-        SdchManager::SdchErrorRecovery(SdchManager::PASS_THROUGH_OLD_CACHED);
+        LogSdchProblem(SDCH_PASS_THROUGH_OLD_CACHED);
         decoding_status_ = PASS_THROUGH;
         cause = RESPONSE_OLD_UNENCODED;
       } else if (possible_pass_through_) {
@@ -256,11 +295,11 @@
         // though it is not!
         // Meta-refresh won't help, as we didn't advertise an SDCH dictionary!!
         // Worse yet, meta-refresh could lead to an infinite refresh loop.
-        SdchManager::SdchErrorRecovery(SdchManager::PASSING_THROUGH_NON_SDCH);
+        LogSdchProblem(SDCH_PASSING_THROUGH_NON_SDCH);
         decoding_status_ = PASS_THROUGH;
         // ... but further back-off on advertising SDCH support.
         url_request_context_->sdch_manager()->BlacklistDomain(
-            url_, SdchManager::PASSING_THROUGH_NON_SDCH);
+            url_, SDCH_PASSING_THROUGH_NON_SDCH);
         cause = RESPONSE_ENCODING_LIE;
       }
       DCHECK_NE(RESPONSE_NONE, cause);
@@ -274,6 +313,10 @@
         UMA_HISTOGRAM_ENUMERATION(
             "Sdch3.ResponseCorruptionDetection.Uncached", cause, RESPONSE_MAX);
       }
+      filter_context_.GetNetLog().AddEvent(
+          NetLog::TYPE_SDCH_RESPONSE_CORRUPTION_DETECTION,
+          base::Bind(&NetLogSdchResponseCorruptionDetectionCallback, cause,
+                     filter_context_.IsCachedContent()));
 
       if (decoding_status_ == PASS_THROUGH) {
         dest_buffer_excess_ = dictionary_hash_;  // Send what we scanned.
@@ -282,13 +325,12 @@
         if (std::string::npos == mime_type_.find("text/html")) {
           // Since we can't do a meta-refresh (along with an exponential
           // backoff), we'll just make sure this NEVER happens again.
-          SdchManager::ProblemCodes problem =
-              (filter_context_.IsCachedContent() ?
-               SdchManager::CACHED_META_REFRESH_UNSUPPORTED :
-               SdchManager::META_REFRESH_UNSUPPORTED);
+          SdchProblemCode problem = (filter_context_.IsCachedContent()
+                                         ? SDCH_CACHED_META_REFRESH_UNSUPPORTED
+                                         : SDCH_META_REFRESH_UNSUPPORTED);
           url_request_context_->sdch_manager()->BlacklistDomainForever(
               url_, problem);
-          SdchManager::SdchErrorRecovery(problem);
+          LogSdchProblem(problem);
           return FILTER_ERROR;
         }
         // HTML content means we can issue a meta-refresh, and get the content
@@ -296,14 +338,13 @@
         if (filter_context_.IsCachedContent()) {
           // Cached content is probably a startup tab, so we'll just get fresh
           // content and try again, without disabling sdch.
-          SdchManager::SdchErrorRecovery(
-              SdchManager::META_REFRESH_CACHED_RECOVERY);
+          LogSdchProblem(SDCH_META_REFRESH_CACHED_RECOVERY);
         } else {
           // Since it wasn't in the cache, we definately need at least some
           // period of blacklisting to get the correct content.
           url_request_context_->sdch_manager()->BlacklistDomain(
-              url_, SdchManager::META_REFRESH_RECOVERY);
-          SdchManager::SdchErrorRecovery(SdchManager::META_REFRESH_RECOVERY);
+              url_, SDCH_META_REFRESH_RECOVERY);
+          LogSdchProblem(SDCH_META_REFRESH_RECOVERY);
         }
         decoding_status_ = META_REFRESH_RECOVERY;
         // Issue a meta redirect with SDCH disabled.
@@ -357,7 +398,7 @@
   if (!ret) {
     vcdiff_streaming_decoder_.reset(NULL);  // Don't call it again.
     decoding_status_ = DECODING_ERROR;
-    SdchManager::SdchErrorRecovery(SdchManager::DECODE_BODY_ERROR);
+    LogSdchProblem(SDCH_DECODE_BODY_ERROR);
     return FILTER_ERROR;
   }
 
@@ -394,32 +435,35 @@
   DCHECK(!dictionary_.get());
   dictionary_hash_is_plausible_ = true;  // Assume plausible, but check.
 
+  SdchProblemCode rv = SDCH_OK;
   if ('\0' == dictionary_hash_[kServerIdLength - 1]) {
     SdchManager* manager(url_request_context_->sdch_manager());
-    manager->GetVcdiffDictionary(
-        std::string(dictionary_hash_, 0, kServerIdLength - 1),
-        url_, &dictionary_);
-  } else {
-    dictionary_hash_is_plausible_ = false;
-  }
-
-  if (!dictionary_.get()) {
-    DCHECK(dictionary_hash_.size() == kServerIdLength);
-    // Since dictionary was not found, check to see if hash was even plausible.
-    for (size_t i = 0; i < kServerIdLength - 1; ++i) {
-      char base64_char = dictionary_hash_[i];
-      if (!isalnum(base64_char) && '-' != base64_char && '_' != base64_char) {
-        dictionary_hash_is_plausible_ = false;
-        break;
+    rv = manager->GetVcdiffDictionary(
+        std::string(dictionary_hash_, 0, kServerIdLength - 1), url_,
+        &dictionary_);
+    if (rv == SDCH_DICTIONARY_HASH_NOT_FOUND) {
+      DCHECK(dictionary_hash_.size() == kServerIdLength);
+      // Since dictionary was not found, check to see if hash was even
+      // plausible.
+      for (size_t i = 0; i < kServerIdLength - 1; ++i) {
+        char base64_char = dictionary_hash_[i];
+        if (!isalnum(base64_char) && '-' != base64_char && '_' != base64_char) {
+          rv = SDCH_DICTIONARY_HASH_MALFORMED;
+          dictionary_hash_is_plausible_ = false;
+          break;
+        }
       }
     }
-    if (dictionary_hash_is_plausible_)
-      SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_NOT_FOUND);
-    else
-      SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_MALFORMED);
+  } else {
+    dictionary_hash_is_plausible_ = false;
+    rv = SDCH_DICTIONARY_HASH_MALFORMED;
+  }
+  if (rv != SDCH_OK) {
+    LogSdchProblem(rv);
     decoding_status_ = DECODING_ERROR;
     return FILTER_ERROR;
   }
+  DCHECK(dictionary_.get());
   vcdiff_streaming_decoder_.reset(new open_vcdiff::VCDiffStreamingDecoder);
   vcdiff_streaming_decoder_->SetAllowVcdTarget(false);
   vcdiff_streaming_decoder_->StartDecoding(dictionary_->text().data(),
@@ -446,4 +490,11 @@
   return amount;
 }
 
+void SdchFilter::LogSdchProblem(SdchProblemCode problem) {
+  SdchManager::SdchErrorRecovery(problem);
+  filter_context_.GetNetLog().AddEvent(
+      NetLog::TYPE_SDCH_DECODING_ERROR,
+      base::Bind(&NetLogSdchResourceProblemCallback, problem));
+}
+
 }  // namespace net
diff --git a/net/filter/sdch_filter.h b/net/filter/sdch_filter.h
index e9648b1..a1a6607 100644
--- a/net/filter/sdch_filter.h
+++ b/net/filter/sdch_filter.h
@@ -64,6 +64,9 @@
   // specified dest_buffer.
   int OutputBufferExcess(char* const dest_buffer, size_t available_space);
 
+  // Add SDCH Problem to net-log and record histogram.
+  void LogSdchProblem(SdchProblemCode problem);
+
   // Context data from the owner of this filter.
   const FilterContext& filter_context_;
 
diff --git a/net/filter/sdch_filter_unittest.cc b/net/filter/sdch_filter_unittest.cc
index 2263001..79b5673 100644
--- a/net/filter/sdch_filter_unittest.cc
+++ b/net/filter/sdch_filter_unittest.cc
@@ -67,14 +67,7 @@
   // the attempt succeeded.
   bool AddSdchDictionary(const std::string& dictionary_text,
                          const GURL& gurl) {
-    std::string list;
-    sdch_manager_->GetAvailDictionaryList(gurl, &list);
-    sdch_manager_->AddSdchDictionary(dictionary_text, gurl);
-    std::string list2;
-    sdch_manager_->GetAvailDictionaryList(gurl, &list2);
-
-    // The list of hashes should change iff the addition succeeds.
-    return (list != list2);
+    return sdch_manager_->AddSdchDictionary(dictionary_text, gurl) == SDCH_OK;
   }
 
   MockFilterContext* filter_context() { return filter_context_.get(); }
@@ -412,9 +405,10 @@
   EXPECT_EQ(0, output_bytes_or_buffer_size);
   EXPECT_EQ(Filter::FILTER_ERROR, status);
 
-  EXPECT_FALSE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
+  EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET,
+            sdch_manager_->IsInSupportedDomain(GURL(url_string)));
   sdch_manager_->ClearBlacklistings();
-  EXPECT_TRUE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
+  EXPECT_EQ(SDCH_OK, sdch_manager_->IsInSupportedDomain(GURL(url_string)));
 }
 
 TEST_F(SdchFilterTest, DictionaryAddOnce) {
@@ -667,10 +661,11 @@
                               filter.get(), &output));
   EXPECT_EQ(output.size(), 0u);  // No output written.
 
-  EXPECT_TRUE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
-  EXPECT_FALSE(sdch_manager_->IsInSupportedDomain(wrong_domain_url));
+  EXPECT_EQ(SDCH_OK, sdch_manager_->IsInSupportedDomain(GURL(url_string)));
+  EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET,
+            sdch_manager_->IsInSupportedDomain(wrong_domain_url));
   sdch_manager_->ClearBlacklistings();
-  EXPECT_TRUE(sdch_manager_->IsInSupportedDomain(wrong_domain_url));
+  EXPECT_EQ(SDCH_OK, sdch_manager_->IsInSupportedDomain(wrong_domain_url));
 }
 
 TEST_F(SdchFilterTest, DictionaryPathValidation) {
@@ -723,9 +718,10 @@
                               output_block_size, filter.get(), &output));
   EXPECT_EQ(output.size(), 0u);  // No output written.
 
-  EXPECT_FALSE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
+  EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET,
+            sdch_manager_->IsInSupportedDomain(GURL(url_string)));
   sdch_manager_->ClearBlacklistings();
-  EXPECT_TRUE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
+  EXPECT_EQ(SDCH_OK, sdch_manager_->IsInSupportedDomain(GURL(url_string)));
 }
 
 TEST_F(SdchFilterTest, DictionaryPortValidation) {
@@ -789,9 +785,10 @@
                               output_block_size, filter.get(), &output));
   EXPECT_EQ(output.size(), 0u);  // No output written.
 
-  EXPECT_FALSE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
+  EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET,
+            sdch_manager_->IsInSupportedDomain(GURL(url_string)));
   sdch_manager_->ClearBlacklistings();
-  EXPECT_TRUE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
+  EXPECT_EQ(SDCH_OK, sdch_manager_->IsInSupportedDomain(GURL(url_string)));
 }
 
 //------------------------------------------------------------------------------
diff --git a/net/net.gypi b/net/net.gypi
index fef6136..a972783 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -275,8 +275,12 @@
       'base/request_priority.h',
       'base/sdch_manager.cc',
       'base/sdch_manager.h',
+      'base/sdch_net_log_params.cc',
+      'base/sdch_net_log_params.h',
       'base/sdch_observer.cc',
       'base/sdch_observer.h',
+      'base/sdch_problem_code_list.h',
+      'base/sdch_problem_codes.h',
       'base/static_cookie_policy.cc',
       'base/static_cookie_policy.h',
       'base/test_data_stream.cc',
diff --git a/net/url_request/sdch_dictionary_fetcher.cc b/net/url_request/sdch_dictionary_fetcher.cc
index 5806193..5ac65ef 100644
--- a/net/url_request/sdch_dictionary_fetcher.cc
+++ b/net/url_request/sdch_dictionary_fetcher.cc
@@ -11,6 +11,7 @@
 #include "base/compiler_specific.h"
 #include "base/thread_task_runner_handle.h"
 #include "net/base/load_flags.h"
+#include "net/base/sdch_net_log_params.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_status.h"
 #include "net/url_request/url_request_throttler_manager.h"
@@ -45,16 +46,15 @@
   // Avoid pushing duplicate copy onto queue. We may fetch this url again later
   // and get a different dictionary, but there is no reason to have it in the
   // queue twice at one time.
-  if (!fetch_queue_.empty() && fetch_queue_.back() == dictionary_url) {
+  if ((!fetch_queue_.empty() && fetch_queue_.back() == dictionary_url) ||
+      attempted_load_.find(dictionary_url) != attempted_load_.end()) {
+    // TODO(rdsmith): log this error to the net log of the URLRequest
+    // initiating this fetch, once URLRequest will be passed here.
     SdchManager::SdchErrorRecovery(
-        SdchManager::DICTIONARY_ALREADY_SCHEDULED_TO_DOWNLOAD);
+        SDCH_DICTIONARY_PREVIOUSLY_SCHEDULED_TO_DOWNLOAD);
     return;
   }
-  if (attempted_load_.find(dictionary_url) != attempted_load_.end()) {
-    SdchManager::SdchErrorRecovery(
-        SdchManager::DICTIONARY_ALREADY_TRIED_TO_DOWNLOAD);
-    return;
-  }
+
   attempted_load_.insert(dictionary_url);
   fetch_queue_.push(dictionary_url);
 
@@ -166,6 +166,7 @@
 
   next_state_ = STATE_REQUEST_STARTED;
   current_request_->Start();
+  current_request_->net_log().AddEvent(NetLog::TYPE_SDCH_DICTIONARY_FETCH);
 
   return OK;
 }
@@ -207,7 +208,12 @@
       // an infinite loop.  It's not clear how to handle a read failure
       // without a promise to invoke the callback at some point in the future,
       // so the request is failed.
-      SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_FETCH_READ_FAILED);
+      SdchManager::SdchErrorRecovery(SDCH_DICTIONARY_FETCH_READ_FAILED);
+      current_request_->net_log().AddEvent(
+          NetLog::TYPE_SDCH_DICTIONARY_ERROR,
+          base::Bind(&NetLogSdchDictionaryFetchProblemCallback,
+                     SDCH_DICTIONARY_FETCH_READ_FAILED, current_request_->url(),
+                     true));
       DLOG(FATAL)
           << "URLRequest::Read() returned false without IO pending or error!";
       return ERR_FAILED;
@@ -228,8 +234,10 @@
   DCHECK(CalledOnValidThread());
 
   // If the dictionary was successfully fetched, add it to the manager.
-  if (rv == OK)
-    dictionary_fetched_callback_.Run(dictionary_, current_request_->url());
+  if (rv == OK) {
+    dictionary_fetched_callback_.Run(dictionary_, current_request_->url(),
+                                     current_request_->net_log());
+  }
 
   current_request_.reset();
   buffer_ = NULL;
diff --git a/net/url_request/sdch_dictionary_fetcher.h b/net/url_request/sdch_dictionary_fetcher.h
index 5b6f269e..79d8778 100644
--- a/net/url_request/sdch_dictionary_fetcher.h
+++ b/net/url_request/sdch_dictionary_fetcher.h
@@ -36,7 +36,8 @@
                                          public base::NonThreadSafe {
  public:
   typedef base::Callback<void(const std::string& dictionary_text,
-                              const GURL& dictionary_url)>
+                              const GURL& dictionary_url,
+                              const BoundNetLog& net_log)>
       OnDictionaryFetchedCallback;
 
   // The consumer must guarantee that |*context| outlives this object.
diff --git a/net/url_request/sdch_dictionary_fetcher_unittest.cc b/net/url_request/sdch_dictionary_fetcher_unittest.cc
index 0febd7e..ae76cbc 100644
--- a/net/url_request/sdch_dictionary_fetcher_unittest.cc
+++ b/net/url_request/sdch_dictionary_fetcher_unittest.cc
@@ -99,7 +99,8 @@
   }
 
   void OnDictionaryFetched(const std::string& dictionary_text,
-                           const GURL& dictionary_url) {
+                           const GURL& dictionary_url,
+                           const BoundNetLog& net_log) {
     dictionary_additions.push_back(
         DictionaryAdditions(dictionary_text, dictionary_url));
   }
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 4261b92..33a50884 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -24,6 +24,7 @@
 #include "net/base/net_util.h"
 #include "net/base/network_delegate.h"
 #include "net/base/sdch_manager.h"
+#include "net/base/sdch_net_log_params.h"
 #include "net/cert/cert_status_flags.h"
 #include "net/cookies/cookie_store.h"
 #include "net/http/http_content_disposition.h"
@@ -70,6 +71,7 @@
   int GetResponseCode() const override;
   const URLRequestContext* GetURLRequestContext() const override;
   void RecordPacketStats(StatisticSelector statistic) const override;
+  const BoundNetLog& GetNetLog() const override;
 
   // Method to allow us to reset filter context for a response that should have
   // been SDCH encoded when there is an update due to an explicit HTTP header.
@@ -78,6 +80,10 @@
  private:
   URLRequestHttpJob* job_;
 
+  // URLRequestHttpJob may be detached from URLRequest, but we still need to
+  // return something.
+  BoundNetLog dummy_log_;
+
   DISALLOW_COPY_AND_ASSIGN(HttpFilterContext);
 };
 
@@ -147,6 +153,10 @@
   job_->RecordPacketStats(statistic);
 }
 
+const BoundNetLog& URLRequestHttpJob::HttpFilterContext::GetNetLog() const {
+  return job_->request() ? job_->request()->net_log() : dummy_log_;
+}
+
 // TODO(darin): make sure the port blocking code is not lost
 // static
 URLRequestJob* URLRequestHttpJob::Factory(URLRequest* request,
@@ -321,21 +331,42 @@
   ProcessPublicKeyPinsHeader();
 
   SdchManager* sdch_manager(request()->context()->sdch_manager());
-  if (sdch_manager && sdch_manager->IsInSupportedDomain(request_->url())) {
-    const std::string name = "Get-Dictionary";
-    std::string url_text;
-    void* iter = NULL;
-    // TODO(jar): We need to not fetch dictionaries the first time they are
-    // seen, but rather wait until we can justify their usefulness.
-    // For now, we will only fetch the first dictionary, which will at least
-    // require multiple suggestions before we get additional ones for this site.
-    // Eventually we should wait until a dictionary is requested several times
-    // before we even download it (so that we don't waste memory or bandwidth).
-    if (GetResponseHeaders()->EnumerateHeader(&iter, name, &url_text)) {
-      // Resolve suggested URL relative to request url.
-      GURL sdch_dictionary_url = request_->url().Resolve(url_text);
-      if (sdch_dictionary_url.is_valid()) {
-        sdch_manager->OnGetDictionary(request_->url(), sdch_dictionary_url);
+  if (sdch_manager) {
+    SdchProblemCode rv = sdch_manager->IsInSupportedDomain(request()->url());
+    if (rv != SDCH_OK) {
+      // If SDCH is just disabled, it is not a real error.
+      if (rv != SDCH_DISABLED && rv != SDCH_SECURE_SCHEME_NOT_SUPPORTED) {
+        SdchManager::SdchErrorRecovery(rv);
+        request()->net_log().AddEvent(
+            NetLog::TYPE_SDCH_DECODING_ERROR,
+            base::Bind(&NetLogSdchResourceProblemCallback, rv));
+      }
+    } else {
+      const std::string name = "Get-Dictionary";
+      std::string url_text;
+      void* iter = NULL;
+      // TODO(jar): We need to not fetch dictionaries the first time they are
+      // seen, but rather wait until we can justify their usefulness.
+      // For now, we will only fetch the first dictionary, which will at least
+      // require multiple suggestions before we get additional ones for this
+      // site. Eventually we should wait until a dictionary is requested
+      // several times
+      // before we even download it (so that we don't waste memory or
+      // bandwidth).
+      if (GetResponseHeaders()->EnumerateHeader(&iter, name, &url_text)) {
+        // Resolve suggested URL relative to request url.
+        GURL sdch_dictionary_url = request_->url().Resolve(url_text);
+        if (sdch_dictionary_url.is_valid()) {
+          rv = sdch_manager->OnGetDictionary(request_->url(),
+                                             sdch_dictionary_url);
+          if (rv != SDCH_OK) {
+            SdchManager::SdchErrorRecovery(rv);
+            request_->net_log().AddEvent(
+                NetLog::TYPE_SDCH_DICTIONARY_ERROR,
+                base::Bind(&NetLogSdchDictionaryFetchProblemCallback, rv,
+                           sdch_dictionary_url, false));
+          }
+        }
       }
     }
   }
@@ -483,13 +514,24 @@
   // simple_data_source.
   if (!request_info_.extra_headers.HasHeader(
       HttpRequestHeaders::kAcceptEncoding)) {
-    bool advertise_sdch = sdch_manager &&
-        // We don't support SDCH responses to POST as there is a possibility
-        // of having SDCH encoded responses returned (e.g. by the cache)
-        // which we cannot decode, and in those situations, we will need
-        // to retransmit the request without SDCH, which is illegal for a POST.
-        request()->method() != "POST" &&
-        sdch_manager->IsInSupportedDomain(request_->url());
+    // We don't support SDCH responses to POST as there is a possibility
+    // of having SDCH encoded responses returned (e.g. by the cache)
+    // which we cannot decode, and in those situations, we will need
+    // to retransmit the request without SDCH, which is illegal for a POST.
+    bool advertise_sdch = sdch_manager != NULL && request()->method() != "POST";
+    if (advertise_sdch) {
+      SdchProblemCode rv = sdch_manager->IsInSupportedDomain(request()->url());
+      if (rv != SDCH_OK) {
+        advertise_sdch = false;
+        // If SDCH is just disabled, it is not a real error.
+        if (rv != SDCH_DISABLED && rv != SDCH_SECURE_SCHEME_NOT_SUPPORTED) {
+          SdchManager::SdchErrorRecovery(rv);
+          request()->net_log().AddEvent(
+              NetLog::TYPE_SDCH_DECODING_ERROR,
+              base::Bind(&NetLogSdchResourceProblemCallback, rv));
+        }
+      }
+    }
     std::string avail_dictionaries;
     if (advertise_sdch) {
       sdch_manager->GetAvailDictionaryList(request_->url(),
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index f0c150e..4d493e3 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -31046,11 +31046,22 @@
 </histogram>
 
 <histogram name="Sdch3.ProblemCodes_3" enum="SdchProblemCode">
+  <obsolete>
+    Deprecated 2014-11. Sdch3.ProblemCodes_5 used instead.
+  </obsolete>
   <owner>rdsmith@chromium.org</owner>
   <summary>Each sample is the report of a distinct problem code.</summary>
 </histogram>
 
 <histogram name="Sdch3.ProblemCodes_4" enum="SdchProblemCode">
+  <obsolete>
+    Deprecated 2014-11. Sdch3.ProblemCodes_5 used instead.
+  </obsolete>
+  <owner>rdsmith@chromium.org</owner>
+  <summary>Each sample is the report of a distinct problem code.</summary>
+</histogram>
+
+<histogram name="Sdch3.ProblemCodes_5" enum="SdchProblemCode">
   <owner>rdsmith@chromium.org</owner>
   <summary>Each sample is the report of a distinct problem code.</summary>
 </histogram>
@@ -53458,7 +53469,9 @@
 </enum>
 
 <enum name="SdchProblemCode" type="int">
-  <summary>SDCH problem codes, listed in net/base/sdch_manager.h</summary>
+  <summary>
+    SDCH problem codes, listed in net/base/sdch_problem_code_list.h
+  </summary>
   <int value="1" label="ADDED_CONTENT_ENCODING"/>
   <int value="2" label="FIXED_CONTENT_ENCODING"/>
   <int value="3" label="FIXED_CONTENT_ENCODINGS"/>
@@ -53474,6 +53487,7 @@
   <int value="13" label="DICTIONARY_FOUND_HAS_WRONG_SCHEME"/>
   <int value="14" label="DICTIONARY_HASH_NOT_FOUND"/>
   <int value="15" label="DICTIONARY_HASH_MALFORMED"/>
+  <int value="16" label="DICTIONARY_FOUND_EXPIRED"/>
   <int value="20" label="DICTIONARY_HAS_NO_HEADER"/>
   <int value="21" label="DICTIONARY_HEADER_LINE_MISSING_COLON"/>
   <int value="22" label="DICTIONARY_MISSING_DOMAIN_SPECIFIER"/>
@@ -53482,15 +53496,21 @@
   <int value="25" label="DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL"/>
   <int value="26" label="DICTIONARY_HAS_NO_TEXT"/>
   <int value="27" label="DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX"/>
+  <int value="28" label="DICTIONARY_UNSUPPORTED_VERSION"/>
   <int value="30" label="DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST"/>
   <int value="31" label="DICTIONARY_SELECTED_FOR_SSL"/>
   <int value="32" label="DICTIONARY_ALREADY_LOADED"/>
   <int value="33" label="DICTIONARY_SELECTED_FROM_NON_HTTP"/>
   <int value="34" label="DICTIONARY_IS_TOO_LARGE"/>
   <int value="35" label="DICTIONARY_COUNT_EXCEEDED"/>
-  <int value="36" label="DICTIONARY_ALREADY_SCHEDULED_TO_DOWNLOAD"/>
-  <int value="37" label="DICTIONARY_ALREADY_TRIED_TO_DOWNLOAD"/>
+  <int value="36" label="defunct">
+    DICTIONARY_PREVIOUSLY_SCHEDULED_TO_DOWNLOAD used instead
+  </int>
+  <int value="37" label="defunct">
+    DICTIONARY_PREVIOUSLY_SCHEDULED_TO_DOWNLOAD used instead
+  </int>
   <int value="38" label="DICTIONARY_FETCH_READ_FAILED"/>
+  <int value="39" label="DICTIONARY_PREVIOUSLY_SCHEDULED_TO_DOWNLOAD"/>
   <int value="40" label="ATTEMPT_TO_DECODE_NON_HTTP_DATA"/>
   <int value="50" label="MULTIENCODING_FOR_NON_SDCH_REQUEST"/>
   <int value="51" label="SDCH_CONTENT_ENCODE_FOR_NON_SDCH_REQUEST"/>
@@ -53521,6 +53541,8 @@
   <int value="95" label="PRIOR_TO_DICTIONARY"/>
   <int value="96" label="DECODE_ERROR"/>
   <int value="100" label="LATENCY_TEST_DISALLOWED"/>
+  <int value="105" label="DISABLED"/>
+  <int value="106" label="SECURE_SCHEME_NOT_SUPPORTED"/>
 </enum>
 
 <enum name="SdchResponseCorruptionDetectionCauses" type="int">