diff --git a/BUILD.gn b/BUILD.gn
index 72893af1..f9c6907 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -897,6 +897,7 @@
     data_deps = [
       ":layout_test_data_mojo_bindings",
       "//content/shell:content_shell",
+      "//mojo/public/interfaces/bindings/tests:test_associated_interfaces",
       "//mojo/public/interfaces/bindings/tests:test_interfaces",
       "//third_party/WebKit/public:blink_devtools_frontend_resources_files",
       "//third_party/mesa:osmesa",
diff --git a/DEPS b/DEPS
index 88c054a5..382871a 100644
--- a/DEPS
+++ b/DEPS
@@ -44,7 +44,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '36a60914e44cddc57bc81aee75cd6bb56fa8a51d',
+  'v8_revision': '0512f9ad8a5fda58196ce7bd9f5ab1233507d7e2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -64,7 +64,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '350d2d904a3e6bd1e96542c5e223d301d9bdbe6a',
+  'pdfium_revision': '25694831670ef6172b1b9b71359a6c192e26da20',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -96,7 +96,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'b5b525957fba7bd8fce6908b02df49f0d7e0992c',
+  'catapult_revision': '4d4343808af0ecf365e4a789add07c30d17f6b0e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
diff --git a/ash/public/cpp/shell_window_ids.h b/ash/public/cpp/shell_window_ids.h
index 5f3ce13..ab5a168 100644
--- a/ash/public/cpp/shell_window_ids.h
+++ b/ash/public/cpp/shell_window_ids.h
@@ -113,7 +113,7 @@
   // The topmost container, used for power off animation.
   kShellWindowId_PowerButtonAnimationContainer,
 
-  kShellWindowId_Min = kShellWindowId_NonLockScreenContainersContainer,
+  kShellWindowId_Min = kShellWindowId_ScreenRotationContainer,
   kShellWindowId_Max = kShellWindowId_PowerButtonAnimationContainer,
 };
 
diff --git a/chrome/VERSION b/chrome/VERSION
index 9174e1f..a4ae5445 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=60
 MINOR=0
-BUILD=3078
+BUILD=3079
 PATCH=0
diff --git a/chrome/browser/resources/print_preview/previewarea/preview_area.js b/chrome/browser/resources/print_preview/previewarea/preview_area.js
index 522995c..a8f754e 100644
--- a/chrome/browser/resources/print_preview/previewarea/preview_area.js
+++ b/chrome/browser/resources/print_preview/previewarea/preview_area.js
@@ -484,11 +484,7 @@
      * @private
      */
     createPlugin_: function(srcUrl) {
-      if (this.plugin_) {
-        console.warn('Pdf preview plugin already created');
-        return;
-      }
-
+      assert(!this.plugin_);
       this.plugin_ = /** @type {print_preview.PDFPlugin} */(
           PDFCreateOutOfProcessPlugin(srcUrl));
       this.plugin_.setKeyEventCallback(this.keyEventCallback_);
@@ -503,15 +499,9 @@
       this.getChildElement('.preview-area-plugin-wrapper').
           appendChild(/** @type {Node} */(this.plugin_));
 
-
-      var pageNumbers =
-          this.printTicketStore_.pageRange.getPageNumberSet().asArray();
-      var grayscale = !this.printTicketStore_.color.getValue();
       this.plugin_.setLoadCallback(this.onPluginLoad_.bind(this));
       this.plugin_.setViewportChangedCallback(
           this.onPreviewVisualStateChange_.bind(this));
-      this.plugin_.resetPrintPreviewMode(srcUrl, grayscale, pageNumbers,
-                                         this.documentInfo_.isModifiable);
     },
 
     /**
@@ -569,14 +559,13 @@
       this.isPluginReloaded_ = false;
       if (!this.plugin_) {
         this.createPlugin_(event.previewUrl);
-      } else {
-        var grayscale = !this.printTicketStore_.color.getValue();
-        var pageNumbers =
-            this.printTicketStore_.pageRange.getPageNumberSet().asArray();
-        var url = event.previewUrl;
-        this.plugin_.resetPrintPreviewMode(url, grayscale, pageNumbers,
-                                           this.documentInfo_.isModifiable);
       }
+      this.plugin_.resetPrintPreviewMode(
+          event.previewUrl,
+          !this.printTicketStore_.color.getValue(),
+          this.printTicketStore_.pageRange.getPageNumberSet().asArray(),
+          this.documentInfo_.isModifiable);
+
       cr.dispatchSimpleEvent(
           this, PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS);
     },
diff --git a/chrome/browser/resources/settings/certificate_manager_page/certificate_entry.html b/chrome/browser/resources/settings/certificate_manager_page/certificate_entry.html
index 31cb15c..0b44004 100644
--- a/chrome/browser/resources/settings/certificate_manager_page/certificate_entry.html
+++ b/chrome/browser/resources/settings/certificate_manager_page/certificate_entry.html
@@ -1,6 +1,5 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
 <link rel="import" href="certificates_browser_proxy.html">
 <link rel="import" href="../settings_shared_css.html">
 <link rel="import" href="certificate_subentry.html">
@@ -15,16 +14,14 @@
       </cr-expand-button>
     </div>
     <template is="dom-if" if="[[expanded_]]">
-      <iron-collapse opened="[[expanded_]]" no-animation>
-        <div class="list-frame">
-          <template is="dom-repeat" items="[[model.subnodes]]">
-            <settings-certificate-subentry model="[[item]]"
-                certificate-type="[[certificateType]]"
-                is-last$="[[isLast_(index, model)]]">
-            </settings-certificate-subentry>
-          </template>
-        </div>
-      <iron-collapse>
+      <div class="list-frame">
+        <template is="dom-repeat" items="[[model.subnodes]]">
+          <settings-certificate-subentry model="[[item]]"
+              certificate-type="[[certificateType]]"
+              is-last$="[[isLast_(index, model)]]">
+          </settings-certificate-subentry>
+        </template>
+      </div>
     </template>
   </template>
   <script src="certificate_entry.js"></script>
diff --git a/chrome/browser/resources/settings/settings_page/compiled_resources2.gyp b/chrome/browser/resources/settings/settings_page/compiled_resources2.gyp
index f702f75..b008722 100644
--- a/chrome/browser/resources/settings/settings_page/compiled_resources2.gyp
+++ b/chrome/browser/resources/settings/settings_page/compiled_resources2.gyp
@@ -19,7 +19,10 @@
       'dependencies': [
         '../compiled_resources2.gyp:route',
         '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert',
+        '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr',
         '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data',
+        '<(DEPTH)/ui/webui/resources/js/cr/compiled_resources2.gyp:ui',
+        '<(DEPTH)/ui/webui/resources/js/cr/ui/compiled_resources2.gyp:focus_outline_manager',
       ],
       'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
     },
diff --git a/chrome/browser/resources/settings/settings_page/settings_animated_pages.html b/chrome/browser/resources/settings/settings_page/settings_animated_pages.html
index 42e1956..720893f 100644
--- a/chrome/browser/resources/settings/settings_page/settings_animated_pages.html
+++ b/chrome/browser/resources/settings/settings_page/settings_animated_pages.html
@@ -1,4 +1,7 @@
 <link rel="import" href="chrome://resources/html/assert.html">
+<link rel="import" href="chrome://resources/html/cr.html">
+<link rel="import" href="chrome://resources/html/cr/ui.html">
+<link rel="import" href="chrome://resources/html/cr/ui/focus_outline_manager.html">
 <link rel="import" href="chrome://resources/html/polymer.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/fade-in-animation.html">
diff --git a/chrome/browser/resources/settings/settings_page/settings_animated_pages.js b/chrome/browser/resources/settings/settings_page/settings_animated_pages.js
index 3da7e7a..b50d4fc 100644
--- a/chrome/browser/resources/settings/settings_page/settings_animated_pages.js
+++ b/chrome/browser/resources/settings/settings_page/settings_animated_pages.js
@@ -51,6 +51,11 @@
         this.lightDomChanged_.bind(this));
   },
 
+  /** @override */
+  attached: function() {
+    this.outline_ = cr.ui.FocusOutlineManager.forDocument(document);
+  },
+
   /**
    * @param {!Event} e
    * @private
@@ -82,7 +87,19 @@
       // the currentRouteChanged callback. Using 'iron-select' listener which
       // fires after the animation has finished allows focus() to work as
       // expected.
-      this.querySelector(selector).focus();
+      var toFocus = this.querySelector(selector);
+      var suppressInk = !this.outline_.visible;
+      var origNoInk;
+
+      if (suppressInk) {
+        origNoInk = toFocus.noink;
+        toFocus.noink = true;
+      }
+
+      toFocus.focus();
+
+      if (suppressInk)
+        toFocus.noink = origNoInk;
     }
   },
 
diff --git a/components/certificate_transparency/single_tree_tracker.cc b/components/certificate_transparency/single_tree_tracker.cc
index f3d151e..99fe13f 100644
--- a/components/certificate_transparency/single_tree_tracker.cc
+++ b/components/certificate_transparency/single_tree_tracker.cc
@@ -25,7 +25,6 @@
 #include "net/log/net_log.h"
 
 using net::SHA256HashValue;
-using net::ct::LogEntry;
 using net::ct::MerkleAuditProof;
 using net::ct::MerkleTreeLeaf;
 using net::ct::SignedCertificateTimestamp;
@@ -33,10 +32,6 @@
 
 // Overview of the process for auditing CT log entries
 //
-// A CT log entry, represented by net::ct::LogEntry, is made up of the
-// end-entity certificate and the Signed Certificate Timestamp associated with
-// it.
-//
 // In this file, obsered CT log entries are audited for inclusion in the CT log.
 // A pre-requirement for auditing a log entry is having a Signed Tree Head (STH)
 // from that log that is 24 hours (MMD period) after the timestamp in the SCT.
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 179d0ed..029b7420 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -223,9 +223,6 @@
     self.Fail('conformance2/glsl3/vector-dynamic-indexing-swizzled-lvalue.html',
         ['mac'], bug=709351)
     self.Fail('conformance2/rendering/' +
-        'blitframebuffer-resolve-to-back-buffer.html',
-        ['mac'], bug=699566)
-    self.Fail('conformance2/rendering/' +
         'framebuffer-completeness-unaffected.html',
         ['mac'], bug=630800)
     self.Fail('deqp/functional/gles3/fbocompleteness.html',
diff --git a/device/BUILD.gn b/device/BUILD.gn
index 1542a87..0b508417 100644
--- a/device/BUILD.gn
+++ b/device/BUILD.gn
@@ -74,8 +74,6 @@
     "sensors/sensor_manager_android_unittest.cc",
     "sensors/sensor_manager_chromeos_unittest.cc",
     "test/run_all_unittests.cc",
-    "u2f/u2f_apdu_unittest.cc",
-    "u2f/u2f_message_unittest.cc",
   ]
 
   deps = [
@@ -135,13 +133,20 @@
       "hid/test_report_descriptors.cc",
       "hid/test_report_descriptors.h",
       "serial/serial_io_handler_posix_unittest.cc",
+      "u2f/u2f_apdu_unittest.cc",
       "u2f/u2f_hid_device_unittest.cc",
+      "u2f/u2f_message_unittest.cc",
+      "u2f/u2f_register_unittest.cc",
+      "u2f/u2f_request_unittest.cc",
+      "u2f/u2f_sign_unittest.cc",
     ]
     deps += [
       "//device/hid",
       "//device/hid:mocks",
       "//device/serial",
       "//device/serial:test_support",
+      "//device/u2f",
+      "//device/u2f:mocks",
     ]
   }
 
@@ -171,7 +176,6 @@
     deps += [
       "//device/base",
       "//device/base:mocks",
-      "//device/u2f",
       "//device/usb",
       "//device/usb:test_support",
       "//device/usb/mojo",
diff --git a/device/geolocation/wifi_data_provider_common.cc b/device/geolocation/wifi_data_provider_common.cc
index 729efb60..527de27 100644
--- a/device/geolocation/wifi_data_provider_common.cc
+++ b/device/geolocation/wifi_data_provider_common.cc
@@ -13,7 +13,7 @@
 namespace device {
 
 base::string16 MacAddressAsString16(const uint8_t mac_as_int[6]) {
-  // mac_as_int is big-endian. Write in byte chunks.
+  // |mac_as_int| is big-endian. Write in byte chunks.
   // Format is XX-XX-XX-XX-XX-XX.
   static const char* const kMacFormatString = "%02x-%02x-%02x-%02x-%02x-%02x";
   return base::ASCIIToUTF16(base::StringPrintf(
@@ -27,17 +27,17 @@
 WifiDataProviderCommon::~WifiDataProviderCommon() {}
 
 void WifiDataProviderCommon::StartDataProvider() {
-  DCHECK(wlan_api_ == NULL);
+  DCHECK(!wlan_api_);
   wlan_api_.reset(NewWlanApi());
-  if (wlan_api_ == NULL) {
+  if (!wlan_api_) {
     // Error! Can't do scans, so don't try and schedule one.
     is_first_scan_complete_ = true;
     return;
   }
 
-  DCHECK(polling_policy_ == NULL);
+  DCHECK(!polling_policy_);
   polling_policy_.reset(NewPollingPolicy());
-  DCHECK(polling_policy_ != NULL);
+  DCHECK(polling_policy_);
 
   // Perform first scan ASAP regardless of the polling policy. If this scan
   // fails we'll retry at a rate in line with the polling policy.
diff --git a/device/geolocation/wifi_data_provider_common.h b/device/geolocation/wifi_data_provider_common.h
index 68f9c4c..a569af1e 100644
--- a/device/geolocation/wifi_data_provider_common.h
+++ b/device/geolocation/wifi_data_provider_common.h
@@ -50,9 +50,10 @@
  protected:
   ~WifiDataProviderCommon() override;
 
+  // TODO(mcasas): change return types and possibly names of these two methods,
+  // see https://crbug.com/714348.
   // Returns ownership.
   virtual WlanApiInterface* NewWlanApi() = 0;
-
   // Returns ownership.
   virtual WifiPollingPolicy* NewPollingPolicy() = 0;
 
diff --git a/device/geolocation/wifi_data_provider_common_unittest.cc b/device/geolocation/wifi_data_provider_common_unittest.cc
index eb3257e2..4e837b79 100644
--- a/device/geolocation/wifi_data_provider_common_unittest.cc
+++ b/device/geolocation/wifi_data_provider_common_unittest.cc
@@ -17,33 +17,28 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 using testing::_;
+using testing::AnyNumber;
 using testing::AtLeast;
-using testing::DoDefault;
+using testing::DoAll;
 using testing::Invoke;
+using testing::InvokeWithoutArgs;
 using testing::Return;
+using testing::SetArgPointee;
+using testing::WithArgs;
 
 namespace device {
 
 class MockWlanApi : public WifiDataProviderCommon::WlanApiInterface {
  public:
-  MockWlanApi() : calls_(0), bool_return_(true) {
-    ANNOTATE_BENIGN_RACE(&calls_, "This is a test-only data race on a counter");
+  MockWlanApi() {
     ON_CALL(*this, GetAccessPointData(_))
-        .WillByDefault(Invoke(this, &MockWlanApi::GetAccessPointDataInternal));
+        .WillByDefault(DoAll(SetArgPointee<0>(data_out_), Return(true)));
   }
 
   MOCK_METHOD1(GetAccessPointData, bool(WifiData::AccessPointDataSet* data));
 
-  int calls_;
-  bool bool_return_;
-  WifiData::AccessPointDataSet data_out_;
-
  private:
-  bool GetAccessPointDataInternal(WifiData::AccessPointDataSet* data) {
-    ++calls_;
-    *data = data_out_;
-    return bool_return_;
-  }
+  WifiData::AccessPointDataSet data_out_;
 };
 
 class MockPollingPolicy : public WifiPollingPolicy {
@@ -51,32 +46,29 @@
   MockPollingPolicy() {
     ON_CALL(*this, PollingInterval()).WillByDefault(Return(1));
     ON_CALL(*this, NoWifiInterval()).WillByDefault(Return(1));
+    // We are not interested in calls to UpdatePollingInterval() method.
+    EXPECT_CALL(*this, UpdatePollingInterval(_)).Times(AnyNumber());
   }
 
+  // WifiPollingPolicy implementation.
+  MOCK_METHOD1(UpdatePollingInterval, void(bool));
   MOCK_METHOD0(PollingInterval, int());
   MOCK_METHOD0(NoWifiInterval, int());
-
-  virtual void UpdatePollingInterval(bool) {}
 };
 
 class WifiDataProviderCommonWithMock : public WifiDataProviderCommon {
  public:
   WifiDataProviderCommonWithMock()
-      : new_wlan_api_(new MockWlanApi),
-        new_polling_policy_(new MockPollingPolicy) {}
+      : wlan_api_(new MockWlanApi), polling_policy_(new MockPollingPolicy) {}
 
   // WifiDataProviderCommon
-  WlanApiInterface* NewWlanApi() override {
-    CHECK(new_wlan_api_ != NULL);
-    return new_wlan_api_.release();
-  }
+  WlanApiInterface* NewWlanApi() override { return wlan_api_.release(); }
   WifiPollingPolicy* NewPollingPolicy() override {
-    CHECK(new_polling_policy_ != NULL);
-    return new_polling_policy_.release();
+    return polling_policy_.release();
   }
 
-  std::unique_ptr<MockWlanApi> new_wlan_api_;
-  std::unique_ptr<MockPollingPolicy> new_polling_policy_;
+  std::unique_ptr<MockWlanApi> wlan_api_;
+  std::unique_ptr<MockPollingPolicy> polling_policy_;
 
  private:
   ~WifiDataProviderCommonWithMock() override {}
@@ -84,119 +76,93 @@
   DISALLOW_COPY_AND_ASSIGN(WifiDataProviderCommonWithMock);
 };
 
-WifiDataProvider* CreateWifiDataProviderCommonWithMock() {
-  return new WifiDataProviderCommonWithMock;
-}
-
 // Main test fixture
 class GeolocationWifiDataProviderCommonTest : public testing::Test {
  public:
   GeolocationWifiDataProviderCommonTest()
-      : main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
-        wifi_data_callback_(
-            base::Bind(&GeolocationWifiDataProviderCommonTest::OnWifiDataUpdate,
-                       base::Unretained(this))) {}
+      : wifi_data_callback_(base::Bind(&base::DoNothing)),
+        provider_(new WifiDataProviderCommonWithMock),
+        wlan_api_(provider_->wlan_api_.get()),
+        polling_policy_(provider_->polling_policy_.get()) {}
 
   void SetUp() override {
-    provider_ = new WifiDataProviderCommonWithMock;
-    wlan_api_ = provider_->new_wlan_api_.get();
-    polling_policy_ = provider_->new_polling_policy_.get();
     provider_->AddCallback(&wifi_data_callback_);
   }
 
   void TearDown() override {
     provider_->RemoveCallback(&wifi_data_callback_);
     provider_->StopDataProvider();
-    provider_ = NULL;
-  }
-
-  void OnWifiDataUpdate() {
-    // Callbacks must run on the originating thread.
-    EXPECT_TRUE(main_task_runner_->BelongsToCurrentThread());
-    run_loop_->Quit();
-  }
-
-  void RunLoop() {
-    run_loop_.reset(new base::RunLoop());
-    run_loop_->Run();
   }
 
  protected:
-  base::MessageLoopForUI message_loop_;
-  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
-  std::unique_ptr<base::RunLoop> run_loop_;
+  const base::MessageLoopForUI message_loop_;
   WifiDataProviderManager::WifiDataUpdateCallback wifi_data_callback_;
-  scoped_refptr<WifiDataProviderCommonWithMock> provider_;
-  MockWlanApi* wlan_api_;
-  MockPollingPolicy* polling_policy_;
+  const scoped_refptr<WifiDataProviderCommonWithMock> provider_;
+
+  MockWlanApi* const wlan_api_;
+  MockPollingPolicy* const polling_policy_;
 };
 
 TEST_F(GeolocationWifiDataProviderCommonTest, CreateDestroy) {
   // Test fixture members were SetUp correctly.
-  EXPECT_TRUE(main_task_runner_->BelongsToCurrentThread());
-  EXPECT_TRUE(NULL != provider_.get());
-  EXPECT_TRUE(NULL != wlan_api_);
-}
-
-TEST_F(GeolocationWifiDataProviderCommonTest, RunNormal) {
-  EXPECT_CALL(*wlan_api_, GetAccessPointData(_)).Times(AtLeast(1));
-  EXPECT_CALL(*polling_policy_, PollingInterval()).Times(AtLeast(1));
-  provider_->StartDataProvider();
-  RunLoop();
-  SUCCEED();
+  EXPECT_TRUE(provider_);
+  EXPECT_TRUE(wlan_api_);
+  EXPECT_TRUE(polling_policy_);
 }
 
 TEST_F(GeolocationWifiDataProviderCommonTest, NoWifi) {
+  base::RunLoop run_loop;
   EXPECT_CALL(*polling_policy_, NoWifiInterval()).Times(AtLeast(1));
-  EXPECT_CALL(*wlan_api_, GetAccessPointData(_)).WillRepeatedly(Return(false));
+  EXPECT_CALL(*wlan_api_, GetAccessPointData(_))
+      .WillOnce(InvokeWithoutArgs([&run_loop]() {
+        run_loop.Quit();
+        return false;
+      }));
+
   provider_->StartDataProvider();
-  RunLoop();
+  run_loop.Run();
 }
 
 TEST_F(GeolocationWifiDataProviderCommonTest, IntermittentWifi) {
+  base::RunLoop run_loop;
   EXPECT_CALL(*polling_policy_, PollingInterval()).Times(AtLeast(1));
   EXPECT_CALL(*polling_policy_, NoWifiInterval()).Times(1);
   EXPECT_CALL(*wlan_api_, GetAccessPointData(_))
       .WillOnce(Return(true))
-      .WillOnce(Return(false))
-      .WillRepeatedly(DoDefault());
-
-  AccessPointData single_access_point;
-  single_access_point.channel = 2;
-  single_access_point.mac_address = 3;
-  single_access_point.radio_signal_strength = 4;
-  single_access_point.signal_to_noise = 5;
-  single_access_point.ssid = base::ASCIIToUTF16("foossid");
-  wlan_api_->data_out_.insert(single_access_point);
+      .WillOnce(InvokeWithoutArgs([&run_loop]() {
+        run_loop.Quit();
+        return false;
+      }));
 
   provider_->StartDataProvider();
-  RunLoop();
-  RunLoop();
+  run_loop.Run();
 }
 
-#if defined(OS_MACOSX)
-#define MAYBE_DoAnEmptyScan DISABLED_DoAnEmptyScan
-#else
-#define MAYBE_DoAnEmptyScan DoAnEmptyScan
-#endif
-TEST_F(GeolocationWifiDataProviderCommonTest, MAYBE_DoAnEmptyScan) {
-  EXPECT_CALL(*wlan_api_, GetAccessPointData(_)).Times(AtLeast(1));
+// This test runs StartDataProvider() and expects that GetAccessPointData() is
+// called. The retrieved WifiData is expected to be empty.
+TEST_F(GeolocationWifiDataProviderCommonTest, DoAnEmptyScan) {
+  base::RunLoop run_loop;
+
   EXPECT_CALL(*polling_policy_, PollingInterval()).Times(AtLeast(1));
+  EXPECT_CALL(*wlan_api_, GetAccessPointData(_))
+      .WillOnce(InvokeWithoutArgs([&run_loop]() {
+        run_loop.Quit();
+        return true;
+      }));
+
   provider_->StartDataProvider();
-  RunLoop();
-  EXPECT_EQ(wlan_api_->calls_, 1);
+  run_loop.Run();
+
   WifiData data;
   EXPECT_TRUE(provider_->GetData(&data));
-  EXPECT_EQ(0, static_cast<int>(data.access_point_data.size()));
+  EXPECT_TRUE(data.access_point_data.empty());
 }
 
-#if defined(OS_MACOSX)
-#define MAYBE_DoScanWithResults DISABLED_DoScanWithResults
-#else
-#define MAYBE_DoScanWithResults DoScanWithResults
-#endif
-TEST_F(GeolocationWifiDataProviderCommonTest, MAYBE_DoScanWithResults) {
-  EXPECT_CALL(*wlan_api_, GetAccessPointData(_)).Times(AtLeast(1));
+// This test runs StartDataProvider() and expects that GetAccessPointData() is
+// called. Some mock WifiData is returned then and expected to be retrieved.
+TEST_F(GeolocationWifiDataProviderCommonTest, DoScanWithResults) {
+  base::RunLoop run_loop;
+
   EXPECT_CALL(*polling_policy_, PollingInterval()).Times(AtLeast(1));
   AccessPointData single_access_point;
   single_access_point.channel = 2;
@@ -204,24 +170,24 @@
   single_access_point.radio_signal_strength = 4;
   single_access_point.signal_to_noise = 5;
   single_access_point.ssid = base::ASCIIToUTF16("foossid");
-  wlan_api_->data_out_.insert(single_access_point);
+
+  WifiData::AccessPointDataSet data_out({single_access_point});
+
+  EXPECT_CALL(*wlan_api_, GetAccessPointData(_))
+      .WillOnce(WithArgs<0>(
+          Invoke([&data_out, &run_loop](WifiData::AccessPointDataSet* data) {
+            *data = data_out;
+            run_loop.Quit();
+            return true;
+          })));
 
   provider_->StartDataProvider();
-  RunLoop();
-  EXPECT_EQ(wlan_api_->calls_, 1);
+  run_loop.Run();
+
   WifiData data;
   EXPECT_TRUE(provider_->GetData(&data));
-  EXPECT_EQ(1, static_cast<int>(data.access_point_data.size()));
+  ASSERT_EQ(1u, data.access_point_data.size());
   EXPECT_EQ(single_access_point.ssid, data.access_point_data.begin()->ssid);
 }
 
-TEST_F(GeolocationWifiDataProviderCommonTest, RegisterUnregister) {
-  WifiDataProviderManager::SetFactoryForTesting(
-      CreateWifiDataProviderCommonWithMock);
-  WifiDataProviderManager::Register(&wifi_data_callback_);
-  RunLoop();
-  WifiDataProviderManager::Unregister(&wifi_data_callback_);
-  WifiDataProviderManager::ResetFactoryForTesting();
-}
-
 }  // namespace device
diff --git a/device/u2f/BUILD.gn b/device/u2f/BUILD.gn
index 91fd6a2..0fcfe4b 100644
--- a/device/u2f/BUILD.gn
+++ b/device/u2f/BUILD.gn
@@ -19,6 +19,13 @@
     "u2f_message.h",
     "u2f_packet.cc",
     "u2f_packet.h",
+    "u2f_register.cc",
+    "u2f_register.h",
+    "u2f_request.cc",
+    "u2f_request.h",
+    "u2f_return_code.h",
+    "u2f_sign.cc",
+    "u2f_sign.h",
   ]
 
   deps = [
@@ -30,6 +37,21 @@
   ]
 }
 
+source_set("mocks") {
+  testonly = true
+
+  sources = [
+    "mock_u2f_device.cc",
+    "mock_u2f_device.h",
+  ]
+
+  deps = [
+    ":u2f",
+    "//base",
+    "//testing/gmock",
+  ]
+}
+
 fuzzer_test("u2f_apdu_fuzzer") {
   sources = [
     "u2f_apdu_fuzzer.cc",
diff --git a/device/u2f/mock_u2f_device.cc b/device/u2f/mock_u2f_device.cc
new file mode 100644
index 0000000..4f8bdc4
--- /dev/null
+++ b/device/u2f/mock_u2f_device.cc
@@ -0,0 +1,53 @@
+// Copyright 2017 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 "mock_u2f_device.h"
+
+namespace device {
+
+MockU2fDevice::MockU2fDevice() {}
+
+MockU2fDevice::~MockU2fDevice() {}
+
+void MockU2fDevice::DeviceTransact(std::unique_ptr<U2fApduCommand> command,
+                                   const DeviceCallback& cb) {
+  DeviceTransactPtr(command.get(), cb);
+}
+
+// static
+void MockU2fDevice::NotSatisfied(U2fApduCommand* cmd,
+                                 const DeviceCallback& cb) {
+  cb.Run(true, base::MakeUnique<U2fApduResponse>(
+                   std::vector<uint8_t>(),
+                   U2fApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED));
+}
+
+// static
+void MockU2fDevice::WrongData(U2fApduCommand* cmd, const DeviceCallback& cb) {
+  cb.Run(true,
+         base::MakeUnique<U2fApduResponse>(
+             std::vector<uint8_t>(), U2fApduResponse::Status::SW_WRONG_DATA));
+}
+
+// static
+void MockU2fDevice::NoErrorSign(U2fApduCommand* cmd, const DeviceCallback& cb) {
+  cb.Run(true, base::MakeUnique<U2fApduResponse>(
+                   std::vector<uint8_t>({kSign}),
+                   U2fApduResponse::Status::SW_NO_ERROR));
+}
+
+// static
+void MockU2fDevice::NoErrorRegister(U2fApduCommand* cmd,
+                                    const DeviceCallback& cb) {
+  cb.Run(true, base::MakeUnique<U2fApduResponse>(
+                   std::vector<uint8_t>({kRegister}),
+                   U2fApduResponse::Status::SW_NO_ERROR));
+}
+
+// static
+void MockU2fDevice::WinkDoNothing(const WinkCallback& cb) {
+  cb.Run();
+}
+
+}  // namespace device
diff --git a/device/u2f/mock_u2f_device.h b/device/u2f/mock_u2f_device.h
new file mode 100644
index 0000000..f4e65da
--- /dev/null
+++ b/device/u2f/mock_u2f_device.h
@@ -0,0 +1,43 @@
+// Copyright 2017 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 DEVICE_U2F_MOCK_U2F_DEVICE_H_
+#define DEVICE_U2F_MOCK_U2F_DEVICE_H_
+
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "u2f_apdu_command.h"
+#include "u2f_device.h"
+
+namespace device {
+
+class MockU2fDevice : public U2fDevice {
+ public:
+  static constexpr uint8_t kSign = 0x1;
+  static constexpr uint8_t kRegister = 0x5;
+
+  MockU2fDevice();
+  ~MockU2fDevice() override;
+
+  MOCK_METHOD1(TryWink, void(const WinkCallback& cb));
+  MOCK_METHOD0(GetId, std::string(void));
+  // GMock cannot mock a method taking a std::unique_ptr<T>
+  MOCK_METHOD2(DeviceTransactPtr,
+               void(U2fApduCommand* command, const DeviceCallback& cb));
+  void DeviceTransact(std::unique_ptr<U2fApduCommand> command,
+                      const DeviceCallback& cb) override;
+  static void TransactNoError(std::unique_ptr<U2fApduCommand> command,
+                              const DeviceCallback& cb);
+  static void NotSatisfied(U2fApduCommand* cmd, const DeviceCallback& cb);
+  static void WrongData(U2fApduCommand* cmd, const DeviceCallback& cb);
+  static void NoErrorSign(U2fApduCommand* cmd, const DeviceCallback& cb);
+  static void NoErrorRegister(U2fApduCommand* cmd, const DeviceCallback& cb);
+  static void WinkDoNothing(const WinkCallback& cb);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_U2F_MOCK_U2F_DEVICE_H_
diff --git a/device/u2f/u2f_apdu_response.h b/device/u2f/u2f_apdu_response.h
index 44bf7d6..2e5d74e 100644
--- a/device/u2f/u2f_apdu_response.h
+++ b/device/u2f/u2f_apdu_response.h
@@ -23,6 +23,7 @@
     SW_NO_ERROR = 0x9000,
     SW_CONDITIONS_NOT_SATISFIED = 0x6985,
     SW_WRONG_DATA = 0x6A80,
+    SW_WRONG_LENGTH = 0x6700,
   };
 
   U2fApduResponse(std::vector<uint8_t> message, Status response_status);
diff --git a/device/u2f/u2f_device.cc b/device/u2f/u2f_device.cc
index dc9e5ec..20575cf 100644
--- a/device/u2f/u2f_device.cc
+++ b/device/u2f/u2f_device.cc
@@ -20,7 +20,7 @@
   std::unique_ptr<U2fApduCommand> register_cmd =
       U2fApduCommand::CreateRegister(app_param, challenge_param);
   if (!register_cmd) {
-    callback.Run(ReturnCode::INVALID_PARAMS, std::vector<uint8_t>());
+    callback.Run(U2fReturnCode::INVALID_PARAMS, std::vector<uint8_t>());
     return;
   }
   DeviceTransact(std::move(register_cmd),
@@ -35,7 +35,7 @@
   std::unique_ptr<U2fApduCommand> sign_cmd =
       U2fApduCommand::CreateSign(app_param, challenge_param, key_handle);
   if (!sign_cmd) {
-    callback.Run(ReturnCode::INVALID_PARAMS, std::vector<uint8_t>());
+    callback.Run(U2fReturnCode::INVALID_PARAMS, std::vector<uint8_t>());
     return;
   }
   DeviceTransact(std::move(sign_cmd),
@@ -59,22 +59,22 @@
     bool success,
     std::unique_ptr<U2fApduResponse> register_response) {
   if (!success || !register_response) {
-    callback.Run(ReturnCode::FAILURE, std::vector<uint8_t>());
+    callback.Run(U2fReturnCode::FAILURE, std::vector<uint8_t>());
     return;
   }
   switch (register_response->status()) {
     case U2fApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED:
-      callback.Run(ReturnCode::CONDITIONS_NOT_SATISFIED,
+      callback.Run(U2fReturnCode::CONDITIONS_NOT_SATISFIED,
                    std::vector<uint8_t>());
       break;
     case U2fApduResponse::Status::SW_NO_ERROR:
-      callback.Run(ReturnCode::SUCCESS, register_response->data());
+      callback.Run(U2fReturnCode::SUCCESS, register_response->data());
       break;
     case U2fApduResponse::Status::SW_WRONG_DATA:
-      callback.Run(ReturnCode::INVALID_PARAMS, std::vector<uint8_t>());
+      callback.Run(U2fReturnCode::INVALID_PARAMS, std::vector<uint8_t>());
       break;
     default:
-      callback.Run(ReturnCode::FAILURE, std::vector<uint8_t>());
+      callback.Run(U2fReturnCode::FAILURE, std::vector<uint8_t>());
       break;
   }
 }
@@ -83,22 +83,21 @@
                                bool success,
                                std::unique_ptr<U2fApduResponse> sign_response) {
   if (!success || !sign_response) {
-    callback.Run(ReturnCode::FAILURE, std::vector<uint8_t>());
+    callback.Run(U2fReturnCode::FAILURE, std::vector<uint8_t>());
     return;
   }
   switch (sign_response->status()) {
     case U2fApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED:
-      callback.Run(ReturnCode::CONDITIONS_NOT_SATISFIED,
+      callback.Run(U2fReturnCode::CONDITIONS_NOT_SATISFIED,
                    std::vector<uint8_t>());
       break;
     case U2fApduResponse::Status::SW_NO_ERROR:
-      callback.Run(ReturnCode::SUCCESS, sign_response->data());
+      callback.Run(U2fReturnCode::SUCCESS, sign_response->data());
       break;
     case U2fApduResponse::Status::SW_WRONG_DATA:
-      callback.Run(ReturnCode::INVALID_PARAMS, std::vector<uint8_t>());
-      break;
+    case U2fApduResponse::Status::SW_WRONG_LENGTH:
     default:
-      callback.Run(ReturnCode::FAILURE, std::vector<uint8_t>());
+      callback.Run(U2fReturnCode::INVALID_PARAMS, std::vector<uint8_t>());
       break;
   }
 }
diff --git a/device/u2f/u2f_device.h b/device/u2f/u2f_device.h
index 06b9d2f..77c8133 100644
--- a/device/u2f/u2f_device.h
+++ b/device/u2f/u2f_device.h
@@ -10,6 +10,7 @@
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "u2f_apdu_response.h"
+#include "u2f_return_code.h"
 
 namespace device {
 
@@ -23,15 +24,9 @@
     U2F_V2,
     UNKNOWN,
   };
-  enum class ReturnCode : uint8_t {
-    SUCCESS,
-    FAILURE,
-    INVALID_PARAMS,
-    CONDITIONS_NOT_SATISFIED,
-  };
 
   using MessageCallback =
-      base::Callback<void(ReturnCode, std::vector<uint8_t>)>;
+      base::Callback<void(U2fReturnCode, std::vector<uint8_t>)>;
   using VersionCallback =
       base::Callback<void(bool success, ProtocolVersion version)>;
   using DeviceCallback =
diff --git a/device/u2f/u2f_register.cc b/device/u2f/u2f_register.cc
new file mode 100644
index 0000000..0f43e260
--- /dev/null
+++ b/device/u2f/u2f_register.cc
@@ -0,0 +1,61 @@
+// Copyright 2017 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 "u2f_register.h"
+
+#include "base/memory/ptr_util.h"
+
+namespace device {
+
+U2fRegister::U2fRegister(const std::vector<uint8_t>& challenge_hash,
+                         const std::vector<uint8_t>& app_param,
+                         const ResponseCallback& cb)
+    : U2fRequest(cb),
+      challenge_hash_(challenge_hash),
+      app_param_(app_param),
+      weak_factory_(this) {}
+
+U2fRegister::~U2fRegister() {}
+
+// static
+std::unique_ptr<U2fRequest> U2fRegister::TryRegistration(
+    const std::vector<uint8_t>& challenge_hash,
+    const std::vector<uint8_t>& app_param,
+    const ResponseCallback& cb) {
+  std::unique_ptr<U2fRequest> request =
+      base::MakeUnique<U2fRegister>(challenge_hash, app_param, cb);
+  request->Start();
+  return request;
+}
+
+void U2fRegister::TryDevice() {
+  DCHECK(current_device_);
+
+  current_device_->Register(
+      app_param_, challenge_hash_,
+      base::Bind(&U2fRegister::OnTryDevice, weak_factory_.GetWeakPtr()));
+}
+
+void U2fRegister::OnTryDevice(U2fReturnCode return_code,
+                              std::vector<uint8_t> response_data) {
+  switch (return_code) {
+    case U2fReturnCode::SUCCESS:
+      state_ = State::COMPLETE;
+      cb_.Run(return_code, response_data);
+      break;
+    case U2fReturnCode::CONDITIONS_NOT_SATISFIED:
+      // Waiting for user touch, move on and try this device later
+      state_ = State::IDLE;
+      Transition();
+      break;
+    default:
+      state_ = State::IDLE;
+      // An error has occured, quit trying this device
+      current_device_ = nullptr;
+      Transition();
+      break;
+  }
+}
+
+}  // namespace device
diff --git a/device/u2f/u2f_register.h b/device/u2f/u2f_register.h
new file mode 100644
index 0000000..466127ed
--- /dev/null
+++ b/device/u2f/u2f_register.h
@@ -0,0 +1,37 @@
+// Copyright 2017 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 DEVICE_U2F_U2F_REGISTER_H_
+#define DEVICE_U2F_U2F_REGISTER_H_
+
+#include <vector>
+
+#include "u2f_request.h"
+
+namespace device {
+
+class U2fRegister : public U2fRequest {
+ public:
+  U2fRegister(const std::vector<uint8_t>& challenge_hash,
+              const std::vector<uint8_t>& app_param,
+              const ResponseCallback& cb);
+  ~U2fRegister() override;
+
+  static std::unique_ptr<U2fRequest> TryRegistration(
+      const std::vector<uint8_t>& challenge_hash,
+      const std::vector<uint8_t>& app_param,
+      const ResponseCallback& cb);
+
+ private:
+  void TryDevice() override;
+  void OnTryDevice(U2fReturnCode, std::vector<uint8_t>);
+
+  std::vector<uint8_t> challenge_hash_;
+  std::vector<uint8_t> app_param_;
+  base::WeakPtrFactory<U2fRegister> weak_factory_;
+};
+
+}  // namespace device
+
+#endif  // DEVICE_U2F_U2F_REGISTER_H_
diff --git a/device/u2f/u2f_register_unittest.cc b/device/u2f/u2f_register_unittest.cc
new file mode 100644
index 0000000..2d0d2d4
--- /dev/null
+++ b/device/u2f/u2f_register_unittest.cc
@@ -0,0 +1,130 @@
+// Copyright 2017 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 <list>
+
+#include "base/run_loop.h"
+#include "base/test/test_io_thread.h"
+#include "device/base/mock_device_client.h"
+#include "device/hid/mock_hid_service.h"
+#include "device/test/test_device_client.h"
+#include "mock_u2f_device.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "u2f_register.h"
+
+namespace device {
+
+class U2fRegisterTest : public testing::Test {
+ public:
+  U2fRegisterTest() : io_thread_(base::TestIOThread::kAutoStart) {}
+
+  void SetUp() override {
+    MockHidService* hid_service = device_client_.hid_service();
+    hid_service->FirstEnumerationComplete();
+  }
+
+ protected:
+  base::MessageLoopForUI message_loop_;
+  base::TestIOThread io_thread_;
+  device::MockDeviceClient device_client_;
+};
+
+class TestRegisterCallback {
+ public:
+  TestRegisterCallback()
+      : callback_(base::Bind(&TestRegisterCallback::ReceivedCallback,
+                             base::Unretained(this))) {}
+  ~TestRegisterCallback() {}
+
+  void ReceivedCallback(U2fReturnCode status_code,
+                        std::vector<uint8_t> response) {
+    response_ = std::make_pair(status_code, response);
+    closure_.Run();
+  }
+
+  std::pair<U2fReturnCode, std::vector<uint8_t>>& WaitForCallback() {
+    closure_ = run_loop_.QuitClosure();
+    run_loop_.Run();
+    return response_;
+  }
+
+  const U2fRequest::ResponseCallback& callback() { return callback_; }
+
+ private:
+  std::pair<U2fReturnCode, std::vector<uint8_t>> response_;
+  base::Closure closure_;
+  U2fRequest::ResponseCallback callback_;
+  base::RunLoop run_loop_;
+};
+
+TEST_F(U2fRegisterTest, TestRegisterSuccess) {
+  std::unique_ptr<MockU2fDevice> device(new MockU2fDevice());
+  EXPECT_CALL(*device.get(), DeviceTransactPtr(testing::_, testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+  EXPECT_CALL(*device.get(), TryWink(testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+  TestRegisterCallback cb;
+  std::unique_ptr<U2fRequest> request = U2fRegister::TryRegistration(
+      std::vector<uint8_t>(32), std::vector<uint8_t>(32), cb.callback());
+  request->Start();
+  request->AddDeviceForTesting(std::move(device));
+  std::pair<U2fReturnCode, std::vector<uint8_t>>& response =
+      cb.WaitForCallback();
+  EXPECT_EQ(U2fReturnCode::SUCCESS, response.first);
+  ASSERT_LT(static_cast<size_t>(0), response.second.size());
+  EXPECT_EQ(static_cast<uint8_t>(MockU2fDevice::kRegister), response.second[0]);
+}
+
+TEST_F(U2fRegisterTest, TestDelayedSuccess) {
+  std::unique_ptr<MockU2fDevice> device(new MockU2fDevice());
+
+  // Go through the state machine twice before success
+  EXPECT_CALL(*device.get(), DeviceTransactPtr(testing::_, testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::NotSatisfied))
+      .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+  EXPECT_CALL(*device.get(), TryWink(testing::_))
+      .Times(2)
+      .WillRepeatedly(testing::Invoke(MockU2fDevice::WinkDoNothing));
+  TestRegisterCallback cb;
+
+  std::unique_ptr<U2fRequest> request = U2fRegister::TryRegistration(
+      std::vector<uint8_t>(32), std::vector<uint8_t>(32), cb.callback());
+  request->Start();
+  request->AddDeviceForTesting(std::move(device));
+  std::pair<U2fReturnCode, std::vector<uint8_t>>& response =
+      cb.WaitForCallback();
+  EXPECT_EQ(U2fReturnCode::SUCCESS, response.first);
+  ASSERT_LT(static_cast<size_t>(0), response.second.size());
+  EXPECT_EQ(static_cast<uint8_t>(MockU2fDevice::kRegister), response.second[0]);
+}
+
+TEST_F(U2fRegisterTest, TestMultipleDevices) {
+  // Second device will have a successful touch
+  std::unique_ptr<MockU2fDevice> device0(new MockU2fDevice());
+  std::unique_ptr<MockU2fDevice> device1(new MockU2fDevice());
+
+  EXPECT_CALL(*device0.get(), DeviceTransactPtr(testing::_, testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::NotSatisfied));
+  // One wink per device
+  EXPECT_CALL(*device0.get(), TryWink(testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+  EXPECT_CALL(*device1.get(), DeviceTransactPtr(testing::_, testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+  EXPECT_CALL(*device1.get(), TryWink(testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+
+  TestRegisterCallback cb;
+  std::unique_ptr<U2fRequest> request = U2fRegister::TryRegistration(
+      std::vector<uint8_t>(32), std::vector<uint8_t>(32), cb.callback());
+  request->Start();
+  request->AddDeviceForTesting(std::move(device0));
+  request->AddDeviceForTesting(std::move(device1));
+  std::pair<U2fReturnCode, std::vector<uint8_t>>& response =
+      cb.WaitForCallback();
+  EXPECT_EQ(U2fReturnCode::SUCCESS, response.first);
+  ASSERT_LT(static_cast<size_t>(0), response.second.size());
+  EXPECT_EQ(static_cast<uint8_t>(MockU2fDevice::kRegister), response.second[0]);
+}
+
+}  // namespace device
diff --git a/device/u2f/u2f_request.cc b/device/u2f/u2f_request.cc
new file mode 100644
index 0000000..a010352
--- /dev/null
+++ b/device/u2f/u2f_request.cc
@@ -0,0 +1,148 @@
+// Copyright 2017 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 "u2f_request.h"
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "device/base/device_client.h"
+#include "u2f_hid_device.h"
+
+namespace device {
+
+U2fRequest::U2fRequest(const ResponseCallback& cb)
+    : state_(State::INIT),
+      cb_(cb),
+      hid_service_observer_(this),
+      weak_factory_(this) {
+  filter_.SetUsagePage(0xf1d0);
+}
+
+void U2fRequest::Transition() {
+  switch (state_) {
+    case State::IDLE:
+      IterateDevice();
+      if (!current_device_) {
+        // No devices available
+        state_ = State::OFF;
+        break;
+      }
+      state_ = State::WINK;
+      current_device_->TryWink(
+          base::Bind(&U2fRequest::Transition, weak_factory_.GetWeakPtr()));
+      break;
+    case State::WINK:
+      state_ = State::BUSY;
+      TryDevice();
+    default:
+      break;
+  }
+}
+
+void U2fRequest::Start() {
+  if (state_ == State::INIT) {
+    state_ = State::BUSY;
+    Enumerate();
+  }
+}
+
+void U2fRequest::Enumerate() {
+  HidService* hid_service = DeviceClient::Get()->GetHidService();
+  DCHECK(hid_service);
+  hid_service_observer_.Add(hid_service);
+  hid_service->GetDevices(
+      base::Bind(&U2fRequest::OnEnumerate, weak_factory_.GetWeakPtr()));
+}
+
+void U2fRequest::OnEnumerate(
+    const std::vector<scoped_refptr<HidDeviceInfo>>& devices) {
+  for (auto device_info : devices) {
+    if (filter_.Matches(device_info))
+      devices_.push_back(base::MakeUnique<U2fHidDevice>(device_info));
+  }
+
+  state_ = State::IDLE;
+  Transition();
+}
+
+void U2fRequest::OnDeviceAdded(scoped_refptr<HidDeviceInfo> device_info) {
+  // Ignore non-U2F devices
+  if (!filter_.Matches(device_info))
+    return;
+
+  auto device = base::MakeUnique<U2fHidDevice>(device_info);
+  AddDevice(std::move(device));
+}
+
+void U2fRequest::OnDeviceRemoved(scoped_refptr<HidDeviceInfo> device_info) {
+  // Ignore non-U2F devices
+  if (!filter_.Matches(device_info))
+    return;
+
+  auto device = base::MakeUnique<U2fHidDevice>(device_info);
+
+  // Check if the active device was removed
+  if (current_device_ && current_device_->GetId() == device->GetId()) {
+    current_device_ = nullptr;
+    state_ = State::IDLE;
+    Transition();
+    return;
+  }
+
+  // Remove the device if it exists in either device list
+  devices_.remove_if([&device](const std::unique_ptr<U2fDevice>& this_device) {
+    return this_device->GetId() == device->GetId();
+  });
+  attempted_devices_.remove_if(
+      [&device](const std::unique_ptr<U2fDevice>& this_device) {
+        return this_device->GetId() == device->GetId();
+      });
+}
+
+void U2fRequest::IterateDevice() {
+  // Move active device to attempted device list
+  if (current_device_)
+    attempted_devices_.push_back(std::move(current_device_));
+
+  // If there is an additional device on device list, make it active.
+  // Otherwise, if all devices have been tried, move attempted devices back to
+  // the main device list.
+  if (devices_.size() > 0) {
+    current_device_ = std::move(devices_.front());
+    devices_.pop_front();
+  } else if (attempted_devices_.size() > 0) {
+    devices_ = std::move(attempted_devices_);
+    // After trying every device, wait 200ms before trying again
+    delay_callback_.Reset(
+        base::Bind(&U2fRequest::OnWaitComplete, weak_factory_.GetWeakPtr()));
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, delay_callback_.callback(),
+        base::TimeDelta::FromMilliseconds(200));
+  }
+}
+
+void U2fRequest::OnWaitComplete() {
+  state_ = State::IDLE;
+  Transition();
+}
+
+void U2fRequest::AddDevice(std::unique_ptr<U2fDevice> device) {
+  devices_.push_back(std::move(device));
+
+  // Start the state machine if this is the only device
+  if (state_ == State::OFF) {
+    state_ = State::IDLE;
+    delay_callback_.Cancel();
+    Transition();
+  }
+}
+
+void U2fRequest::AddDeviceForTesting(std::unique_ptr<U2fDevice> device) {
+  AddDevice(std::move(device));
+}
+
+U2fRequest::~U2fRequest() {}
+
+}  // namespace device
diff --git a/device/u2f/u2f_request.h b/device/u2f/u2f_request.h
new file mode 100644
index 0000000..902a2ca
--- /dev/null
+++ b/device/u2f/u2f_request.h
@@ -0,0 +1,65 @@
+// Copyright 2017 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 DEVICE_U2F_U2F_REQUEST_H_
+#define DEVICE_U2F_U2F_REQUEST_H_
+
+#include "base/cancelable_callback.h"
+#include "base/scoped_observer.h"
+#include "device/hid/hid_device_filter.h"
+#include "device/hid/hid_service.h"
+#include "u2f_device.h"
+
+namespace device {
+class U2fRequest : HidService::Observer {
+ public:
+  using ResponseCallback = base::Callback<void(U2fReturnCode status_code,
+                                               std::vector<uint8_t> response)>;
+
+  U2fRequest(const ResponseCallback& callback);
+  virtual ~U2fRequest();
+
+  void Start();
+  void AddDeviceForTesting(std::unique_ptr<U2fDevice> device);
+
+ protected:
+  enum class State {
+    INIT,
+    BUSY,
+    WINK,
+    IDLE,
+    OFF,
+    COMPLETE,
+  };
+
+  void Transition();
+  virtual void TryDevice() = 0;
+
+  std::unique_ptr<U2fDevice> current_device_;
+  State state_;
+  ResponseCallback cb_;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(U2fRequestTest, TestAddRemoveDevice);
+  FRIEND_TEST_ALL_PREFIXES(U2fRequestTest, TestIterateDevice);
+  FRIEND_TEST_ALL_PREFIXES(U2fRequestTest, TestBasicMachine);
+
+  void Enumerate();
+  void IterateDevice();
+  void OnWaitComplete();
+  void AddDevice(std::unique_ptr<U2fDevice> device);
+  void OnDeviceAdded(scoped_refptr<HidDeviceInfo> device_info) override;
+  void OnDeviceRemoved(scoped_refptr<HidDeviceInfo> device_info) override;
+  void OnEnumerate(const std::vector<scoped_refptr<HidDeviceInfo>>& devices);
+
+  std::list<std::unique_ptr<U2fDevice>> devices_;
+  std::list<std::unique_ptr<U2fDevice>> attempted_devices_;
+  base::CancelableClosure delay_callback_;
+  HidDeviceFilter filter_;
+  ScopedObserver<HidService, HidService::Observer> hid_service_observer_;
+  base::WeakPtrFactory<U2fRequest> weak_factory_;
+};
+}  // namespace device
+
+#endif  // DEVICE_U2F_U2F_REQUEST_H_
diff --git a/device/u2f/u2f_request_unittest.cc b/device/u2f/u2f_request_unittest.cc
new file mode 100644
index 0000000..53c2130a
--- /dev/null
+++ b/device/u2f/u2f_request_unittest.cc
@@ -0,0 +1,159 @@
+// Copyright 2017 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 <list>
+
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/test/test_io_thread.h"
+#include "device/base/mock_device_client.h"
+#include "device/hid/mock_hid_service.h"
+#include "device/test/test_device_client.h"
+#include "device/test/usb_test_gadget.h"
+#include "device/u2f/u2f_hid_device.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "u2f_request.h"
+
+namespace device {
+namespace {
+#if defined(OS_MACOSX)
+const uint64_t kTestDeviceId0 = 42;
+const uint64_t kTestDeviceId1 = 43;
+const uint64_t kTestDeviceId2 = 44;
+#else
+const char* kTestDeviceId0 = "device0";
+const char* kTestDeviceId1 = "device1";
+const char* kTestDeviceId2 = "device2";
+#endif
+
+class FakeU2fRequest : public U2fRequest {
+ public:
+  FakeU2fRequest(const ResponseCallback& cb) : U2fRequest(cb) {}
+  ~FakeU2fRequest() override {}
+
+  void TryDevice() override {
+    cb_.Run(U2fReturnCode::SUCCESS, std::vector<uint8_t>());
+  }
+};
+}  // namespace
+
+class TestResponseCallback {
+ public:
+  TestResponseCallback()
+      : callback_(base::Bind(&TestResponseCallback::ReceivedCallback,
+                             base::Unretained(this))) {}
+  ~TestResponseCallback() {}
+
+  void ReceivedCallback(U2fReturnCode status, std::vector<uint8_t> data) {
+    closure_.Run();
+  }
+
+  void WaitForCallback() {
+    closure_ = run_loop_.QuitClosure();
+    run_loop_.Run();
+  }
+
+  U2fRequest::ResponseCallback& callback() { return callback_; }
+
+ private:
+  base::Closure closure_;
+  U2fRequest::ResponseCallback callback_;
+  base::RunLoop run_loop_;
+};
+
+class U2fRequestTest : public testing::Test {
+ public:
+  U2fRequestTest() : io_thread_(base::TestIOThread::kAutoStart) {}
+
+ protected:
+  base::MessageLoopForUI message_loop_;
+  base::TestIOThread io_thread_;
+  device::MockDeviceClient device_client_;
+};
+
+TEST_F(U2fRequestTest, TestAddRemoveDevice) {
+  MockHidService* hid_service = device_client_.hid_service();
+  HidCollectionInfo c_info;
+  hid_service->FirstEnumerationComplete();
+
+  TestResponseCallback cb;
+  FakeU2fRequest request(cb.callback());
+  request.Enumerate();
+  EXPECT_EQ(static_cast<size_t>(0), request.devices_.size());
+
+  // Add one U2F device
+  c_info.usage = HidUsageAndPage(1, static_cast<HidUsageAndPage::Page>(0xf1d0));
+  scoped_refptr<HidDeviceInfo> u2f_device = make_scoped_refptr(
+      new HidDeviceInfo(kTestDeviceId0, 0, 0, "Test Fido Device", "123FIDO",
+                        kHIDBusTypeUSB, c_info, 64, 64, 0));
+  hid_service->AddDevice(u2f_device);
+  EXPECT_EQ(static_cast<size_t>(1), request.devices_.size());
+
+  // Add one non-U2F device. Verify that it is not added to our device list.
+  scoped_refptr<HidDeviceInfo> other_device = make_scoped_refptr(
+      new HidDeviceInfo(kTestDeviceId2, 0, 0, "Other Device", "OtherDevice",
+                        kHIDBusTypeUSB, std::vector<uint8_t>()));
+  hid_service->AddDevice(other_device);
+  EXPECT_EQ(static_cast<size_t>(1), request.devices_.size());
+
+  // Remove the non-U2F device and verify that device list was unchanged.
+  hid_service->RemoveDevice(kTestDeviceId2);
+  EXPECT_EQ(static_cast<size_t>(1), request.devices_.size());
+
+  // Remove the U2F device and verify that device list is empty.
+  hid_service->RemoveDevice(kTestDeviceId0);
+  EXPECT_EQ(static_cast<size_t>(0), request.devices_.size());
+}
+
+TEST_F(U2fRequestTest, TestIterateDevice) {
+  TestResponseCallback cb;
+  FakeU2fRequest request(cb.callback());
+  HidCollectionInfo c_info;
+  // Add one U2F device and one non-U2f device
+  c_info.usage = HidUsageAndPage(1, static_cast<HidUsageAndPage::Page>(0xf1d0));
+  scoped_refptr<HidDeviceInfo> device0 = make_scoped_refptr(
+      new HidDeviceInfo(kTestDeviceId0, 0, 0, "Test Fido Device", "123FIDO",
+                        kHIDBusTypeUSB, c_info, 64, 64, 0));
+  request.devices_.push_back(base::MakeUnique<U2fHidDevice>(device0));
+  scoped_refptr<HidDeviceInfo> device1 = make_scoped_refptr(
+      new HidDeviceInfo(kTestDeviceId1, 0, 0, "Test Fido Device", "123FIDO",
+                        kHIDBusTypeUSB, c_info, 64, 64, 0));
+  request.devices_.push_back(base::MakeUnique<U2fHidDevice>(device1));
+
+  // Move first device to current
+  request.IterateDevice();
+  ASSERT_NE(nullptr, request.current_device_);
+  EXPECT_EQ(static_cast<size_t>(1), request.devices_.size());
+
+  // Move second device to current, first to attempted
+  request.IterateDevice();
+  ASSERT_NE(nullptr, request.current_device_);
+  EXPECT_EQ(static_cast<size_t>(1), request.attempted_devices_.size());
+
+  // Move second device from current to attempted, move attempted to devices as
+  // all devices have been attempted
+  request.IterateDevice();
+  ASSERT_EQ(nullptr, request.current_device_);
+  EXPECT_EQ(static_cast<size_t>(2), request.devices_.size());
+  EXPECT_EQ(static_cast<size_t>(0), request.attempted_devices_.size());
+}
+
+TEST_F(U2fRequestTest, TestBasicMachine) {
+  MockHidService* hid_service = device_client_.hid_service();
+  hid_service->FirstEnumerationComplete();
+  TestResponseCallback cb;
+  FakeU2fRequest request(cb.callback());
+  request.Start();
+  // Add one U2F device
+  HidCollectionInfo c_info;
+  c_info.usage = HidUsageAndPage(1, static_cast<HidUsageAndPage::Page>(0xf1d0));
+  scoped_refptr<HidDeviceInfo> u2f_device = make_scoped_refptr(
+      new HidDeviceInfo(kTestDeviceId0, 0, 0, "Test Fido Device", "123FIDO",
+                        kHIDBusTypeUSB, c_info, 64, 64, 0));
+  hid_service->AddDevice(u2f_device);
+  cb.WaitForCallback();
+  EXPECT_EQ(U2fRequest::State::BUSY, request.state_);
+}
+
+}  // namespace device
diff --git a/device/u2f/u2f_return_code.h b/device/u2f/u2f_return_code.h
new file mode 100644
index 0000000..4aa0b42
--- /dev/null
+++ b/device/u2f/u2f_return_code.h
@@ -0,0 +1,19 @@
+// Copyright 2017 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 DEVICE_U2F_U2F_RETURN_CODE_H_
+#define DEVICE_U2F_U2F_RETURN_CODE_H_
+
+namespace device {
+
+enum class U2fReturnCode : uint8_t {
+  SUCCESS,
+  FAILURE,
+  INVALID_PARAMS,
+  CONDITIONS_NOT_SATISFIED,
+};
+
+}  // namespace device
+
+#endif  // DEVICE_U2F_U2F_RETURN_CODE_H_
diff --git a/device/u2f/u2f_sign.cc b/device/u2f/u2f_sign.cc
new file mode 100644
index 0000000..d537c48
--- /dev/null
+++ b/device/u2f/u2f_sign.cc
@@ -0,0 +1,92 @@
+// Copyright 2017 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 "u2f_sign.h"
+
+#include "base/memory/ptr_util.h"
+
+namespace device {
+
+U2fSign::U2fSign(const std::vector<std::vector<uint8_t>>& registered_keys,
+                 const std::vector<uint8_t>& challenge_hash,
+                 const std::vector<uint8_t>& app_param,
+                 const ResponseCallback& cb)
+    : U2fRequest(cb),
+      registered_keys_(registered_keys),
+      challenge_hash_(challenge_hash),
+      app_param_(app_param),
+      weak_factory_(this) {}
+
+U2fSign::~U2fSign() {}
+
+// static
+std::unique_ptr<U2fRequest> U2fSign::TrySign(
+    const std::vector<std::vector<uint8_t>>& registered_keys,
+    const std::vector<uint8_t>& challenge_hash,
+    const std::vector<uint8_t>& app_param,
+    const ResponseCallback& cb) {
+  std::unique_ptr<U2fRequest> request =
+      base::MakeUnique<U2fSign>(registered_keys, challenge_hash, app_param, cb);
+  request->Start();
+  return request;
+}
+
+void U2fSign::TryDevice() {
+  DCHECK(current_device_);
+
+  if (registered_keys_.size() == 0) {
+    // Send registration (Fake enroll) if no keys were provided
+    current_device_->Register(
+        kBogusAppParam, kBogusChallenge,
+        base::Bind(&U2fSign::OnTryDevice, weak_factory_.GetWeakPtr(),
+                   registered_keys_.cbegin()));
+    return;
+  }
+  // Try signing current device with the first registered key
+  auto it = registered_keys_.cbegin();
+  current_device_->Sign(
+      app_param_, challenge_hash_, *it,
+      base::Bind(&U2fSign::OnTryDevice, weak_factory_.GetWeakPtr(), it));
+}
+
+void U2fSign::OnTryDevice(std::vector<std::vector<uint8_t>>::const_iterator it,
+                          U2fReturnCode return_code,
+                          std::vector<uint8_t> response_data) {
+  switch (return_code) {
+    case U2fReturnCode::SUCCESS:
+      state_ = State::COMPLETE;
+      cb_.Run(return_code, response_data);
+      break;
+    case U2fReturnCode::CONDITIONS_NOT_SATISFIED: {
+      // Key handle is accepted by this device, but waiting on user touch. Move
+      // on and try this device again later.
+      state_ = State::IDLE;
+      Transition();
+      break;
+    }
+    case U2fReturnCode::INVALID_PARAMS:
+      if (++it != registered_keys_.end()) {
+        // Key is not for this device. Try signing with the next key.
+        current_device_->Sign(
+            app_param_, challenge_hash_, *it,
+            base::Bind(&U2fSign::OnTryDevice, weak_factory_.GetWeakPtr(), it));
+      } else {
+        // No provided key was accepted by this device. Send registration
+        // (Fake enroll) request to device.
+        current_device_->Register(
+            kBogusAppParam, kBogusChallenge,
+            base::Bind(&U2fSign::OnTryDevice, weak_factory_.GetWeakPtr(),
+                       registered_keys_.cbegin()));
+      }
+      break;
+    default:
+      // Some sort of failure occured. Abandon this device and move on.
+      state_ = State::IDLE;
+      current_device_ = nullptr;
+      Transition();
+      break;
+  }
+}
+
+}  // namespace device
diff --git a/device/u2f/u2f_sign.h b/device/u2f/u2f_sign.h
new file mode 100644
index 0000000..e924137
--- /dev/null
+++ b/device/u2f/u2f_sign.h
@@ -0,0 +1,51 @@
+// Copyright 2017 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 DEVICE_U2F_U2F_SIGN_H_
+#define DEVICE_U2F_U2F_SIGN_H_
+
+#include <vector>
+
+#include "u2f_request.h"
+
+namespace device {
+
+class U2fSign : public U2fRequest {
+ public:
+  U2fSign(const std::vector<std::vector<uint8_t>>& registered_keys,
+          const std::vector<uint8_t>& challenge_hash,
+          const std::vector<uint8_t>& app_param,
+          const ResponseCallback& cb);
+  ~U2fSign() override;
+
+  static std::unique_ptr<U2fRequest> TrySign(
+      const std::vector<std::vector<uint8_t>>& registered_keys,
+      const std::vector<uint8_t>& challenge_hash,
+      const std::vector<uint8_t>& app_param,
+      const ResponseCallback& cb);
+
+ private:
+  void TryDevice() override;
+  void OnTryDevice(std::vector<std::vector<uint8_t>>::const_iterator,
+                   U2fReturnCode,
+                   std::vector<uint8_t>);
+
+  const std::vector<std::vector<uint8_t>> registered_keys_;
+  std::vector<uint8_t> challenge_hash_;
+  std::vector<uint8_t> app_param_;
+  const std::vector<uint8_t> kBogusAppParam = {
+      0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+      0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+      0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41};
+  const std::vector<uint8_t> kBogusChallenge = {
+      0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+      0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+      0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42};
+
+  base::WeakPtrFactory<U2fSign> weak_factory_;
+};
+
+}  // namespace device
+
+#endif  // DEVICE_U2F_U2F_SIGN_H_
diff --git a/device/u2f/u2f_sign_unittest.cc b/device/u2f/u2f_sign_unittest.cc
new file mode 100644
index 0000000..8c846217
--- /dev/null
+++ b/device/u2f/u2f_sign_unittest.cc
@@ -0,0 +1,215 @@
+// Copyright 2017 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 <list>
+
+#include "base/run_loop.h"
+#include "base/test/test_io_thread.h"
+#include "device/base/mock_device_client.h"
+#include "device/hid/mock_hid_service.h"
+#include "device/test/test_device_client.h"
+#include "mock_u2f_device.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "u2f_sign.h"
+
+namespace device {
+class U2fSignTest : public testing::Test {
+ public:
+  U2fSignTest() : io_thread_(base::TestIOThread::kAutoStart) {}
+
+  void SetUp() override {
+    MockHidService* hid_service = device_client_.hid_service();
+    hid_service->FirstEnumerationComplete();
+  }
+
+ protected:
+  base::MessageLoopForUI message_loop_;
+  base::TestIOThread io_thread_;
+  device::MockDeviceClient device_client_;
+};
+
+class TestSignCallback {
+ public:
+  TestSignCallback()
+      : callback_(base::Bind(&TestSignCallback::ReceivedCallback,
+                             base::Unretained(this))) {}
+  ~TestSignCallback() {}
+
+  void ReceivedCallback(U2fReturnCode status_code,
+                        std::vector<uint8_t> response) {
+    response_ = std::make_pair(status_code, response);
+    closure_.Run();
+  }
+
+  std::pair<U2fReturnCode, std::vector<uint8_t>>& WaitForCallback() {
+    closure_ = run_loop_.QuitClosure();
+    run_loop_.Run();
+    return response_;
+  }
+
+  const U2fRequest::ResponseCallback& callback() { return callback_; }
+
+ private:
+  std::pair<U2fReturnCode, std::vector<uint8_t>> response_;
+  base::Closure closure_;
+  U2fRequest::ResponseCallback callback_;
+  base::RunLoop run_loop_;
+};
+
+TEST_F(U2fSignTest, TestSignSuccess) {
+  std::vector<uint8_t> key(32, 0xA);
+  std::vector<std::vector<uint8_t>> handles = {key};
+  std::unique_ptr<MockU2fDevice> device(new MockU2fDevice());
+  EXPECT_CALL(*device.get(), DeviceTransactPtr(testing::_, testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::NoErrorSign));
+  EXPECT_CALL(*device.get(), TryWink(testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+  TestSignCallback cb;
+  std::unique_ptr<U2fRequest> request =
+      U2fSign::TrySign(handles, std::vector<uint8_t>(32),
+                       std::vector<uint8_t>(32), cb.callback());
+  request->Start();
+  request->AddDeviceForTesting(std::move(device));
+  std::pair<U2fReturnCode, std::vector<uint8_t>>& response =
+      cb.WaitForCallback();
+  EXPECT_EQ(U2fReturnCode::SUCCESS, response.first);
+  // Correct key was sent so a sign response is expected
+  ASSERT_LT(static_cast<size_t>(0), response.second.size());
+  EXPECT_EQ(static_cast<uint8_t>(MockU2fDevice::kSign), response.second[0]);
+}
+
+TEST_F(U2fSignTest, TestDelayedSuccess) {
+  std::vector<uint8_t> key(32, 0xA);
+  std::vector<std::vector<uint8_t>> handles = {key};
+  std::unique_ptr<MockU2fDevice> device(new MockU2fDevice());
+
+  // Go through the state machine twice before success
+  EXPECT_CALL(*device.get(), DeviceTransactPtr(testing::_, testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::NotSatisfied))
+      .WillOnce(testing::Invoke(MockU2fDevice::NoErrorSign));
+  EXPECT_CALL(*device.get(), TryWink(testing::_))
+      .Times(2)
+      .WillRepeatedly(testing::Invoke(MockU2fDevice::WinkDoNothing));
+  TestSignCallback cb;
+
+  std::unique_ptr<U2fRequest> request =
+      U2fSign::TrySign(handles, std::vector<uint8_t>(32),
+                       std::vector<uint8_t>(32), cb.callback());
+  request->Start();
+  request->AddDeviceForTesting(std::move(device));
+  std::pair<U2fReturnCode, std::vector<uint8_t>>& response =
+      cb.WaitForCallback();
+  EXPECT_EQ(U2fReturnCode::SUCCESS, response.first);
+  // Correct key was sent so a sign response is expected
+  ASSERT_LT(static_cast<size_t>(0), response.second.size());
+  EXPECT_EQ(static_cast<uint8_t>(MockU2fDevice::kSign), response.second[0]);
+}
+
+TEST_F(U2fSignTest, TestMultipleHandles) {
+  std::vector<uint8_t> key(32, 0xA);
+  std::vector<uint8_t> wrong_key0(32, 0xB);
+  std::vector<uint8_t> wrong_key1(32, 0xC);
+  std::vector<uint8_t> wrong_key2(32, 0xD);
+  // Put wrong key first to ensure that it will be tested before the correct key
+  std::vector<std::vector<uint8_t>> handles = {wrong_key0, wrong_key1,
+                                               wrong_key2, key};
+  std::unique_ptr<MockU2fDevice> device(new MockU2fDevice());
+
+  // Wrong key would respond with SW_WRONG_DATA
+  EXPECT_CALL(*device.get(), DeviceTransactPtr(testing::_, testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+      .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+      .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+      .WillOnce(testing::Invoke(MockU2fDevice::NoErrorSign));
+  // Only one wink expected per device
+  EXPECT_CALL(*device.get(), TryWink(testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+
+  TestSignCallback cb;
+  std::unique_ptr<U2fRequest> request =
+      U2fSign::TrySign(handles, std::vector<uint8_t>(32),
+                       std::vector<uint8_t>(32), cb.callback());
+  request->Start();
+  request->AddDeviceForTesting(std::move(device));
+  std::pair<U2fReturnCode, std::vector<uint8_t>>& response =
+      cb.WaitForCallback();
+  EXPECT_EQ(U2fReturnCode::SUCCESS, response.first);
+  // Correct key was sent so a sign response is expected
+  ASSERT_LT(static_cast<size_t>(0), response.second.size());
+  EXPECT_EQ(static_cast<uint8_t>(MockU2fDevice::kSign), response.second[0]);
+}
+
+TEST_F(U2fSignTest, TestMultipleDevices) {
+  std::vector<uint8_t> key0(32, 0xA);
+  std::vector<uint8_t> key1(32, 0xB);
+  // Second device will have a successful touch
+  std::vector<std::vector<uint8_t>> handles = {key0, key1};
+  std::unique_ptr<MockU2fDevice> device0(new MockU2fDevice());
+  std::unique_ptr<MockU2fDevice> device1(new MockU2fDevice());
+
+  EXPECT_CALL(*device0.get(), DeviceTransactPtr(testing::_, testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+      .WillOnce(testing::Invoke(MockU2fDevice::NotSatisfied));
+  // One wink per device
+  EXPECT_CALL(*device0.get(), TryWink(testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+  EXPECT_CALL(*device1.get(), DeviceTransactPtr(testing::_, testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::NoErrorSign));
+  EXPECT_CALL(*device1.get(), TryWink(testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+
+  TestSignCallback cb;
+  std::unique_ptr<U2fRequest> request =
+      U2fSign::TrySign(handles, std::vector<uint8_t>(32),
+                       std::vector<uint8_t>(32), cb.callback());
+  request->Start();
+  request->AddDeviceForTesting(std::move(device0));
+  request->AddDeviceForTesting(std::move(device1));
+  std::pair<U2fReturnCode, std::vector<uint8_t>>& response =
+      cb.WaitForCallback();
+  EXPECT_EQ(U2fReturnCode::SUCCESS, response.first);
+  // Correct key was sent so a sign response is expected
+  ASSERT_LT(static_cast<size_t>(0), response.second.size());
+  EXPECT_EQ(static_cast<uint8_t>(MockU2fDevice::kSign), response.second[0]);
+}
+
+TEST_F(U2fSignTest, TestFakeEnroll) {
+  std::vector<uint8_t> key0(32, 0xA);
+  std::vector<uint8_t> key1(32, 0xB);
+  // Second device will be have a successful touch
+  std::vector<std::vector<uint8_t>> handles = {key0, key1};
+  std::unique_ptr<MockU2fDevice> device0(new MockU2fDevice());
+  std::unique_ptr<MockU2fDevice> device1(new MockU2fDevice());
+
+  EXPECT_CALL(*device0.get(), DeviceTransactPtr(testing::_, testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+      .WillOnce(testing::Invoke(MockU2fDevice::NotSatisfied));
+  // One wink per device
+  EXPECT_CALL(*device0.get(), TryWink(testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+  // Both keys will be tried, when both fail, register is tried on that device
+  EXPECT_CALL(*device1.get(), DeviceTransactPtr(testing::_, testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+      .WillOnce(testing::Invoke(MockU2fDevice::WrongData))
+      .WillOnce(testing::Invoke(MockU2fDevice::NoErrorRegister));
+  EXPECT_CALL(*device1.get(), TryWink(testing::_))
+      .WillOnce(testing::Invoke(MockU2fDevice::WinkDoNothing));
+
+  TestSignCallback cb;
+  std::unique_ptr<U2fRequest> request =
+      U2fSign::TrySign(handles, std::vector<uint8_t>(32),
+                       std::vector<uint8_t>(32), cb.callback());
+  request->Start();
+  request->AddDeviceForTesting(std::move(device0));
+  request->AddDeviceForTesting(std::move(device1));
+  std::pair<U2fReturnCode, std::vector<uint8_t>>& response =
+      cb.WaitForCallback();
+  EXPECT_EQ(U2fReturnCode::SUCCESS, response.first);
+  // Device that responded had no correct keys, so a registration response is
+  // expected
+  ASSERT_LT(static_cast<size_t>(0), response.second.size());
+  EXPECT_EQ(static_cast<uint8_t>(MockU2fDevice::kRegister), response.second[0]);
+}
+
+}  // namespace device
diff --git a/gpu/GLES2/extensions/CHROMIUM/CHROMIUM_image.txt b/gpu/GLES2/extensions/CHROMIUM/CHROMIUM_image.txt
index 2ba1e39..0d6a4c4 100644
--- a/gpu/GLES2/extensions/CHROMIUM/CHROMIUM_image.txt
+++ b/gpu/GLES2/extensions/CHROMIUM/CHROMIUM_image.txt
@@ -32,10 +32,10 @@
 
 New Procedures and Functions
 
-    GLuint CreateImageCHROMIUM(ClientBuffer buffer,
-                               GLsizei width,
-                               GLsizei height,
-                               GLenum internalformat)
+  GLuint CreateImageCHROMIUM(ClientBuffer buffer,
+                             GLsizei width,
+                             GLsizei height,
+                             GLenum internalformat)
 
     Create an image from <buffer> with width equal to <width> and
     height equal to <height> and format equal to <internalformat>.
@@ -50,12 +50,42 @@
     COMPRESSED_RGB_S3TC_DXT1_EXT, COMPRESSED_RGBA_S3TC_DXT5_EXT or
     ETC1_RGB8_OES.
 
-    void DestroyImageCHROMIUM(GLuint image_id)
+  void DestroyImageCHROMIUM(GLuint image_id)
 
     Frees the image previously created by a call to CreateImageCHROMIUM.
 
     INVALID_OPERATION is generated if <image_id> is not a valid image id.
 
+  void BindTexImage2DCHROMIUM(GLenum target, GLint image_id)
+
+    Binds the texture object currently bound to <target> to the image
+    <image_id> previously created by a call to CreateImageCHROMIUM.
+
+    INVALID_OPERATION is generated if no texture is bound to <target>.
+
+    INVALID_OPERATION is generated if <image_id> is not a valid image id.
+
+  void BindTexImage2DWithInternalformatCHROMIUM(GLenum target,
+                                                GLenum internalformat,
+                                                GLint image_id)
+
+    Behaves exactly like BindTexImage2DCHROMIUM, but forces the
+    texture to use the specified <internalformat> rather than the
+    default one. This function is provided solely as a workaround for
+    driver bugs on some platforms. BindTexImage2DCHROMIUM should be
+    used by almost all users.
+
+  void ReleaseTexImage2DCHROMIUM(GLenum target, GLint image_id)
+
+    Unbinds the texture object bound to <target> from the image
+    <image_id> previously created by a call to CreateImageCHROMIUM. If
+    the texture is not currently bound to the image, has no effect,
+    though may still generate errors.
+
+    INVALID_OPERATION is generated if no texture is bound to <target>.
+
+    INVALID_OPERATION is generated if <image_id> is not a valid image id.
+
 Dependencies on EXT_texture_format_BGRA8888
 
     If EXT_texture_format_BGRA8888 is not supported:
diff --git a/gpu/GLES2/gl2chromium_autogen.h b/gpu/GLES2/gl2chromium_autogen.h
index b5114bd..8820d0f 100644
--- a/gpu/GLES2/gl2chromium_autogen.h
+++ b/gpu/GLES2/gl2chromium_autogen.h
@@ -310,6 +310,8 @@
   GLES2_GET_FUN(CreateAndConsumeTextureCHROMIUM)
 #define glBindUniformLocationCHROMIUM GLES2_GET_FUN(BindUniformLocationCHROMIUM)
 #define glBindTexImage2DCHROMIUM GLES2_GET_FUN(BindTexImage2DCHROMIUM)
+#define glBindTexImage2DWithInternalformatCHROMIUM \
+  GLES2_GET_FUN(BindTexImage2DWithInternalformatCHROMIUM)
 #define glReleaseTexImage2DCHROMIUM GLES2_GET_FUN(ReleaseTexImage2DCHROMIUM)
 #define glTraceBeginCHROMIUM GLES2_GET_FUN(TraceBeginCHROMIUM)
 #define glTraceEndCHROMIUM GLES2_GET_FUN(TraceEndCHROMIUM)
diff --git a/gpu/GLES2/gl2extchromium.h b/gpu/GLES2/gl2extchromium.h
index e38949b..8c1f3df1 100644
--- a/gpu/GLES2/gl2extchromium.h
+++ b/gpu/GLES2/gl2extchromium.h
@@ -84,6 +84,14 @@
                                                     GLsizei height,
                                                     GLenum internalformat);
 GL_APICALL void GL_APIENTRY glDestroyImageCHROMIUM(GLuint image_id);
+GL_APICALL void GL_APIENTRY glBindTexImage2DCHROMIUM(GLenum target,
+                                                     GLint imageId);
+GL_APICALL void GL_APIENTRY
+glBindTexImage2DWithInternalformatCHROMIUM(GLenum target,
+                                           GLenum internalformat,
+                                           GLint imageId);
+GL_APICALL void GL_APIENTRY glReleaseTexImage2DCHROMIUM(GLenum target,
+                                                        GLint imageId);
 #endif
 typedef GLuint(GL_APIENTRYP PFNGLCREATEIMAGECHROMIUMPROC)(
     ClientBuffer buffer,
@@ -92,6 +100,14 @@
     GLenum internalformat);
 typedef void (
     GL_APIENTRYP PFNGLDESTROYIMAGECHROMIUMPROC)(GLuint image_id);
+typedef void(GL_APIENTRYP PFNGLBINDTEXIMAGE2DCHROMIUMPROC)(GLenum target,
+                                                           GLint imageId);
+typedef void(GL_APIENTRYP PFNGLBINDTEXIMAGE2DWITHINTERNALFORMATCHROMIUMPROC)(
+    GLenum target,
+    GLenum internalformat,
+    GLint imageId);
+typedef void(GL_APIENTRYP PFNGLRELEASETEXIMAGE2DCHROMIUMPROC)(GLenum target,
+                                                              GLint imageId);
 #endif  /* GL_CHROMIUM_image */
 
 #ifndef GL_RGB_YCRCB_420_CHROMIUM
@@ -169,21 +185,6 @@
 #endif
 #endif  /* GL_CHROMIUM_get_error_query */
 
-/* GL_CHROMIUM_texture_from_image */
-#ifndef GL_CHROMIUM_texture_from_image
-#define GL_CHROMIUM_texture_from_image 1
-#ifdef GL_GLEXT_PROTOTYPES
-GL_APICALL void GL_APIENTRY glBindTexImage2DCHROMIUM(
-    GLenum target, GLint imageId);
-GL_APICALL void GL_APIENTRY glReleaseTexImage2DCHROMIUM(
-    GLenum target, GLint imageId);
-#endif
-typedef void (GL_APIENTRYP PFNGLBINDTEXIMAGE2DCHROMIUMPROC) (
-    GLenum target, GLint imageId);
-typedef void (GL_APIENTRYP PFNGLRELEASETEXIMAGE2DCHROMIUMPROC) (
-    GLenum target, GLint imageId);
-#endif  /* GL_CHROMIUM_texture_from_image */
-
 /* GL_CHROMIUM_post_sub_buffer */
 #ifndef GL_CHROMIUM_post_sub_buffer
 #define GL_CHROMIUM_post_sub_buffer 1
diff --git a/gpu/command_buffer/build_gles2_cmd_buffer.py b/gpu/command_buffer/build_gles2_cmd_buffer.py
index 479b2d6..01593a3 100755
--- a/gpu/command_buffer/build_gles2_cmd_buffer.py
+++ b/gpu/command_buffer/build_gles2_cmd_buffer.py
@@ -4278,6 +4278,11 @@
     'unit_test': False,
     'extension': "CHROMIUM_image",
   },
+  'BindTexImage2DWithInternalformatCHROMIUM': {
+    'decoder_func': 'DoBindTexImage2DWithInternalformatCHROMIUM',
+    'unit_test': False,
+    'extension': "CHROMIUM_image",
+  },
   'ReleaseTexImage2DCHROMIUM': {
     'decoder_func': 'DoReleaseTexImage2DCHROMIUM',
     'unit_test': False,
diff --git a/gpu/command_buffer/client/gles2_c_lib_autogen.h b/gpu/command_buffer/client/gles2_c_lib_autogen.h
index b789a6b..8640ab7 100644
--- a/gpu/command_buffer/client/gles2_c_lib_autogen.h
+++ b/gpu/command_buffer/client/gles2_c_lib_autogen.h
@@ -1415,6 +1415,13 @@
 void GL_APIENTRY GLES2BindTexImage2DCHROMIUM(GLenum target, GLint imageId) {
   gles2::GetGLContext()->BindTexImage2DCHROMIUM(target, imageId);
 }
+void GL_APIENTRY
+GLES2BindTexImage2DWithInternalformatCHROMIUM(GLenum target,
+                                              GLenum internalformat,
+                                              GLint imageId) {
+  gles2::GetGLContext()->BindTexImage2DWithInternalformatCHROMIUM(
+      target, internalformat, imageId);
+}
 void GL_APIENTRY GLES2ReleaseTexImage2DCHROMIUM(GLenum target, GLint imageId) {
   gles2::GetGLContext()->ReleaseTexImage2DCHROMIUM(target, imageId);
 }
@@ -2819,6 +2826,11 @@
         reinterpret_cast<GLES2FunctionPointer>(glBindTexImage2DCHROMIUM),
     },
     {
+        "glBindTexImage2DWithInternalformatCHROMIUM",
+        reinterpret_cast<GLES2FunctionPointer>(
+            glBindTexImage2DWithInternalformatCHROMIUM),
+    },
+    {
         "glReleaseTexImage2DCHROMIUM",
         reinterpret_cast<GLES2FunctionPointer>(glReleaseTexImage2DCHROMIUM),
     },
diff --git a/gpu/command_buffer/client/gles2_cmd_helper_autogen.h b/gpu/command_buffer/client/gles2_cmd_helper_autogen.h
index 834b474..2573fd9 100644
--- a/gpu/command_buffer/client/gles2_cmd_helper_autogen.h
+++ b/gpu/command_buffer/client/gles2_cmd_helper_autogen.h
@@ -2651,6 +2651,16 @@
   }
 }
 
+void BindTexImage2DWithInternalformatCHROMIUM(GLenum target,
+                                              GLenum internalformat,
+                                              GLint imageId) {
+  gles2::cmds::BindTexImage2DWithInternalformatCHROMIUM* c =
+      GetCmdSpace<gles2::cmds::BindTexImage2DWithInternalformatCHROMIUM>();
+  if (c) {
+    c->Init(target, internalformat, imageId);
+  }
+}
+
 void ReleaseTexImage2DCHROMIUM(GLenum target, GLint imageId) {
   gles2::cmds::ReleaseTexImage2DCHROMIUM* c =
       GetCmdSpace<gles2::cmds::ReleaseTexImage2DCHROMIUM>();
diff --git a/gpu/command_buffer/client/gles2_implementation_autogen.h b/gpu/command_buffer/client/gles2_implementation_autogen.h
index ee52301..d605f61 100644
--- a/gpu/command_buffer/client/gles2_implementation_autogen.h
+++ b/gpu/command_buffer/client/gles2_implementation_autogen.h
@@ -996,6 +996,10 @@
 
 void BindTexImage2DCHROMIUM(GLenum target, GLint imageId) override;
 
+void BindTexImage2DWithInternalformatCHROMIUM(GLenum target,
+                                              GLenum internalformat,
+                                              GLint imageId) override;
+
 void ReleaseTexImage2DCHROMIUM(GLenum target, GLint imageId) override;
 
 void TraceBeginCHROMIUM(const char* category_name,
diff --git a/gpu/command_buffer/client/gles2_implementation_impl_autogen.h b/gpu/command_buffer/client/gles2_implementation_impl_autogen.h
index db5d667..a5a5091 100644
--- a/gpu/command_buffer/client/gles2_implementation_impl_autogen.h
+++ b/gpu/command_buffer/client/gles2_implementation_impl_autogen.h
@@ -3195,6 +3195,21 @@
   CheckGLError();
 }
 
+void GLES2Implementation::BindTexImage2DWithInternalformatCHROMIUM(
+    GLenum target,
+    GLenum internalformat,
+    GLint imageId) {
+  GPU_CLIENT_SINGLE_THREAD_CHECK();
+  GPU_CLIENT_LOG("[" << GetLogPrefix()
+                     << "] glBindTexImage2DWithInternalformatCHROMIUM("
+                     << GLES2Util::GetStringTextureBindTarget(target) << ", "
+                     << GLES2Util::GetStringEnum(internalformat) << ", "
+                     << imageId << ")");
+  helper_->BindTexImage2DWithInternalformatCHROMIUM(target, internalformat,
+                                                    imageId);
+  CheckGLError();
+}
+
 void GLES2Implementation::ReleaseTexImage2DCHROMIUM(GLenum target,
                                                     GLint imageId) {
   GPU_CLIENT_SINGLE_THREAD_CHECK();
diff --git a/gpu/command_buffer/client/gles2_implementation_unittest_autogen.h b/gpu/command_buffer/client/gles2_implementation_unittest_autogen.h
index cee92feee..55e10eae 100644
--- a/gpu/command_buffer/client/gles2_implementation_unittest_autogen.h
+++ b/gpu/command_buffer/client/gles2_implementation_unittest_autogen.h
@@ -2781,6 +2781,17 @@
   EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)));
 }
 
+TEST_F(GLES2ImplementationTest, BindTexImage2DWithInternalformatCHROMIUM) {
+  struct Cmds {
+    cmds::BindTexImage2DWithInternalformatCHROMIUM cmd;
+  };
+  Cmds expected;
+  expected.cmd.Init(GL_TEXTURE_2D, 2, 3);
+
+  gl_->BindTexImage2DWithInternalformatCHROMIUM(GL_TEXTURE_2D, 2, 3);
+  EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)));
+}
+
 TEST_F(GLES2ImplementationTest, ReleaseTexImage2DCHROMIUM) {
   struct Cmds {
     cmds::ReleaseTexImage2DCHROMIUM cmd;
diff --git a/gpu/command_buffer/client/gles2_interface_autogen.h b/gpu/command_buffer/client/gles2_interface_autogen.h
index 65724af..07a37155 100644
--- a/gpu/command_buffer/client/gles2_interface_autogen.h
+++ b/gpu/command_buffer/client/gles2_interface_autogen.h
@@ -732,6 +732,9 @@
                                          GLint location,
                                          const char* name) = 0;
 virtual void BindTexImage2DCHROMIUM(GLenum target, GLint imageId) = 0;
+virtual void BindTexImage2DWithInternalformatCHROMIUM(GLenum target,
+                                                      GLenum internalformat,
+                                                      GLint imageId) = 0;
 virtual void ReleaseTexImage2DCHROMIUM(GLenum target, GLint imageId) = 0;
 virtual void TraceBeginCHROMIUM(const char* category_name,
                                 const char* trace_name) = 0;
diff --git a/gpu/command_buffer/client/gles2_interface_stub_autogen.h b/gpu/command_buffer/client/gles2_interface_stub_autogen.h
index 54dabbc..bee6141 100644
--- a/gpu/command_buffer/client/gles2_interface_stub_autogen.h
+++ b/gpu/command_buffer/client/gles2_interface_stub_autogen.h
@@ -710,6 +710,9 @@
                                  GLint location,
                                  const char* name) override;
 void BindTexImage2DCHROMIUM(GLenum target, GLint imageId) override;
+void BindTexImage2DWithInternalformatCHROMIUM(GLenum target,
+                                              GLenum internalformat,
+                                              GLint imageId) override;
 void ReleaseTexImage2DCHROMIUM(GLenum target, GLint imageId) override;
 void TraceBeginCHROMIUM(const char* category_name,
                         const char* trace_name) override;
diff --git a/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h b/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h
index 5073353b..fd49260 100644
--- a/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h
+++ b/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h
@@ -965,6 +965,10 @@
                                                      const char* /* name */) {}
 void GLES2InterfaceStub::BindTexImage2DCHROMIUM(GLenum /* target */,
                                                 GLint /* imageId */) {}
+void GLES2InterfaceStub::BindTexImage2DWithInternalformatCHROMIUM(
+    GLenum /* target */,
+    GLenum /* internalformat */,
+    GLint /* imageId */) {}
 void GLES2InterfaceStub::ReleaseTexImage2DCHROMIUM(GLenum /* target */,
                                                    GLint /* imageId */) {}
 void GLES2InterfaceStub::TraceBeginCHROMIUM(const char* /* category_name */,
diff --git a/gpu/command_buffer/client/gles2_trace_implementation_autogen.h b/gpu/command_buffer/client/gles2_trace_implementation_autogen.h
index 783d7cc..911c4fd0 100644
--- a/gpu/command_buffer/client/gles2_trace_implementation_autogen.h
+++ b/gpu/command_buffer/client/gles2_trace_implementation_autogen.h
@@ -710,6 +710,9 @@
                                  GLint location,
                                  const char* name) override;
 void BindTexImage2DCHROMIUM(GLenum target, GLint imageId) override;
+void BindTexImage2DWithInternalformatCHROMIUM(GLenum target,
+                                              GLenum internalformat,
+                                              GLint imageId) override;
 void ReleaseTexImage2DCHROMIUM(GLenum target, GLint imageId) override;
 void TraceBeginCHROMIUM(const char* category_name,
                         const char* trace_name) override;
diff --git a/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h b/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h
index 01db22e..4b096cd 100644
--- a/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h
+++ b/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h
@@ -2054,6 +2054,16 @@
   gl_->BindTexImage2DCHROMIUM(target, imageId);
 }
 
+void GLES2TraceImplementation::BindTexImage2DWithInternalformatCHROMIUM(
+    GLenum target,
+    GLenum internalformat,
+    GLint imageId) {
+  TRACE_EVENT_BINARY_EFFICIENT0(
+      "gpu", "GLES2Trace::BindTexImage2DWithInternalformatCHROMIUM");
+  gl_->BindTexImage2DWithInternalformatCHROMIUM(target, internalformat,
+                                                imageId);
+}
+
 void GLES2TraceImplementation::ReleaseTexImage2DCHROMIUM(GLenum target,
                                                          GLint imageId) {
   TRACE_EVENT_BINARY_EFFICIENT0("gpu", "GLES2Trace::ReleaseTexImage2DCHROMIUM");
diff --git a/gpu/command_buffer/cmd_buffer_functions.txt b/gpu/command_buffer/cmd_buffer_functions.txt
index a1055e8..2b87eff 100644
--- a/gpu/command_buffer/cmd_buffer_functions.txt
+++ b/gpu/command_buffer/cmd_buffer_functions.txt
@@ -293,6 +293,7 @@
 GL_APICALL void         GL_APIENTRY glCreateAndConsumeTextureINTERNAL (GLenumTextureBindTarget target, GLuint texture, const GLbyte* mailbox);
 GL_APICALL void         GL_APIENTRY glBindUniformLocationCHROMIUM (GLidProgram program, GLint location, const char* name);
 GL_APICALL void         GL_APIENTRY glBindTexImage2DCHROMIUM (GLenumTextureBindTarget target, GLint imageId);
+GL_APICALL void         GL_APIENTRY glBindTexImage2DWithInternalformatCHROMIUM (GLenumTextureBindTarget target, GLenum internalformat, GLint imageId);
 GL_APICALL void         GL_APIENTRY glReleaseTexImage2DCHROMIUM (GLenumTextureBindTarget target, GLint imageId);
 GL_APICALL void         GL_APIENTRY glTraceBeginCHROMIUM (const char* category_name, const char* trace_name);
 GL_APICALL void         GL_APIENTRY glTraceEndCHROMIUM (void);
diff --git a/gpu/command_buffer/common/gles2_cmd_format_autogen.h b/gpu/command_buffer/common/gles2_cmd_format_autogen.h
index 2f1e47f..3ec79a2 100644
--- a/gpu/command_buffer/common/gles2_cmd_format_autogen.h
+++ b/gpu/command_buffer/common/gles2_cmd_format_autogen.h
@@ -13084,6 +13084,52 @@
 static_assert(offsetof(BindTexImage2DCHROMIUM, imageId) == 8,
               "offset of BindTexImage2DCHROMIUM imageId should be 8");
 
+struct BindTexImage2DWithInternalformatCHROMIUM {
+  typedef BindTexImage2DWithInternalformatCHROMIUM ValueType;
+  static const CommandId kCmdId = kBindTexImage2DWithInternalformatCHROMIUM;
+  static const cmd::ArgFlags kArgFlags = cmd::kFixed;
+  static const uint8_t cmd_flags = CMD_FLAG_SET_TRACE_LEVEL(3);
+
+  static uint32_t ComputeSize() {
+    return static_cast<uint32_t>(sizeof(ValueType));  // NOLINT
+  }
+
+  void SetHeader() { header.SetCmd<ValueType>(); }
+
+  void Init(GLenum _target, GLenum _internalformat, GLint _imageId) {
+    SetHeader();
+    target = _target;
+    internalformat = _internalformat;
+    imageId = _imageId;
+  }
+
+  void* Set(void* cmd, GLenum _target, GLenum _internalformat, GLint _imageId) {
+    static_cast<ValueType*>(cmd)->Init(_target, _internalformat, _imageId);
+    return NextCmdAddress<ValueType>(cmd);
+  }
+
+  gpu::CommandHeader header;
+  uint32_t target;
+  uint32_t internalformat;
+  int32_t imageId;
+};
+
+static_assert(sizeof(BindTexImage2DWithInternalformatCHROMIUM) == 16,
+              "size of BindTexImage2DWithInternalformatCHROMIUM should be 16");
+static_assert(
+    offsetof(BindTexImage2DWithInternalformatCHROMIUM, header) == 0,
+    "offset of BindTexImage2DWithInternalformatCHROMIUM header should be 0");
+static_assert(
+    offsetof(BindTexImage2DWithInternalformatCHROMIUM, target) == 4,
+    "offset of BindTexImage2DWithInternalformatCHROMIUM target should be 4");
+static_assert(offsetof(BindTexImage2DWithInternalformatCHROMIUM,
+                       internalformat) == 8,
+              "offset of BindTexImage2DWithInternalformatCHROMIUM "
+              "internalformat should be 8");
+static_assert(
+    offsetof(BindTexImage2DWithInternalformatCHROMIUM, imageId) == 12,
+    "offset of BindTexImage2DWithInternalformatCHROMIUM imageId should be 12");
+
 struct ReleaseTexImage2DCHROMIUM {
   typedef ReleaseTexImage2DCHROMIUM ValueType;
   static const CommandId kCmdId = kReleaseTexImage2DCHROMIUM;
diff --git a/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h b/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h
index 1bf9ced5..d7094ea1 100644
--- a/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h
+++ b/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h
@@ -4434,6 +4434,21 @@
   CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
 }
 
+TEST_F(GLES2FormatTest, BindTexImage2DWithInternalformatCHROMIUM) {
+  cmds::BindTexImage2DWithInternalformatCHROMIUM& cmd =
+      *GetBufferAs<cmds::BindTexImage2DWithInternalformatCHROMIUM>();
+  void* next_cmd = cmd.Set(&cmd, static_cast<GLenum>(11),
+                           static_cast<GLenum>(12), static_cast<GLint>(13));
+  EXPECT_EQ(static_cast<uint32_t>(
+                cmds::BindTexImage2DWithInternalformatCHROMIUM::kCmdId),
+            cmd.header.command);
+  EXPECT_EQ(sizeof(cmd), cmd.header.size * 4u);
+  EXPECT_EQ(static_cast<GLenum>(11), cmd.target);
+  EXPECT_EQ(static_cast<GLenum>(12), cmd.internalformat);
+  EXPECT_EQ(static_cast<GLint>(13), cmd.imageId);
+  CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
+}
+
 TEST_F(GLES2FormatTest, ReleaseTexImage2DCHROMIUM) {
   cmds::ReleaseTexImage2DCHROMIUM& cmd =
       *GetBufferAs<cmds::ReleaseTexImage2DCHROMIUM>();
diff --git a/gpu/command_buffer/common/gles2_cmd_ids_autogen.h b/gpu/command_buffer/common/gles2_cmd_ids_autogen.h
index ffef2d5..bd9ccf3 100644
--- a/gpu/command_buffer/common/gles2_cmd_ids_autogen.h
+++ b/gpu/command_buffer/common/gles2_cmd_ids_autogen.h
@@ -281,59 +281,60 @@
   OP(CreateAndConsumeTextureINTERNALImmediate)             /* 522 */ \
   OP(BindUniformLocationCHROMIUMBucket)                    /* 523 */ \
   OP(BindTexImage2DCHROMIUM)                               /* 524 */ \
-  OP(ReleaseTexImage2DCHROMIUM)                            /* 525 */ \
-  OP(TraceBeginCHROMIUM)                                   /* 526 */ \
-  OP(TraceEndCHROMIUM)                                     /* 527 */ \
-  OP(DiscardFramebufferEXTImmediate)                       /* 528 */ \
-  OP(LoseContextCHROMIUM)                                  /* 529 */ \
-  OP(InsertFenceSyncCHROMIUM)                              /* 530 */ \
-  OP(WaitSyncTokenCHROMIUM)                                /* 531 */ \
-  OP(DrawBuffersEXTImmediate)                              /* 532 */ \
-  OP(DiscardBackbufferCHROMIUM)                            /* 533 */ \
-  OP(ScheduleOverlayPlaneCHROMIUM)                         /* 534 */ \
-  OP(ScheduleCALayerSharedStateCHROMIUM)                   /* 535 */ \
-  OP(ScheduleCALayerCHROMIUM)                              /* 536 */ \
-  OP(ScheduleCALayerInUseQueryCHROMIUMImmediate)           /* 537 */ \
-  OP(CommitOverlayPlanesCHROMIUM)                          /* 538 */ \
-  OP(SwapInterval)                                         /* 539 */ \
-  OP(FlushDriverCachesCHROMIUM)                            /* 540 */ \
-  OP(ScheduleDCLayerSharedStateCHROMIUM)                   /* 541 */ \
-  OP(ScheduleDCLayerCHROMIUM)                              /* 542 */ \
-  OP(MatrixLoadfCHROMIUMImmediate)                         /* 543 */ \
-  OP(MatrixLoadIdentityCHROMIUM)                           /* 544 */ \
-  OP(GenPathsCHROMIUM)                                     /* 545 */ \
-  OP(DeletePathsCHROMIUM)                                  /* 546 */ \
-  OP(IsPathCHROMIUM)                                       /* 547 */ \
-  OP(PathCommandsCHROMIUM)                                 /* 548 */ \
-  OP(PathParameterfCHROMIUM)                               /* 549 */ \
-  OP(PathParameteriCHROMIUM)                               /* 550 */ \
-  OP(PathStencilFuncCHROMIUM)                              /* 551 */ \
-  OP(StencilFillPathCHROMIUM)                              /* 552 */ \
-  OP(StencilStrokePathCHROMIUM)                            /* 553 */ \
-  OP(CoverFillPathCHROMIUM)                                /* 554 */ \
-  OP(CoverStrokePathCHROMIUM)                              /* 555 */ \
-  OP(StencilThenCoverFillPathCHROMIUM)                     /* 556 */ \
-  OP(StencilThenCoverStrokePathCHROMIUM)                   /* 557 */ \
-  OP(StencilFillPathInstancedCHROMIUM)                     /* 558 */ \
-  OP(StencilStrokePathInstancedCHROMIUM)                   /* 559 */ \
-  OP(CoverFillPathInstancedCHROMIUM)                       /* 560 */ \
-  OP(CoverStrokePathInstancedCHROMIUM)                     /* 561 */ \
-  OP(StencilThenCoverFillPathInstancedCHROMIUM)            /* 562 */ \
-  OP(StencilThenCoverStrokePathInstancedCHROMIUM)          /* 563 */ \
-  OP(BindFragmentInputLocationCHROMIUMBucket)              /* 564 */ \
-  OP(ProgramPathFragmentInputGenCHROMIUM)                  /* 565 */ \
-  OP(GetBufferSubDataAsyncCHROMIUM)                        /* 566 */ \
-  OP(CoverageModulationCHROMIUM)                           /* 567 */ \
-  OP(BlendBarrierKHR)                                      /* 568 */ \
-  OP(ApplyScreenSpaceAntialiasingCHROMIUM)                 /* 569 */ \
-  OP(BindFragDataLocationIndexedEXTBucket)                 /* 570 */ \
-  OP(BindFragDataLocationEXTBucket)                        /* 571 */ \
-  OP(GetFragDataIndexEXT)                                  /* 572 */ \
-  OP(UniformMatrix4fvStreamTextureMatrixCHROMIUMImmediate) /* 573 */ \
-  OP(OverlayPromotionHintCHROMIUM)                         /* 574 */ \
-  OP(SwapBuffersWithBoundsCHROMIUMImmediate)               /* 575 */ \
-  OP(SetDrawRectangleCHROMIUM)                             /* 576 */ \
-  OP(SetEnableDCLayersCHROMIUM)                            /* 577 */
+  OP(BindTexImage2DWithInternalformatCHROMIUM)             /* 525 */ \
+  OP(ReleaseTexImage2DCHROMIUM)                            /* 526 */ \
+  OP(TraceBeginCHROMIUM)                                   /* 527 */ \
+  OP(TraceEndCHROMIUM)                                     /* 528 */ \
+  OP(DiscardFramebufferEXTImmediate)                       /* 529 */ \
+  OP(LoseContextCHROMIUM)                                  /* 530 */ \
+  OP(InsertFenceSyncCHROMIUM)                              /* 531 */ \
+  OP(WaitSyncTokenCHROMIUM)                                /* 532 */ \
+  OP(DrawBuffersEXTImmediate)                              /* 533 */ \
+  OP(DiscardBackbufferCHROMIUM)                            /* 534 */ \
+  OP(ScheduleOverlayPlaneCHROMIUM)                         /* 535 */ \
+  OP(ScheduleCALayerSharedStateCHROMIUM)                   /* 536 */ \
+  OP(ScheduleCALayerCHROMIUM)                              /* 537 */ \
+  OP(ScheduleCALayerInUseQueryCHROMIUMImmediate)           /* 538 */ \
+  OP(CommitOverlayPlanesCHROMIUM)                          /* 539 */ \
+  OP(SwapInterval)                                         /* 540 */ \
+  OP(FlushDriverCachesCHROMIUM)                            /* 541 */ \
+  OP(ScheduleDCLayerSharedStateCHROMIUM)                   /* 542 */ \
+  OP(ScheduleDCLayerCHROMIUM)                              /* 543 */ \
+  OP(MatrixLoadfCHROMIUMImmediate)                         /* 544 */ \
+  OP(MatrixLoadIdentityCHROMIUM)                           /* 545 */ \
+  OP(GenPathsCHROMIUM)                                     /* 546 */ \
+  OP(DeletePathsCHROMIUM)                                  /* 547 */ \
+  OP(IsPathCHROMIUM)                                       /* 548 */ \
+  OP(PathCommandsCHROMIUM)                                 /* 549 */ \
+  OP(PathParameterfCHROMIUM)                               /* 550 */ \
+  OP(PathParameteriCHROMIUM)                               /* 551 */ \
+  OP(PathStencilFuncCHROMIUM)                              /* 552 */ \
+  OP(StencilFillPathCHROMIUM)                              /* 553 */ \
+  OP(StencilStrokePathCHROMIUM)                            /* 554 */ \
+  OP(CoverFillPathCHROMIUM)                                /* 555 */ \
+  OP(CoverStrokePathCHROMIUM)                              /* 556 */ \
+  OP(StencilThenCoverFillPathCHROMIUM)                     /* 557 */ \
+  OP(StencilThenCoverStrokePathCHROMIUM)                   /* 558 */ \
+  OP(StencilFillPathInstancedCHROMIUM)                     /* 559 */ \
+  OP(StencilStrokePathInstancedCHROMIUM)                   /* 560 */ \
+  OP(CoverFillPathInstancedCHROMIUM)                       /* 561 */ \
+  OP(CoverStrokePathInstancedCHROMIUM)                     /* 562 */ \
+  OP(StencilThenCoverFillPathInstancedCHROMIUM)            /* 563 */ \
+  OP(StencilThenCoverStrokePathInstancedCHROMIUM)          /* 564 */ \
+  OP(BindFragmentInputLocationCHROMIUMBucket)              /* 565 */ \
+  OP(ProgramPathFragmentInputGenCHROMIUM)                  /* 566 */ \
+  OP(GetBufferSubDataAsyncCHROMIUM)                        /* 567 */ \
+  OP(CoverageModulationCHROMIUM)                           /* 568 */ \
+  OP(BlendBarrierKHR)                                      /* 569 */ \
+  OP(ApplyScreenSpaceAntialiasingCHROMIUM)                 /* 570 */ \
+  OP(BindFragDataLocationIndexedEXTBucket)                 /* 571 */ \
+  OP(BindFragDataLocationEXTBucket)                        /* 572 */ \
+  OP(GetFragDataIndexEXT)                                  /* 573 */ \
+  OP(UniformMatrix4fvStreamTextureMatrixCHROMIUMImmediate) /* 574 */ \
+  OP(OverlayPromotionHintCHROMIUM)                         /* 575 */ \
+  OP(SwapBuffersWithBoundsCHROMIUMImmediate)               /* 576 */ \
+  OP(SetDrawRectangleCHROMIUM)                             /* 577 */ \
+  OP(SetEnableDCLayersCHROMIUM)                            /* 578 */
 
 enum CommandId {
   kOneBeforeStartPoint =
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index b9097406..db2c9d2 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -1041,6 +1041,14 @@
   void DoBindTexImage2DCHROMIUM(
       GLenum target,
       GLint image_id);
+  void DoBindTexImage2DWithInternalformatCHROMIUM(GLenum target,
+                                                  GLenum internalformat,
+                                                  GLint image_id);
+  // Common implementation of DoBindTexImage2DCHROMIUM entry points.
+  void BindTexImage2DCHROMIUMImpl(const char* function_name,
+                                  GLenum target,
+                                  GLenum internalformat,
+                                  GLint image_id);
   void DoReleaseTexImage2DCHROMIUM(
       GLenum target,
       GLint image_id);
@@ -17763,10 +17771,26 @@
     GLenum target, GLint image_id) {
   TRACE_EVENT0("gpu", "GLES2DecoderImpl::DoBindTexImage2DCHROMIUM");
 
+  BindTexImage2DCHROMIUMImpl("glBindTexImage2DCHROMIUM", target, 0, image_id);
+}
+
+void GLES2DecoderImpl::DoBindTexImage2DWithInternalformatCHROMIUM(
+    GLenum target,
+    GLenum internalformat,
+    GLint image_id) {
+  TRACE_EVENT0("gpu",
+               "GLES2DecoderImpl::DoBindTexImage2DWithInternalformatCHROMIUM");
+
+  BindTexImage2DCHROMIUMImpl("glBindTexImage2DWithInternalformatCHROMIUM",
+                             target, internalformat, image_id);
+}
+
+void GLES2DecoderImpl::BindTexImage2DCHROMIUMImpl(const char* function_name,
+                                                  GLenum target,
+                                                  GLenum internalformat,
+                                                  GLint image_id) {
   if (target == GL_TEXTURE_CUBE_MAP) {
-    LOCAL_SET_GL_ERROR(
-        GL_INVALID_ENUM,
-        "glBindTexImage2DCHROMIUM", "invalid target");
+    LOCAL_SET_GL_ERROR(GL_INVALID_ENUM, function_name, "invalid target");
     return;
   }
 
@@ -17775,17 +17799,14 @@
   TextureRef* texture_ref =
       texture_manager()->GetTextureInfoForTargetUnlessDefault(&state_, target);
   if (!texture_ref) {
-    LOCAL_SET_GL_ERROR(
-        GL_INVALID_OPERATION,
-        "glBindTexImage2DCHROMIUM", "no texture bound");
+    LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, "no texture bound");
     return;
   }
 
   gl::GLImage* image = image_manager()->LookupImage(image_id);
   if (!image) {
-    LOCAL_SET_GL_ERROR(
-        GL_INVALID_OPERATION,
-        "glBindTexImage2DCHROMIUM", "no image found with the given ID");
+    LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name,
+                       "no image found with the given ID");
     return;
   }
 
@@ -17797,15 +17818,22 @@
 
     // Note: We fallback to using CopyTexImage() before the texture is used
     // when BindTexImage() fails.
-    if (image->BindTexImage(target))
-      image_state = Texture::BOUND;
+    if (internalformat) {
+      if (image->BindTexImageWithInternalformat(target, internalformat))
+        image_state = Texture::BOUND;
+    } else {
+      if (image->BindTexImage(target))
+        image_state = Texture::BOUND;
+    }
   }
 
   gfx::Size size = image->GetSize();
-  GLenum internalformat = image->GetInternalFormat();
-  texture_manager()->SetLevelInfo(
-      texture_ref, target, 0, internalformat, size.width(), size.height(), 1, 0,
-      internalformat, GL_UNSIGNED_BYTE, gfx::Rect(size));
+  GLenum texture_internalformat =
+      internalformat ? internalformat : image->GetInternalFormat();
+  texture_manager()->SetLevelInfo(texture_ref, target, 0,
+                                  texture_internalformat, size.width(),
+                                  size.height(), 1, 0, texture_internalformat,
+                                  GL_UNSIGNED_BYTE, gfx::Rect(size));
   texture_manager()->SetLevelImage(texture_ref, target, 0, image, image_state);
 }
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h b/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
index fa2f5799..25237e2 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
@@ -4778,6 +4778,24 @@
   return error::kNoError;
 }
 
+error::Error GLES2DecoderImpl::HandleBindTexImage2DWithInternalformatCHROMIUM(
+    uint32_t immediate_data_size,
+    const volatile void* cmd_data) {
+  const volatile gles2::cmds::BindTexImage2DWithInternalformatCHROMIUM& c =
+      *static_cast<const volatile gles2::cmds::
+                       BindTexImage2DWithInternalformatCHROMIUM*>(cmd_data);
+  GLenum target = static_cast<GLenum>(c.target);
+  GLenum internalformat = static_cast<GLenum>(c.internalformat);
+  GLint imageId = static_cast<GLint>(c.imageId);
+  if (!validators_->texture_bind_target.IsValid(target)) {
+    LOCAL_SET_GL_ERROR_INVALID_ENUM(
+        "glBindTexImage2DWithInternalformatCHROMIUM", target, "target");
+    return error::kNoError;
+  }
+  DoBindTexImage2DWithInternalformatCHROMIUM(target, internalformat, imageId);
+  return error::kNoError;
+}
+
 error::Error GLES2DecoderImpl::HandleReleaseTexImage2DCHROMIUM(
     uint32_t immediate_data_size,
     const volatile void* cmd_data) {
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
index eac5a9889..032369e0 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
@@ -885,6 +885,34 @@
   }
 }
 
+error::Error GLES2DecoderPassthroughImpl::BindTexImage2DCHROMIUMImpl(
+    GLenum target,
+    GLenum internalformat,
+    GLint imageId) {
+  if (target != GL_TEXTURE_2D) {
+    InsertError(GL_INVALID_ENUM, "Invalid target");
+    return error::kNoError;
+  }
+
+  gl::GLImage* image = image_manager_->LookupImage(imageId);
+  if (image == nullptr) {
+    InsertError(GL_INVALID_OPERATION, "No image found with the given ID");
+    return error::kNoError;
+  }
+
+  if (internalformat) {
+    if (!image->BindTexImageWithInternalformat(target, internalformat)) {
+      image->CopyTexImage(target);
+    }
+  } else {
+    if (!image->BindTexImage(target)) {
+      image->CopyTexImage(target);
+    }
+  }
+
+  return error::kNoError;
+}
+
 #define GLES2_CMD_OP(name)                                               \
   {                                                                      \
       &GLES2DecoderPassthroughImpl::Handle##name, cmds::name::kArgFlags, \
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
index 0985a2c3..f6ab1099 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
@@ -303,6 +303,10 @@
 
   void UpdateTextureBinding(GLenum target, GLuint client_id, GLuint service_id);
 
+  error::Error BindTexImage2DCHROMIUMImpl(GLenum target,
+                                          GLenum internalformat,
+                                          GLint image_id);
+
   int commands_to_process_;
 
   DebugMarkerManager debug_marker_manager_;
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h
index 60282e14..7ab02bc 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h
@@ -796,6 +796,9 @@
                                            GLint location,
                                            const char* name);
 error::Error DoBindTexImage2DCHROMIUM(GLenum target, GLint imageId);
+error::Error DoBindTexImage2DWithInternalformatCHROMIUM(GLenum target,
+                                                        GLenum internalformat,
+                                                        GLint imageId);
 error::Error DoReleaseTexImage2DCHROMIUM(GLenum target, GLint imageId);
 error::Error DoTraceBeginCHROMIUM(const char* category_name,
                                   const char* trace_name);
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
index 9bfcb6b..3600f809 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
@@ -3485,22 +3485,15 @@
 error::Error GLES2DecoderPassthroughImpl::DoBindTexImage2DCHROMIUM(
     GLenum target,
     GLint imageId) {
-  if (target != GL_TEXTURE_2D) {
-    InsertError(GL_INVALID_ENUM, "Invalid target");
-    return error::kNoError;
-  }
+  return BindTexImage2DCHROMIUMImpl(target, 0, imageId);
+}
 
-  gl::GLImage* image = image_manager_->LookupImage(imageId);
-  if (image == nullptr) {
-    InsertError(GL_INVALID_OPERATION, "No image found with the given ID");
-    return error::kNoError;
-  }
-
-  if (!image->BindTexImage(target)) {
-    image->CopyTexImage(target);
-  }
-
-  return error::kNoError;
+error::Error
+GLES2DecoderPassthroughImpl::DoBindTexImage2DWithInternalformatCHROMIUM(
+    GLenum target,
+    GLenum internalformat,
+    GLint imageId) {
+  return BindTexImage2DCHROMIUMImpl(target, internalformat, imageId);
 }
 
 error::Error GLES2DecoderPassthroughImpl::DoReleaseTexImage2DCHROMIUM(
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc
index 9c6f95f..629a24d 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc
@@ -4024,6 +4024,24 @@
   return error::kNoError;
 }
 
+error::Error
+GLES2DecoderPassthroughImpl::HandleBindTexImage2DWithInternalformatCHROMIUM(
+    uint32_t immediate_data_size,
+    const volatile void* cmd_data) {
+  const volatile gles2::cmds::BindTexImage2DWithInternalformatCHROMIUM& c =
+      *static_cast<const volatile gles2::cmds::
+                       BindTexImage2DWithInternalformatCHROMIUM*>(cmd_data);
+  GLenum target = static_cast<GLenum>(c.target);
+  GLenum internalformat = static_cast<GLenum>(c.internalformat);
+  GLint imageId = static_cast<GLint>(c.imageId);
+  error::Error error = DoBindTexImage2DWithInternalformatCHROMIUM(
+      target, internalformat, imageId);
+  if (error != error::kNoError) {
+    return error;
+  }
+  return error::kNoError;
+}
+
 error::Error GLES2DecoderPassthroughImpl::HandleReleaseTexImage2DCHROMIUM(
     uint32_t immediate_data_size,
     const volatile void* cmd_data) {
diff --git a/ios/chrome/app/spotlight/topsites_spotlight_manager.mm b/ios/chrome/app/spotlight/topsites_spotlight_manager.mm
index 58b45fe..5d100de 100644
--- a/ios/chrome/app/spotlight/topsites_spotlight_manager.mm
+++ b/ios/chrome/app/spotlight/topsites_spotlight_manager.mm
@@ -20,7 +20,7 @@
 #include "ios/chrome/browser/suggestions/suggestions_service_factory.h"
 #include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h"
 #include "ios/chrome/browser/sync/sync_observer_bridge.h"
-#include "ios/chrome/browser/ui/ntp/google_landing_controller.h"
+#include "ios/chrome/browser/ui/ntp/google_landing_mediator.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -229,7 +229,7 @@
 - (void)onMostVisitedURLsAvailable:
     (const history::MostVisitedURLList&)top_sites {
   NSUInteger sitesToIndex =
-      MIN(top_sites.size(), [GoogleLandingController maxSitesShown]);
+      MIN(top_sites.size(), [GoogleLandingMediator maxSitesShown]);
   for (size_t i = 0; i < sitesToIndex; i++) {
     const GURL& URL = top_sites[i].url;
 
@@ -247,8 +247,7 @@
     (const suggestions::SuggestionsProfile&)suggestionsProfile {
   size_t size = suggestionsProfile.suggestions_size();
   if (size) {
-    NSUInteger sitesToIndex =
-        MIN(size, [GoogleLandingController maxSitesShown]);
+    NSUInteger sitesToIndex = MIN(size, [GoogleLandingMediator maxSitesShown]);
     for (size_t i = 0; i < sitesToIndex; i++) {
       const suggestions::ChromeSuggestion& suggestion =
           suggestionsProfile.suggestions(i);
diff --git a/ios/chrome/browser/ui/ntp/BUILD.gn b/ios/chrome/browser/ui/ntp/BUILD.gn
index 64726d7..8c223db 100644
--- a/ios/chrome/browser/ui/ntp/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/BUILD.gn
@@ -113,8 +113,12 @@
   sources = [
     "centering_scrollview.h",
     "centering_scrollview.mm",
+    "google_landing_consumer.h",
     "google_landing_controller.h",
     "google_landing_controller.mm",
+    "google_landing_data_source.h",
+    "google_landing_mediator.h",
+    "google_landing_mediator.mm",
     "incognito_panel_controller.h",
     "incognito_panel_controller.mm",
     "most_visited_cell.h",
diff --git a/ios/chrome/browser/ui/ntp/google_landing_consumer.h b/ios/chrome/browser/ui/ntp/google_landing_consumer.h
new file mode 100644
index 0000000..9c6b9ea
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/google_landing_consumer.h
@@ -0,0 +1,66 @@
+// Copyright 2017 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 IOS_CHROME_BROWSER_UI_NTP_GOOGLE_LANDING_CONSUMER_H_
+#define IOS_CHROME_BROWSER_UI_NTP_GOOGLE_LANDING_CONSUMER_H_
+
+#import <Foundation/Foundation.h>
+
+// TODO(crbug.com/694750): Remove these two when the types below are changed.
+#include "components/ntp_tiles/ntp_tile.h"
+#include "ios/public/provider/chrome/browser/images/whats_new_icon.h"
+#include "ios/public/provider/chrome/browser/ui/logo_vendor.h"
+
+// Handles google landing controller update notifications.
+@protocol GoogleLandingConsumer<NSObject>
+
+// Whether the Google logo or doodle is being shown.
+- (void)setLogoIsShowing:(BOOL)logoIsShowing;
+
+// Exposes view and methods to drive the doodle.
+- (void)setLogoVendor:(id<LogoVendor>)logoVendor;
+
+// |YES| if this consumer is incognito.
+- (void)setIsOffTheRecord:(BOOL)isOffTheRecord;
+
+// |YES| if this consumer is has voice search enabled.
+- (void)setVoiceSearchIsEnabled:(BOOL)voiceSearchIsEnabled;
+
+// Sets the maximum number of sites shown.
+- (void)setMaximumMostVisitedSitesShown:
+    (NSUInteger)maximumMostVisitedSitesShown;
+
+// Sets the text of a what's new promo.
+- (void)setPromoText:(NSString*)promoText;
+
+// Sets the icon of a what's new promo.
+// TODO(crbug.com/694750): This should not be WhatsNewIcon.
+- (void)setPromoIcon:(WhatsNewIcon)promoIcon;
+
+// |YES| if a what's new promo can be displayed.
+- (void)setPromoCanShow:(BOOL)promoCanShow;
+
+// TODO(crbug.com/694750): This should be replaced with consumer suitable data
+// type property.
+// Tells the consumer to that most visited data updated.
+- (void)mostVisitedDataUpdated;
+
+// Tells the consumer a most visited icon was updated.
+- (void)mostVisitedIconMadeAvailableAtIndex:(NSUInteger)index;
+
+// TODO(crbug.com/694750): These two calls can be made with dispatcher instead.
+// The location bar has lost focus.
+- (void)locationBarResignsFirstResponder;
+
+// Tell location bar has taken focus.
+- (void)locationBarBecomesFirstResponder;
+
+// TODO(crbug.com/694750): This call will be removed once dispatching is
+// available.
+// Asks the consumer to execute a chrome command.
+- (void)chromeExecuteCommand:(id)sender;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_NTP_GOOGLE_LANDING_CONSUMER_H_
diff --git a/ios/chrome/browser/ui/ntp/google_landing_controller.h b/ios/chrome/browser/ui/ntp/google_landing_controller.h
index 94283429..af2352d 100644
--- a/ios/chrome/browser/ui/ntp/google_landing_controller.h
+++ b/ios/chrome/browser/ui/ntp/google_landing_controller.h
@@ -9,34 +9,21 @@
 
 #include <memory>
 
+#import "ios/chrome/browser/ui/ntp/google_landing_consumer.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_panel_protocol.h"
 #import "ios/chrome/browser/ui/toolbar/toolbar_owner.h"
 #import "ios/public/provider/chrome/browser/voice/logo_animation_controller.h"
 
-@protocol OmniboxFocuser;
-@class TabModel;
-@protocol UrlLoader;
-@protocol WebToolbarDelegate;
-
-namespace ios {
-class ChromeBrowserState;
-}
+@protocol GoogleLandingDataSource;
 
 // Google centric new tab page.
 @interface GoogleLandingController
-    : UIViewController<LogoAnimationControllerOwnerOwner,
+    : UIViewController<GoogleLandingConsumer,
+                       LogoAnimationControllerOwnerOwner,
                        NewTabPagePanelProtocol,
                        ToolbarOwner>
 
-// Initialization method.
-- (id)initWithLoader:(id<UrlLoader>)loader
-          browserState:(ios::ChromeBrowserState*)browserState
-               focuser:(id<OmniboxFocuser>)focuser
-    webToolbarDelegate:(id<WebToolbarDelegate>)webToolbarDelegate
-              tabModel:(TabModel*)tabModel;
-
-// Get the maximum number of sites shown.
-+ (NSUInteger)maxSitesShown;
+@property(nonatomic, assign) id<GoogleLandingDataSource> dataSource;
 
 @end
 
diff --git a/ios/chrome/browser/ui/ntp/google_landing_controller.mm b/ios/chrome/browser/ui/ntp/google_landing_controller.mm
index fc420ea..7f1d3c7 100644
--- a/ios/chrome/browser/ui/ntp/google_landing_controller.mm
+++ b/ios/chrome/browser/ui/ntp/google_landing_controller.mm
@@ -6,68 +6,29 @@
 
 #include <algorithm>
 
-#include "base/i18n/case_conversion.h"
-#import "base/ios/weak_nsobject.h"
-#include "base/json/json_reader.h"
-#include "base/logging.h"
-#include "base/mac/bind_objc_block.h"
 #include "base/mac/foundation_util.h"
-#include "base/mac/scoped_nsobject.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
-#include "base/metrics/user_metrics_action.h"
 #include "base/strings/sys_string_conversions.h"
-#include "components/favicon/core/large_icon_service.h"
-#include "components/keyed_service/core/service_access_type.h"
-#include "components/ntp_tiles/most_visited_sites.h"
-#include "components/ntp_tiles/ntp_tile.h"
-#include "components/rappor/rappor_service_impl.h"
-#include "components/search_engines/template_url_service.h"
-#include "components/search_engines/template_url_service_observer.h"
 #include "components/strings/grit/components_strings.h"
-#include "ios/chrome/browser/application_context.h"
-#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
-#import "ios/chrome/browser/favicon/favicon_loader.h"
-#include "ios/chrome/browser/favicon/favicon_service_factory.h"
-#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
-#include "ios/chrome/browser/favicon/large_icon_cache.h"
-#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
-#include "ios/chrome/browser/notification_promo.h"
-#include "ios/chrome/browser/ntp_tiles/ios_most_visited_sites_factory.h"
-#import "ios/chrome/browser/ntp_tiles/most_visited_sites_observer_bridge.h"
-#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
-#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
-#include "ios/chrome/browser/suggestions/suggestions_service_factory.h"
-#import "ios/chrome/browser/tabs/tab_model.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
 #import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
 #include "ios/chrome/browser/ui/commands/ios_command_ids.h"
 #import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
+#import "ios/chrome/browser/ui/ntp/google_landing_data_source.h"
 #import "ios/chrome/browser/ui/ntp/most_visited_cell.h"
 #import "ios/chrome/browser/ui/ntp/most_visited_layout.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_header_constants.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_header_view.h"
-#import "ios/chrome/browser/ui/ntp/notification_promo_whats_new.h"
 #import "ios/chrome/browser/ui/ntp/whats_new_header_view.h"
-#import "ios/chrome/browser/ui/orientation_limiting_navigation_controller.h"
 #import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
-#import "ios/chrome/browser/ui/toolbar/toolbar_owner.h"
-#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
 #include "ios/chrome/browser/ui/ui_util.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
-#import "ios/chrome/browser/ui/url_loader.h"
 #include "ios/chrome/common/string_util.h"
 #include "ios/chrome/grit/ios_strings.h"
-#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
-#include "ios/public/provider/chrome/browser/ui/logo_vendor.h"
-#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
 #import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
 #import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
-#include "ios/web/public/referrer.h"
 #import "ios/web/public/web_state/context_menu_params.h"
 #import "net/base/mac/url_conversions.h"
-#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "ui/base/l10n/l10n_util.h"
 
 using base::UserMetricsAction;
@@ -99,7 +60,6 @@
 const CGFloat kWhatsNewHeaderHiddenHeight = 8;
 const CGFloat kDoodleTopMarginIPadPortrait = 82;
 const CGFloat kDoodleTopMarginIPadLandscape = 82;
-const NSInteger kMaxNumMostVisitedFavicons = 8;
 const NSInteger kMaxNumMostVisitedFaviconRows = 2;
 const CGFloat kMaxSearchFieldFrameMargin = 200;
 const CGFloat kShiftTilesDownAnimationDuration = 0.2;
@@ -109,38 +69,6 @@
 
 }  // namespace
 
-namespace google_landing {
-
-// Observer used to hide the Google logo and doodle if the TemplateURLService
-// changes.
-class SearchEngineObserver : public TemplateURLServiceObserver {
- public:
-  SearchEngineObserver(GoogleLandingController* owner,
-                       TemplateURLService* urlService);
-  ~SearchEngineObserver() override;
-  void OnTemplateURLServiceChanged() override;
-
- private:
-  base::WeakNSObject<GoogleLandingController> _owner;
-  TemplateURLService* _templateURLService;  // weak
-};
-
-SearchEngineObserver::SearchEngineObserver(GoogleLandingController* owner,
-                                           TemplateURLService* urlService)
-    : _owner(owner), _templateURLService(urlService) {
-  _templateURLService->AddObserver(this);
-}
-
-SearchEngineObserver::~SearchEngineObserver() {
-  _templateURLService->RemoveObserver(this);
-}
-
-void SearchEngineObserver::OnTemplateURLServiceChanged() {
-  [_owner reload];
-}
-
-}  // namespace google_landing
-
 @interface GoogleLandingController (UsedByGoogleLandingView)
 // Update frames for subviews depending on the interface orientation.
 - (void)updateSubviewFrames;
@@ -160,11 +88,6 @@
 
 @implementation GoogleLandingView
 
-- (void)layoutSubviews {
-  [super layoutSubviews];
-  [_googleLanding updateSubviewFrames];
-}
-
 - (void)setFrameDelegate:(GoogleLandingController*)delegate {
   _googleLanding = delegate;
 }
@@ -184,8 +107,7 @@
 
 @end
 
-@interface GoogleLandingController ()<MostVisitedSitesObserving,
-                                      OverscrollActionsControllerDelegate,
+@interface GoogleLandingController ()<OverscrollActionsControllerDelegate,
                                       UICollectionViewDataSource,
                                       UICollectionViewDelegate,
                                       UICollectionViewDelegateFlowLayout,
@@ -194,16 +116,6 @@
   // Fake omnibox.
   base::scoped_nsobject<UIButton> _searchTapTarget;
 
-  // Controller to fetch and show doodles or a default Google logo.
-  base::scoped_nsprotocol<id<LogoVendor>> _doodleController;
-
-  // Most visited data from the MostVisitedSites service (copied upon receiving
-  // the callback).
-  ntp_tiles::NTPTilesVector _mostVisitedData;
-
-  // |YES| if impressions were logged already and shouldn't be logged again.
-  BOOL _recordedPageImpression;
-
   // A collection view for the most visited sites.
   base::scoped_nsobject<UICollectionView> _mostVisitedView;
 
@@ -214,9 +126,6 @@
   // |YES| when notifications indicate the omnibox is focused.
   BOOL _omniboxFocused;
 
-  // Delegate to focus and blur the omnibox.
-  base::WeakNSProtocol<id<OmniboxFocuser>> _focuser;
-
   // Tap and swipe gesture recognizers when the omnibox is focused.
   base::scoped_nsobject<UITapGestureRecognizer> _tapGestureRecognizer;
   base::scoped_nsobject<UISwipeGestureRecognizer> _swipeGestureRecognizer;
@@ -224,57 +133,63 @@
   // Handles displaying the context menu for all form factors.
   base::scoped_nsobject<ContextMenuCoordinator> _contextMenuCoordinator;
 
-  // What's new promo.
-  std::unique_ptr<NotificationPromoWhatsNew> _notification_promo;
-
-  // A MostVisitedSites::Observer bridge object to get notified of most visited
-  // sites changes.
-  std::unique_ptr<ntp_tiles::MostVisitedSitesObserverBridge>
-      _most_visited_observer_bridge;
-
-  std::unique_ptr<ntp_tiles::MostVisitedSites> _most_visited_sites;
-
   // URL of the last deleted most viewed entry. If present the UI to restore it
   // is shown.
   base::scoped_nsobject<NSURL> _deletedUrl;
 
-  // Listen for default search engine changes.
-  std::unique_ptr<google_landing::SearchEngineObserver> _observer;
-  TemplateURLService* _templateURLService;  // weak
-
   // |YES| if the view has finished its first layout. This is useful when
   // determining if the view has sized itself for tablet.
   BOOL _viewLoaded;
 
+  // |YES| if the fakebox header should be animated on scroll.
   BOOL _animateHeader;
+
+  // |YES| if the collection scrollView is scrolled all the way to the top. Used
+  // to lock this position in place on various frame changes.
   BOOL _scrolledToTop;
+
+  // |YES| if this NTP panel is visible.  When set to |NO| various UI updates
+  // are ignored.
   BOOL _isShowing;
+
   CFTimeInterval _shiftTilesDownStartTime;
   CGSize _mostVisitedCellSize;
-  NSUInteger _maxNumMostVisited;
-  ios::ChromeBrowserState* _browserState;  // Weak.
-  id<UrlLoader> _loader;                   // Weak.
-  std::unique_ptr<
-      suggestions::SuggestionsService::ResponseCallbackList::Subscription>
-      _suggestionsServiceResponseSubscription;
   base::scoped_nsobject<NSLayoutConstraint> _hintLabelLeadingConstraint;
   base::scoped_nsobject<NSLayoutConstraint> _voiceTapTrailingConstraint;
   base::scoped_nsobject<NSMutableArray> _supplementaryViews;
   base::scoped_nsobject<NewTabPageHeaderView> _headerView;
   base::scoped_nsobject<WhatsNewHeaderView> _promoHeaderView;
-  base::WeakNSProtocol<id<WebToolbarDelegate>> _webToolbarDelegate;
-  base::scoped_nsobject<TabModel> _tabModel;
 }
 
-// Whether the Google logo or doodle is being shown.
-@property(nonatomic, readonly, getter=isShowingLogo) BOOL showingLogo;
-
-@property(nonatomic) id<UrlLoader> loader;
-
 // Redeclare the |view| property to be the GoogleLandingView subclass instead of
 // a generic UIView.
 @property(nonatomic, readwrite, strong) GoogleLandingView* view;
 
+// Whether the Google logo or doodle is being shown.
+@property(nonatomic, assign) BOOL logoIsShowing;
+
+// Exposes view and methods to drive the doodle.
+@property(nonatomic, assign) id<LogoVendor> logoVendor;
+
+// |YES| if this consumer is incognito.
+@property(nonatomic, assign) BOOL isOffTheRecord;
+
+// |YES| if this consumer is has voice search enabled.
+@property(nonatomic, assign) BOOL voiceSearchIsEnabled;
+
+// Gets the maximum number of sites shown.
+@property(nonatomic, assign) NSUInteger maximumMostVisitedSitesShown;
+
+// Gets the text of a what's new promo.
+@property(nonatomic, retain) NSString* promoText;
+
+// Gets the icon of a what's new promo.
+// TODO(crbug.com/694750): This should not be WhatsNewIcon.
+@property(nonatomic, assign) WhatsNewIcon promoIcon;
+
+// |YES| if a what's new promo can be displayed.
+@property(nonatomic, assign) BOOL promoCanShow;
+
 // iPhone landscape uses a slightly different layout for the doodle and search
 // field frame. Returns the proper frame from |frames| based on orientation,
 // centered in the view.
@@ -285,8 +200,6 @@
 - (CGRect)searchFieldFrame;
 // Returns the height to use for the What's New promo view.
 - (CGFloat)promoHeaderHeight;
-// Add the LogoController view.
-- (void)addDoodle;
 // Add fake search field and voice search microphone.
 - (void)addSearchField;
 // Add most visited collection view.
@@ -313,19 +226,12 @@
 - (void)setFlowLayoutInset:(UICollectionViewFlowLayout*)layout;
 // Instructs the UICollectionView and UIView to reload it's data and layout.
 - (void)reloadData;
-// Logs a histogram due to a Most Visited item being opened.
-- (void)logMostVisitedClick:(const NSUInteger)visitedIndex
-                   tileType:(ntp_tiles::TileVisualType)tileType;
-// Returns the size of |_mostVisitedData|.
+// Returns the size of |self.mostVisitedData|.
 - (NSUInteger)numberOfItems;
 // Returns the number of non empty tiles (as opposed to the placeholder tiles).
 - (NSInteger)numberOfNonEmptyTilesShown;
-// Returns the URL for the mosted visited item in |_mostVisitedData|.
+// Returns the URL for the mosted visited item in |self.mostVisitedData|.
 - (GURL)urlForIndex:(NSUInteger)index;
-// Removes a blacklisted URL in both |_mostVisitedData|.
-- (void)removeBlacklistedURL:(const GURL&)url;
-// Adds URL to the blacklist in both |_mostVisitedData|.
-- (void)addBlacklistedURL:(const GURL&)url;
 // Returns the expected height of the NewTabPageHeaderView.
 - (CGFloat)heightForSectionWithOmnibox;
 // Returns the nearest ancestor view that is kind of |aClass|.
@@ -333,105 +239,112 @@
 // Updates the collection view's scroll view offset for the next frame of the
 // shiftTilesDown animation.
 - (void)shiftTilesDownAnimationDidFire:(CADisplayLink*)link;
-// Returns the size to use for Most Visited cells in the NTP contained in
-// |view|.
-+ (CGSize)mostVisitedCellSizeForView:(UIView*)view;
+// Returns the size to use for Most Visited cells in the NTP.
+- (CGSize)mostVisitedCellSize;
 // Returns the padding for use between Most Visited cells.
-+ (CGFloat)mostVisitedCellPadding;
+- (CGFloat)mostVisitedCellPadding;
 
 @end
 
 @implementation GoogleLandingController
 
 @dynamic view;
-@synthesize loader = _loader;
+@synthesize logoVendor = _logoVendor;
+@synthesize dataSource = _dataSource;
 // Property declared in NewTabPagePanelProtocol.
 @synthesize delegate = _delegate;
-
-+ (NSUInteger)maxSitesShown {
-  return kMaxNumMostVisitedFavicons;
-}
-
-- (id)initWithLoader:(id<UrlLoader>)loader
-          browserState:(ios::ChromeBrowserState*)browserState
-               focuser:(id<OmniboxFocuser>)focuser
-    webToolbarDelegate:(id<WebToolbarDelegate>)webToolbarDelegate
-              tabModel:(TabModel*)tabModel {
-  self = [super init];
-  if (self) {
-    DCHECK(browserState);
-    _browserState = browserState;
-    _loader = loader;
-    _isShowing = YES;
-    _maxNumMostVisited = [GoogleLandingController maxSitesShown];
-
-    NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
-    [defaultCenter
-        addObserver:self
-           selector:@selector(locationBarBecomesFirstResponder)
-               name:ios_internal::kLocationBarBecomesFirstResponderNotification
-             object:nil];
-    [defaultCenter
-        addObserver:self
-           selector:@selector(locationBarResignsFirstResponder)
-               name:ios_internal::kLocationBarResignsFirstResponderNotification
-             object:nil];
-    [defaultCenter
-        addObserver:self
-           selector:@selector(orientationDidChange:)
-               name:UIApplicationDidChangeStatusBarOrientationNotification
-             object:nil];
-
-    _notification_promo.reset(new NotificationPromoWhatsNew(
-        GetApplicationContext()->GetLocalState()));
-    _notification_promo->Init();
-    _tapGestureRecognizer.reset([[UITapGestureRecognizer alloc]
-        initWithTarget:self
-                action:@selector(blurOmnibox)]);
-    [_tapGestureRecognizer setDelegate:self];
-    _swipeGestureRecognizer.reset([[UISwipeGestureRecognizer alloc]
-        initWithTarget:self
-                action:@selector(blurOmnibox)]);
-    [_swipeGestureRecognizer
-        setDirection:UISwipeGestureRecognizerDirectionDown];
-
-    _focuser.reset(focuser);
-    _webToolbarDelegate.reset(webToolbarDelegate);
-    _tabModel.reset([tabModel retain]);
-
-    _scrolledToTop = NO;
-    _animateHeader = YES;
-  }
-  return self;
-}
+@synthesize isOffTheRecord = _isOffTheRecord;
+@synthesize logoIsShowing = _logoIsShowing;
+@synthesize promoText = _promoText;
+@synthesize promoIcon = _promoIcon;
+@synthesize promoCanShow = _promoCanShow;
+@synthesize maximumMostVisitedSitesShown = _maximumMostVisitedSitesShown;
+@synthesize voiceSearchIsEnabled = _voiceSearchIsEnabled;
 
 - (void)loadView {
-  self.view =
-      [[GoogleLandingView alloc] initWithFrame:[UIScreen mainScreen].bounds];
+  self.view = [[[GoogleLandingView alloc]
+      initWithFrame:[UIScreen mainScreen].bounds] autorelease];
+}
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
   [self.view setAutoresizingMask:UIViewAutoresizingFlexibleHeight |
                                  UIViewAutoresizingFlexibleWidth];
   [self.view setFrameDelegate:self];
+
   // Initialise |shiftTilesDownStartTime| to a sentinel value to indicate that
   // the animation has not yet started.
   _shiftTilesDownStartTime = -1;
-  _mostVisitedCellSize =
-      [GoogleLandingController mostVisitedCellSizeForView:self.view];
-  [self addDoodle];
+  _mostVisitedCellSize = [self mostVisitedCellSize];
+  _isShowing = YES;
+  _scrolledToTop = NO;
+  _animateHeader = YES;
+
+  _tapGestureRecognizer.reset([[UITapGestureRecognizer alloc]
+      initWithTarget:self
+              action:@selector(blurOmnibox)]);
+  [_tapGestureRecognizer setDelegate:self];
+  _swipeGestureRecognizer.reset([[UISwipeGestureRecognizer alloc]
+      initWithTarget:self
+              action:@selector(blurOmnibox)]);
+  [_swipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionDown];
+
   [self addSearchField];
   [self addMostVisited];
   [self addOverscrollActions];
   [self reload];
 }
 
-+ (CGSize)mostVisitedCellSizeForView:(UIView*)view {
+- (void)viewDidLayoutSubviews {
+  [self updateSubviewFrames];
+}
+
+- (void)viewWillTransitionToSize:(CGSize)size
+       withTransitionCoordinator:
+           (id<UIViewControllerTransitionCoordinator>)coordinator {
+  [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
+
+  void (^alongsideBlock)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
+      id<UIViewControllerTransitionCoordinatorContext> context) {
+    if (IsIPadIdiom() && _scrolledToTop) {
+      // Keep the most visited thumbnails scrolled to the top.
+      [_mostVisitedView setContentOffset:CGPointMake(0, [self pinnedOffsetY])];
+      return;
+    };
+
+    // Invalidate the layout so that the collection view's header size is reset
+    // for the new orientation.
+    if (!_scrolledToTop) {
+      [[_mostVisitedView collectionViewLayout] invalidateLayout];
+    }
+
+    // Call -scrollViewDidScroll: so that the omnibox's frame is adjusted for
+    // the scroll view's offset.
+    [self scrollViewDidScroll:_mostVisitedView];
+
+  };
+  [coordinator animateAlongsideTransition:alongsideBlock completion:nil];
+}
+
+- (void)dealloc {
+  [[NSNotificationCenter defaultCenter] removeObserver:self];
+  [_mostVisitedView setDelegate:nil];
+  [_mostVisitedView setDataSource:nil];
+  [_overscrollActionsController invalidate];
+  [super dealloc];
+}
+
+#pragma mark - Private
+
+- (CGSize)mostVisitedCellSize {
   if (IsIPadIdiom()) {
     // On iPads, split-screen and slide-over may require showing smaller cells.
     CGSize maximumCellSize = [MostVisitedCell maximumSize];
-    CGSize viewSize = view.bounds.size;
+    CGSize viewSize = self.view.bounds.size;
     CGFloat smallestDimension =
         viewSize.height > viewSize.width ? viewSize.width : viewSize.height;
     CGFloat cellWidth = AlignValueToPixel(
-        (smallestDimension - 3 * [self.class mostVisitedCellPadding]) / 2);
+        (smallestDimension - 3 * [self mostVisitedCellPadding]) / 2);
     if (cellWidth < maximumCellSize.width) {
       return CGSizeMake(cellWidth, cellWidth);
     } else {
@@ -442,58 +355,18 @@
   }
 }
 
-+ (CGFloat)mostVisitedCellPadding {
+- (CGFloat)mostVisitedCellPadding {
   return IsIPadIdiom() ? kMostVisitedPaddingIPadFavicon
                        : kMostVisitedPaddingIPhone;
 }
 
-- (void)orientationDidChange:(NSNotification*)notification {
-  if (IsIPadIdiom() && _scrolledToTop) {
-    // Keep the most visited thumbnails scrolled to the top.
-    base::WeakNSObject<GoogleLandingController> weakSelf(self);
-    dispatch_after(
-        dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
-          base::scoped_nsobject<GoogleLandingController> strongSelf(
-              [weakSelf retain]);
-          if (!strongSelf)
-            return;
-
-          [strongSelf.get()->_mostVisitedView
-              setContentOffset:CGPointMake(0, [strongSelf pinnedOffsetY])];
-        });
-    return;
-  }
-
-  // Call inside a block to avoid the animation that -orientationDidChange is
-  // wrapped inside.
-  base::WeakNSObject<GoogleLandingController> weakSelf(self);
-  void (^layoutBlock)(void) = ^{
-    base::scoped_nsobject<GoogleLandingController> strongSelf(
-        [weakSelf retain]);
-    // Invalidate the layout so that the collection view's header size is reset
-    // for the new orientation.
-    if (!_scrolledToTop) {
-      [[strongSelf.get()->_mostVisitedView collectionViewLayout]
-          invalidateLayout];
-      [[strongSelf view] setNeedsLayout];
-    }
-
-    // Call -scrollViewDidScroll: so that the omnibox's frame is adjusted for
-    // the scroll view's offset.
-    [self scrollViewDidScroll:_mostVisitedView];
-  };
-
-  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(),
-                 layoutBlock);
-}
-
 - (CGFloat)viewWidth {
   return [self.view frame].size.width;
 }
 
 - (int)numberOfColumns {
   CGFloat width = [self viewWidth];
-  CGFloat padding = [self.class mostVisitedCellPadding];
+  CGFloat padding = [self mostVisitedCellPadding];
   // Try to fit 4 columns.
   if (width >= 5 * padding + _mostVisitedCellSize.width * 4)
     return 4;
@@ -513,26 +386,18 @@
 - (CGFloat)leftMargin {
   int columns = [self numberOfColumns];
   CGFloat whitespace = [self viewWidth] - columns * _mostVisitedCellSize.width -
-                       (columns - 1) * [self.class mostVisitedCellPadding];
+                       (columns - 1) * [self mostVisitedCellPadding];
   CGFloat margin = AlignValueToPixel(whitespace / 2);
-  DCHECK(margin >= [self.class mostVisitedCellPadding]);
+  DCHECK(margin >= [self mostVisitedCellPadding]);
   return margin;
 }
 
-- (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-  [_mostVisitedView setDelegate:nil];
-  [_mostVisitedView setDataSource:nil];
-  [_overscrollActionsController invalidate];
-  [super dealloc];
-}
-
 - (CGRect)doodleFrame {
   const CGRect kDoodleFrame[2] = {
       {{0, 66}, {0, 120}}, {{0, 56}, {0, 120}},
   };
   CGRect doodleFrame = [self getOrientationFrame:kDoodleFrame];
-  if (!IsIPadIdiom() && !self.showingLogo)
+  if (!IsIPadIdiom() && !self.logoIsShowing)
     doodleFrame.size.height = kNonGoogleSearchDoodleHeight;
   if (IsIPadIdiom()) {
     doodleFrame.origin.y = IsPortrait() ? kDoodleTopMarginIPadPortrait
@@ -574,33 +439,13 @@
 
 - (CGFloat)promoHeaderHeight {
   CGFloat promoMaxWidth = [self viewWidth] - 2 * [self leftMargin];
-  NSString* text = base::SysUTF8ToNSString(_notification_promo->promo_text());
+  NSString* text = self.promoText;
   return [WhatsNewHeaderView heightToFitText:text inWidth:promoMaxWidth];
 }
 
-- (ToolbarController*)relinquishedToolbarController {
-  return [_headerView relinquishedToolbarController];
-}
-
-- (void)reparentToolbarController {
-  [_headerView reparentToolbarController];
-}
-
-- (BOOL)isShowingLogo {
-  return [_doodleController isShowingLogo];
-}
-
 - (void)updateLogoAndFakeboxDisplay {
-  BOOL showLogo = NO;
-  TemplateURL* defaultURL = _templateURLService->GetDefaultSearchProvider();
-  if (defaultURL) {
-    showLogo =
-        defaultURL->GetEngineType(_templateURLService->search_terms_data()) ==
-        SEARCH_ENGINE_GOOGLE;
-  }
-
-  if (self.showingLogo != showLogo) {
-    [_doodleController setShowingLogo:showLogo];
+  if (self.logoVendor.showingLogo != self.logoIsShowing) {
+    self.logoVendor.showingLogo = self.logoIsShowing;
     if (_viewLoaded) {
       [self updateSubviewFrames];
 
@@ -622,25 +467,10 @@
       [_promoHeaderView setFrame:whatsNewFrame];
     }
     if (IsIPadIdiom())
-      [_searchTapTarget setHidden:!self.showingLogo];
+      [_searchTapTarget setHidden:!self.logoIsShowing];
   }
 }
 
-// Initialize and add a Google Doodle widget, show a Google logo by default.
-- (void)addDoodle {
-  if (!_doodleController) {
-    _doodleController.reset(ios::GetChromeBrowserProvider()->CreateLogoVendor(
-        _browserState, _loader));
-  }
-  [[_doodleController view] setFrame:[self doodleFrame]];
-
-  _templateURLService =
-      ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
-  _observer.reset(
-      new google_landing::SearchEngineObserver(self, _templateURLService));
-  _templateURLService->Load();
-}
-
 // Initialize and add a search field tap target and a voice search button.
 - (void)addSearchField {
   CGRect searchFieldFrame = [self searchFieldFrame];
@@ -764,9 +594,7 @@
                                             IDS_IOS_ACCNAME_VOICE_SEARCH)];
   [voiceTapTarget setAccessibilityIdentifier:@"Voice Search"];
 
-  if (ios::GetChromeBrowserProvider()
-          ->GetVoiceSearchProvider()
-          ->IsVoiceSearchEnabled()) {
+  if (self.voiceSearchIsEnabled) {
     [voiceTapTarget addTarget:self
                        action:@selector(loadVoiceSearch:)
              forControlEvents:UIControlEventTouchUpInside];
@@ -779,17 +607,13 @@
 }
 
 - (void)loadVoiceSearch:(id)sender {
-  DCHECK(ios::GetChromeBrowserProvider()
-             ->GetVoiceSearchProvider()
-             ->IsVoiceSearchEnabled());
+  DCHECK(self.voiceSearchIsEnabled);
   base::RecordAction(UserMetricsAction("MobileNTPMostVisitedVoiceSearch"));
   [sender chromeExecuteCommand:sender];
 }
 
 - (void)preloadVoiceSearch:(id)sender {
-  DCHECK(ios::GetChromeBrowserProvider()
-             ->GetVoiceSearchProvider()
-             ->IsVoiceSearchEnabled());
+  DCHECK(self.voiceSearchIsEnabled);
   [sender removeTarget:self
                 action:@selector(preloadVoiceSearch:)
       forControlEvents:UIControlEventTouchDown];
@@ -813,13 +637,12 @@
 }
 
 - (void)updateSubviewFrames {
-  _mostVisitedCellSize =
-      [GoogleLandingController mostVisitedCellSizeForView:self.view];
+  _mostVisitedCellSize = [self mostVisitedCellSize];
   UICollectionViewFlowLayout* flowLayout =
       base::mac::ObjCCastStrict<UICollectionViewFlowLayout>(
           [_mostVisitedView collectionViewLayout]);
   [flowLayout setItemSize:_mostVisitedCellSize];
-  [[_doodleController view] setFrame:[self doodleFrame]];
+  self.logoVendor.view.frame = [self doodleFrame];
 
   [self setFlowLayoutInset:flowLayout];
   [flowLayout invalidateLayout];
@@ -855,7 +678,7 @@
 
   if (!_viewLoaded) {
     _viewLoaded = YES;
-    [_doodleController fetchDoodle];
+    [self.logoVendor fetchDoodle];
   }
   [self.delegate updateNtpBarShadowForPanelController:self];
 }
@@ -872,7 +695,7 @@
   [flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
   [flowLayout setItemSize:_mostVisitedCellSize];
   [flowLayout setMinimumInteritemSpacing:8];
-  [flowLayout setMinimumLineSpacing:[self.class mostVisitedCellPadding]];
+  [flowLayout setMinimumLineSpacing:[self mostVisitedCellPadding]];
   DCHECK(!_mostVisitedView);
   _mostVisitedView.reset([[UICollectionView alloc]
              initWithFrame:mostVisitedFrame
@@ -896,12 +719,6 @@
   [_mostVisitedView setAccessibilityIdentifier:@"Google Landing"];
 
   [self.view addSubview:_mostVisitedView];
-  _most_visited_sites =
-      IOSMostVisitedSitesFactory::NewForBrowserState(_browserState);
-  _most_visited_observer_bridge.reset(
-      new ntp_tiles::MostVisitedSitesObserverBridge(self));
-  _most_visited_sites->SetMostVisitedURLsObserver(
-      _most_visited_observer_bridge.get(), kMaxNumMostVisitedFavicons);
 }
 
 - (void)updateSearchField {
@@ -924,10 +741,8 @@
 
 // Check to see if the promo label should be hidden.
 - (void)hideWhatsNewIfNecessary {
-  if (![_promoHeaderView isHidden] && _notification_promo &&
-      !_notification_promo->CanShow()) {
+  if (![_promoHeaderView isHidden] && !self.promoCanShow) {
     [_promoHeaderView setHidden:YES];
-    _notification_promo.reset();
     [self.view setNeedsLayout];
   }
 }
@@ -963,7 +778,7 @@
         if (_scrolledToTop) {
           _animateHeader = NO;
           if (!IsIPadIdiom()) {
-            [_focuser onFakeboxAnimationComplete];
+            [self.dataSource onFakeboxAnimationComplete];
             [_headerView fadeOutShadow];
             [_searchTapTarget setHidden:YES];
           }
@@ -972,12 +787,12 @@
 }
 
 - (void)searchFieldTapped:(id)sender {
-  [_focuser focusFakebox];
+  [self.dataSource focusFakebox];
 }
 
 - (void)blurOmnibox {
   if (_omniboxFocused) {
-    [_focuser cancelOmniboxEdit];
+    [self.dataSource cancelOmniboxEdit];
   } else {
     [self locationBarResignsFirstResponder];
   }
@@ -1000,7 +815,7 @@
   _scrolledToTop = NO;
   if (!IsIPadIdiom()) {
     [_searchTapTarget setHidden:NO];
-    [_focuser onFakeboxBlur];
+    [self.dataSource onFakeboxBlur];
   }
 
   // Reload most visited sites in case the number of placeholder cells needs to
@@ -1056,17 +871,8 @@
   }
 }
 
-- (void)logMostVisitedClick:(const NSUInteger)visitedIndex
-                   tileType:(ntp_tiles::TileVisualType)tileType {
-  new_tab_page_uma::RecordAction(
-      _browserState, new_tab_page_uma::ACTION_OPENED_MOST_VISITED_ENTRY);
-  base::RecordAction(UserMetricsAction("MobileNTPMostVisited"));
-  const ntp_tiles::NTPTile& tile = _mostVisitedData[visitedIndex];
-  ntp_tiles::metrics::RecordTileClick(visitedIndex, tile.source, tileType);
-}
-
 - (void)reloadData {
-  // -reloadData updates from |_mostVisitedData|.
+  // -reloadData updates from |self.mostVisitedData|.
   // -invalidateLayout is necessary because sometimes the flowLayout has the
   // wrong cached size and will throw an internal exception if the
   // -numberOfItems shrinks. -setNeedsLayout is needed in case
@@ -1077,16 +883,12 @@
   [self.view setNeedsLayout];
 }
 
-- (void)willUpdateSnapshot {
-  [_overscrollActionsController clear];
-}
-
 - (CGFloat)heightForSectionWithOmnibox {
   CGFloat headerHeight =
       CGRectGetMaxY([self searchFieldFrame]) + kNTPSearchFieldBottomPadding;
   if (IsIPadIdiom()) {
-    if (self.showingLogo) {
-      if (!_notification_promo || !_notification_promo->CanShow()) {
+    if (self.logoIsShowing) {
+      if (!self.promoCanShow) {
         UIInterfaceOrientation orient =
             [[UIApplication sharedApplication] statusBarOrientation];
         const CGFloat kTopSpacingMaterialPortrait = 56;
@@ -1102,34 +904,14 @@
   return headerHeight;
 }
 
-#pragma mark - MostVisitedSitesObserving
+#pragma mark - ToolbarOwner
 
-- (void)onMostVisitedURLsAvailable:(const ntp_tiles::NTPTilesVector&)data {
-  _mostVisitedData = data;
-  [self reloadData];
-
-  if (data.size() && !_recordedPageImpression) {
-    _recordedPageImpression = YES;
-    std::vector<ntp_tiles::metrics::TileImpression> tiles;
-    for (const ntp_tiles::NTPTile& ntpTile : data) {
-      tiles.emplace_back(ntpTile.source, ntp_tiles::UNKNOWN_TILE_TYPE,
-                         ntpTile.url);
-    }
-    ntp_tiles::metrics::RecordPageImpression(
-        tiles, GetApplicationContext()->GetRapporServiceImpl());
-  }
+- (ToolbarController*)relinquishedToolbarController {
+  return [_headerView relinquishedToolbarController];
 }
 
-- (void)onIconMadeAvailable:(const GURL&)siteUrl {
-  for (size_t i = 0; i < [self numberOfItems]; ++i) {
-    const ntp_tiles::NTPTile& ntpTile = _mostVisitedData[i];
-    if (ntpTile.url == siteUrl) {
-      NSIndexPath* indexPath =
-          [NSIndexPath indexPathForRow:i inSection:SectionWithMostVisited];
-      [_mostVisitedView reloadItemsAtIndexPaths:@[ indexPath ]];
-      break;
-    }
-  }
+- (void)reparentToolbarController {
+  [_headerView reparentToolbarController];
 }
 
 #pragma mark - UICollectionView Methods.
@@ -1144,7 +926,7 @@
     ((UICollectionViewFlowLayout*)collectionViewLayout).headerReferenceSize =
         CGSizeMake(0, headerHeight);
   } else if (section == SectionWithMostVisited) {
-    if (_notification_promo && _notification_promo->CanShow()) {
+    if (self.promoCanShow) {
       headerHeight = [self promoHeaderHeight];
     } else {
       headerHeight = kWhatsNewHeaderHiddenHeight;
@@ -1181,11 +963,11 @@
   const NSUInteger visitedIndex = indexPath.row;
   [self blurOmnibox];
   DCHECK(visitedIndex < [self numberOfItems]);
-  [self logMostVisitedClick:visitedIndex tileType:cell.tileType];
-  [_loader loadURL:[self urlForIndex:visitedIndex]
-               referrer:web::Referrer()
-             transition:ui::PAGE_TRANSITION_AUTO_BOOKMARK
-      rendererInitiated:NO];
+  [self.dataSource logMostVisitedClick:visitedIndex tileType:cell.tileType];
+  [self.dataSource loadURL:[self urlForIndex:visitedIndex]
+                  referrer:web::Referrer()
+                transition:ui::PAGE_TRANSITION_AUTO_BOOKMARK
+         rendererInitiated:NO];
 }
 
 #pragma mark - UICollectionViewDataSource
@@ -1205,19 +987,14 @@
               UICollectionElementKindSectionHeader
                              withReuseIdentifier:@"header"
                                     forIndexPath:indexPath] retain]);
-      [_headerView addSubview:[_doodleController view]];
+      [_headerView addSubview:[self.logoVendor view]];
       [_headerView addSubview:_searchTapTarget];
       [_headerView addViewsToSearchField:_searchTapTarget];
 
       if (!IsIPadIdiom()) {
-        ReadingListModel* readingListModel =
-            ReadingListModelFactory::GetForBrowserState(_browserState);
         // iPhone header also contains a toolbar since the normal toolbar is
         // hidden.
-        [_headerView addToolbarWithDelegate:_webToolbarDelegate
-                                    focuser:_focuser
-                                   tabModel:_tabModel
-                           readingListModel:readingListModel];
+        [_headerView addToolbarWithDataSource:self.dataSource];
       }
       [_supplementaryViews addObject:_headerView];
     }
@@ -1233,11 +1010,10 @@
                                     forIndexPath:indexPath] retain]);
       [_promoHeaderView setSideMargin:[self leftMargin]];
       [_promoHeaderView setDelegate:self];
-      if (_notification_promo && _notification_promo->CanShow()) {
-        [_promoHeaderView
-            setText:base::SysUTF8ToNSString(_notification_promo->promo_text())];
-        [_promoHeaderView setIcon:_notification_promo->icon()];
-        _notification_promo->HandleViewed();
+      if (self.promoCanShow) {
+        [_promoHeaderView setText:self.promoText];
+        [_promoHeaderView setIcon:self.promoIcon];
+        [self.dataSource promoViewed];
       }
       [_supplementaryViews addObject:_promoHeaderView];
     }
@@ -1262,7 +1038,7 @@
   // Phone always contains the maximum number of cells. Cells in excess of the
   // number of thumbnails are used solely for layout/sizing.
   if (!IsIPadIdiom())
-    return _maxNumMostVisited;
+    return self.maximumMostVisitedSitesShown;
 
   return [self numberOfNonEmptyTilesShown];
 }
@@ -1288,10 +1064,11 @@
     return cell;
   }
 
-  const ntp_tiles::NTPTile& ntpTile = _mostVisitedData[indexPath.row];
+  const ntp_tiles::NTPTile& ntpTile =
+      [self.dataSource mostVisitedAtIndex:indexPath.row];
   NSString* title = base::SysUTF16ToNSString(ntpTile.title);
 
-  [cell setupWithURL:ntpTile.url title:title browserState:_browserState];
+  [cell setupWithURL:ntpTile.url title:title dataSource:self.dataSource];
 
   base::scoped_nsobject<UILongPressGestureRecognizer> longPress(
       [[UILongPressGestureRecognizer alloc]
@@ -1345,18 +1122,19 @@
       if (!strongSelf)
         return;
       MostVisitedCell* cell = (MostVisitedCell*)sender.view;
-      [strongSelf logMostVisitedClick:index tileType:cell.tileType];
-      [[strongSelf loader] webPageOrderedOpen:url
-                                     referrer:web::Referrer()
-                                 inBackground:YES
-                                     appendTo:kCurrentTab];
+      [[strongSelf dataSource] logMostVisitedClick:index
+                                          tileType:cell.tileType];
+      [[strongSelf dataSource] webPageOrderedOpen:url
+                                         referrer:web::Referrer()
+                                     inBackground:YES
+                                         appendTo:kCurrentTab];
     };
     [_contextMenuCoordinator
         addItemWithTitle:l10n_util::GetNSStringWithFixup(
                              IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB)
                   action:action];
 
-    if (!_browserState->IsOffTheRecord()) {
+    if (!self.isOffTheRecord) {
       // Open in Incognito Tab.
       action = ^{
         base::scoped_nsobject<GoogleLandingController> strongSelf(
@@ -1364,12 +1142,13 @@
         if (!strongSelf)
           return;
         MostVisitedCell* cell = (MostVisitedCell*)sender.view;
-        [strongSelf logMostVisitedClick:index tileType:cell.tileType];
-        [[strongSelf loader] webPageOrderedOpen:url
-                                       referrer:web::Referrer()
-                                    inIncognito:YES
-                                   inBackground:NO
-                                       appendTo:kCurrentTab];
+        [[strongSelf dataSource] logMostVisitedClick:index
+                                            tileType:cell.tileType];
+        [[strongSelf dataSource] webPageOrderedOpen:url
+                                           referrer:web::Referrer()
+                                        inIncognito:YES
+                                       inBackground:NO
+                                           appendTo:kCurrentTab];
       };
       [_contextMenuCoordinator
           addItemWithTitle:l10n_util::GetNSStringWithFixup(
@@ -1387,7 +1166,7 @@
       if (!strongSelf)
         return;
       base::RecordAction(UserMetricsAction("MostVisited_UrlBlacklisted"));
-      [strongSelf addBlacklistedURL:url];
+      [[strongSelf dataSource] addBlacklistedURL:url];
       [strongSelf showMostVisitedUndoForURL:net::NSURLWithGURL(url)];
     };
     [_contextMenuCoordinator addItemWithTitle:title action:action];
@@ -1410,7 +1189,8 @@
         [weakSelf retain]);
     if (!strongSelf)
       return;
-    [strongSelf removeBlacklistedURL:net::GURLWithNSURL(_deletedUrl)];
+    [[strongSelf dataSource]
+        removeBlacklistedURL:net::GURLWithNSURL(_deletedUrl)];
   };
   action.title = l10n_util::GetNSString(IDS_NEW_TAB_UNDO_THUMBNAIL_REMOVE);
   action.accessibilityIdentifier = @"Undo";
@@ -1425,30 +1205,10 @@
 }
 
 - (void)onPromoLabelTapped {
-  [_focuser cancelOmniboxEdit];
-  _notification_promo->HandleClosed();
+  [self.dataSource cancelOmniboxEdit];
   [_promoHeaderView setHidden:YES];
   [self.view setNeedsLayout];
-
-  if (_notification_promo->IsURLPromo()) {
-    [_loader webPageOrderedOpen:_notification_promo->url()
-                       referrer:web::Referrer()
-                   inBackground:NO
-                       appendTo:kCurrentTab];
-    _notification_promo.reset();
-    return;
-  }
-
-  if (_notification_promo->IsChromeCommand()) {
-    base::scoped_nsobject<GenericChromeCommand> command(
-        [[GenericChromeCommand alloc]
-            initWithTag:_notification_promo->command_id()]);
-    [self.view chromeExecuteCommand:command];
-    _notification_promo.reset();
-    return;
-  }
-
-  NOTREACHED();
+  [self.dataSource promoTapped];
 }
 
 // Returns the Y value to use for the scroll view's contentOffset when scrolling
@@ -1469,7 +1229,7 @@
   // Fetch the doodle after the view finishes laying out. Otherwise, tablet
   // may fetch the wrong sized doodle.
   if (_viewLoaded)
-    [_doodleController fetchDoodle];
+    [self.logoVendor fetchDoodle];
   [self updateLogoAndFakeboxDisplay];
   [self hideWhatsNewIfNecessary];
 }
@@ -1522,10 +1282,14 @@
   return alpha;
 }
 
+- (void)willUpdateSnapshot {
+  [_overscrollActionsController clear];
+}
+
 #pragma mark - LogoAnimationControllerOwnerOwner
 
 - (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
-  return [_doodleController logoAnimationControllerOwner];
+  return [self.logoVendor logoAnimationControllerOwner];
 }
 
 #pragma mark - UIScrollViewDelegate Methods.
@@ -1538,7 +1302,7 @@
   CGFloat pinnedOffsetY = [self pinnedOffsetY];
   if (_omniboxFocused && scrollView.dragging &&
       scrollView.contentOffset.y < pinnedOffsetY) {
-    [_focuser cancelOmniboxEdit];
+    [self.dataSource cancelOmniboxEdit];
   }
 
   if (IsIPadIdiom()) {
@@ -1611,32 +1375,20 @@
 
 #pragma mark - Most visited / Suggestions service wrapper methods.
 
-- (suggestions::SuggestionsService*)suggestionsService {
-  return suggestions::SuggestionsServiceFactory::GetForBrowserState(
-      _browserState);
-}
-
 - (NSUInteger)numberOfItems {
-  NSUInteger numItems = _mostVisitedData.size();
+  NSUInteger numItems = [self.dataSource mostVisitedSize];
   NSUInteger maxItems = [self numberOfColumns] * kMaxNumMostVisitedFaviconRows;
   return MIN(maxItems, numItems);
 }
 
 - (NSInteger)numberOfNonEmptyTilesShown {
-  NSInteger numCells = MIN([self numberOfItems], _maxNumMostVisited);
+  NSInteger numCells =
+      MIN([self numberOfItems], self.maximumMostVisitedSitesShown);
   return MAX(numCells, [self numberOfColumns]);
 }
 
 - (GURL)urlForIndex:(NSUInteger)index {
-  return _mostVisitedData[index].url;
-}
-
-- (void)addBlacklistedURL:(const GURL&)url {
-  _most_visited_sites->AddOrRemoveBlacklistedUrl(url, true);
-}
-
-- (void)removeBlacklistedURL:(const GURL&)url {
-  _most_visited_sites->AddOrRemoveBlacklistedUrl(url, false);
+  return [self.dataSource mostVisitedAtIndex:index].url;
 }
 
 #pragma mark - GoogleLandingController (ExposedForTesting) methods.
@@ -1712,4 +1464,24 @@
   return [self nearestAncestorOfView:[view superview] withClass:aClass];
 }
 
+#pragma mark - GoogleLandingConsumer
+
+- (void)setLogoIsShowing:(BOOL)logoIsShowing {
+  _logoIsShowing = logoIsShowing;
+  [self updateLogoAndFakeboxDisplay];
+}
+
+- (void)mostVisitedDataUpdated {
+  [self reloadData];
+}
+
+- (void)mostVisitedIconMadeAvailableAtIndex:(NSUInteger)index {
+  if (index > [self numberOfItems])
+    return;
+
+  NSIndexPath* indexPath =
+      [NSIndexPath indexPathForRow:index inSection:SectionWithMostVisited];
+  [_mostVisitedView reloadItemsAtIndexPaths:@[ indexPath ]];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/ntp/google_landing_controller_unittest.mm b/ios/chrome/browser/ui/ntp/google_landing_controller_unittest.mm
index 99b3cf8e..3a711ff 100644
--- a/ios/chrome/browser/ui/ntp/google_landing_controller_unittest.mm
+++ b/ios/chrome/browser/ui/ntp/google_landing_controller_unittest.mm
@@ -11,6 +11,7 @@
 #include "ios/chrome/browser/search_engines/template_url_service_factory.h"
 #include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
 #import "ios/chrome/browser/ui/ntp/google_landing_controller.h"
+#import "ios/chrome/browser/ui/ntp/google_landing_mediator.h"
 #include "ios/chrome/test/block_cleanup_test.h"
 #include "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
 #include "ios/web/public/test/test_web_thread.h"
@@ -53,9 +54,11 @@
 
     // Set up stub UrlLoader.
     mockUrlLoader_ = [OCMockObject mockForProtocol:@protocol(UrlLoader)];
-    controller_ = [[GoogleLandingController alloc]
-            initWithLoader:(id<UrlLoader>)mockUrlLoader_
+    controller_ = [[GoogleLandingController alloc] init];
+    mediator_ = [[GoogleLandingMediator alloc]
+          initWithConsumer:controller_
               browserState:chrome_browser_state_.get()
+                    loader:(id<UrlLoader>)mockUrlLoader_
                    focuser:nil
         webToolbarDelegate:nil
                   tabModel:nil];
@@ -67,6 +70,7 @@
   IOSChromeScopedTestingLocalState local_state_;
   std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
   OCMockObject* mockUrlLoader_;
+  GoogleLandingMediator* mediator_;
   GoogleLandingController* controller_;
 };
 
diff --git a/ios/chrome/browser/ui/ntp/google_landing_data_source.h b/ios/chrome/browser/ui/ntp/google_landing_data_source.h
new file mode 100644
index 0000000..48e78e7e7
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/google_landing_data_source.h
@@ -0,0 +1,73 @@
+// Copyright 2017 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 IOS_CHROME_BROWSER_UI_NTP_GOOGLE_LANDING_DATA_SOURCE_H_
+#define IOS_CHROME_BROWSER_UI_NTP_GOOGLE_LANDING_DATA_SOURCE_H_
+
+#import <Foundation/Foundation.h>
+
+#include "components/ntp_tiles/ntp_tile.h"
+#include "components/ntp_tiles/tile_visual_type.h"
+#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
+#import "ios/chrome/browser/ui/url_loader.h"
+#include "url/gurl.h"
+
+class ReadingListModel;
+class LargeIconCache;
+namespace favicon {
+class LargeIconService;
+}
+@class TabModel;
+@protocol WebToolbarDelegate;
+
+// DataSource for the google landing controller.
+// TODO(crbug.com/694750): Most everything here can be moved to dispatcher.
+@protocol GoogleLandingDataSource<OmniboxFocuser, UrlLoader>
+
+// Removes a blacklisted URL in both |_mostVisitedData|.
+- (void)removeBlacklistedURL:(const GURL&)url;
+
+// Adds URL to the blacklist in both |_mostVisitedData|.
+- (void)addBlacklistedURL:(const GURL&)url;
+
+// Logs a histogram due to a Most Visited item being opened.
+- (void)logMostVisitedClick:(const NSUInteger)visitedIndex
+                   tileType:(ntp_tiles::TileVisualType)tileType;
+
+// Called when a what's new promo is viewed.
+- (void)promoViewed;
+
+// Called when a what's new promo is tapped.
+- (void)promoTapped;
+
+// TODO(crbug.com/694750): The following two methods should be moved to the
+// consumer, and converted into types more suitable for a consumer.
+// Gets an a most visited NTP tile at |index|.
+- (ntp_tiles::NTPTile)mostVisitedAtIndex:(NSUInteger)index;
+
+// Gets the number of most visited entries.
+- (NSUInteger)mostVisitedSize;
+
+// TODO(crbug.com/694750): The following five properties will be removed in
+// subsequent CLs, with data provided via GoogleDataConsumer into types more
+// suitable for a consumer.
+
+// Gets the reading list model.
+- (ReadingListModel*)readingListModel;
+
+// Gets the large icon cache.
+- (LargeIconCache*)largeIconCache;
+
+// Gets the large icon service.
+- (favicon::LargeIconService*)largeIconService;
+
+// Gets the toolbar delegate.
+- (id<WebToolbarDelegate>)toolbarDelegate;
+
+// Gets the tab model.
+- (TabModel*)tabModel;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_NTP_GOOGLE_LANDING_DATA_SOURCE_H_
diff --git a/ios/chrome/browser/ui/ntp/google_landing_mediator.h b/ios/chrome/browser/ui/ntp/google_landing_mediator.h
new file mode 100644
index 0000000..95112f9
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/google_landing_mediator.h
@@ -0,0 +1,36 @@
+// Copyright 2017 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 IOS_CHROME_BROWSER_UI_NTP_GOOGLE_LANDING_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_UI_NTP_GOOGLE_LANDING_MEDIATOR_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/chrome/browser/ui/ntp/google_landing_data_source.h"
+
+@protocol GoogleLandingConsumer;
+@protocol OmniboxFocuser;
+@protocol UrlLoader;
+
+namespace ios {
+class ChromeBrowserState;
+}
+
+// A mediator object that provides various data sources for google landing.
+@interface GoogleLandingMediator : NSObject<GoogleLandingDataSource>
+
+- (instancetype)initWithConsumer:(id<GoogleLandingConsumer>)consumer
+                    browserState:(ios::ChromeBrowserState*)browserState
+                          loader:(id<UrlLoader>)loader
+                         focuser:(id<OmniboxFocuser>)focuser
+              webToolbarDelegate:(id<WebToolbarDelegate>)webToolbarDelegate
+                        tabModel:(TabModel*)tabModel NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+
+// Get the maximum number of sites shown.
++ (NSUInteger)maxSitesShown;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_NTP_GOOGLE_LANDING_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/ntp/google_landing_mediator.mm b/ios/chrome/browser/ui/ntp/google_landing_mediator.mm
new file mode 100644
index 0000000..1014342
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/google_landing_mediator.mm
@@ -0,0 +1,392 @@
+// Copyright 2017 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.
+
+#import "ios/chrome/browser/ui/ntp/google_landing_mediator.h"
+
+#import "base/ios/weak_nsobject.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/metrics/user_metrics.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/ntp_tiles/metrics.h"
+#include "components/ntp_tiles/most_visited_sites.h"
+#include "components/ntp_tiles/ntp_tile.h"
+#include "components/rappor/rappor_service_impl.h"
+#include "components/search_engines/template_url_service.h"
+#include "components/search_engines/template_url_service_observer.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/favicon/ios_chrome_large_icon_cache_factory.h"
+#include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
+#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
+#include "ios/chrome/browser/ntp_tiles/ios_most_visited_sites_factory.h"
+#import "ios/chrome/browser/ntp_tiles/most_visited_sites_observer_bridge.h"
+#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
+#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
+#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
+#import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
+#import "ios/chrome/browser/ui/ntp/google_landing_consumer.h"
+#import "ios/chrome/browser/ui/ntp/notification_promo_whats_new.h"
+#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
+#import "ios/chrome/browser/ui/url_loader.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
+
+using base::UserMetricsAction;
+
+namespace {
+
+const NSInteger kMaxNumMostVisitedFavicons = 8;
+
+}  // namespace
+
+@interface GoogleLandingMediator (UsedBySearchEngineObserver)
+// Check to see if the logo visibility should change.
+- (void)updateShowLogo;
+@end
+
+namespace google_landing {
+
+// Observer used to hide the Google logo and doodle if the TemplateURLService
+// changes.
+class SearchEngineObserver : public TemplateURLServiceObserver {
+ public:
+  SearchEngineObserver(GoogleLandingMediator* owner,
+                       TemplateURLService* urlService);
+  ~SearchEngineObserver() override;
+  void OnTemplateURLServiceChanged() override;
+
+ private:
+  base::WeakNSObject<GoogleLandingMediator> _owner;
+  TemplateURLService* _templateURLService;  // weak
+};
+
+SearchEngineObserver::SearchEngineObserver(GoogleLandingMediator* owner,
+                                           TemplateURLService* urlService)
+    : _owner(owner), _templateURLService(urlService) {
+  _templateURLService->AddObserver(this);
+}
+
+SearchEngineObserver::~SearchEngineObserver() {
+  _templateURLService->RemoveObserver(this);
+}
+
+void SearchEngineObserver::OnTemplateURLServiceChanged() {
+  [_owner updateShowLogo];
+}
+
+}  // namespace google_landing
+
+@interface GoogleLandingMediator ()<MostVisitedSitesObserving> {
+  // The ChromeBrowserState associated with this mediator.
+  ios::ChromeBrowserState* _browserState;  // Weak.
+
+  // |YES| if impressions were logged already and shouldn't be logged again.
+  BOOL _recordedPageImpression;
+
+  // The designated url loader.
+  id<UrlLoader> _loader;  // Weak.
+
+  // Delegate to focus and blur the omnibox.
+  base::WeakNSProtocol<id<OmniboxFocuser>> _focuser;
+
+  // Controller to fetch and show doodles or a default Google logo.
+  base::scoped_nsprotocol<id<LogoVendor>> _doodleController;
+
+  // Listen for default search engine changes.
+  std::unique_ptr<google_landing::SearchEngineObserver> _observer;
+  TemplateURLService* _templateURLService;  // weak
+
+  // A MostVisitedSites::Observer bridge object to get notified of most visited
+  // sites changes.
+  std::unique_ptr<ntp_tiles::MostVisitedSitesObserverBridge>
+      _mostVisitedObserverBridge;
+
+  std::unique_ptr<ntp_tiles::MostVisitedSites> _mostVisitedSites;
+
+  // Most visited data from the MostVisitedSites service (copied upon receiving
+  // the callback).
+  ntp_tiles::NTPTilesVector _mostVisitedData;
+
+  base::WeakNSProtocol<id<WebToolbarDelegate>> _webToolbarDelegate;
+
+  base::scoped_nsobject<TabModel> _tabModel;
+
+  // What's new promo.
+  std::unique_ptr<NotificationPromoWhatsNew> _notification_promo;
+}
+
+// Consumer to handle google landing update notifications.
+@property(nonatomic) id<GoogleLandingConsumer> consumer;
+
+// Perform initial setup.
+- (void)setUp;
+
+@end
+
+@implementation GoogleLandingMediator
+
+@synthesize consumer = _consumer;
+
+- (instancetype)initWithConsumer:(id<GoogleLandingConsumer>)consumer
+                    browserState:(ios::ChromeBrowserState*)browserState
+                          loader:(id<UrlLoader>)loader
+                         focuser:(id<OmniboxFocuser>)focuser
+              webToolbarDelegate:(id<WebToolbarDelegate>)webToolbarDelegate
+                        tabModel:(TabModel*)tabModel {
+  self = [super init];
+  if (self) {
+    _consumer = consumer;
+    _browserState = browserState;
+    _loader = loader;
+    _focuser.reset(focuser);
+    _webToolbarDelegate.reset(webToolbarDelegate);
+    _tabModel.reset([tabModel retain]);
+    [self setUp];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  [[NSNotificationCenter defaultCenter] removeObserver:self.consumer];
+  [super dealloc];
+}
+
+- (void)setUp {
+  [_consumer setIsOffTheRecord:_browserState->IsOffTheRecord()];
+  [_consumer setVoiceSearchIsEnabled:ios::GetChromeBrowserProvider()
+                                         ->GetVoiceSearchProvider()
+                                         ->IsVoiceSearchEnabled()];
+  [_consumer
+      setMaximumMostVisitedSitesShown:[GoogleLandingMediator maxSitesShown]];
+
+  // Set up template URL service to listen for default search engine changes.
+  _templateURLService =
+      ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
+  _observer.reset(
+      new google_landing::SearchEngineObserver(self, _templateURLService));
+  _templateURLService->Load();
+  _doodleController.reset(ios::GetChromeBrowserProvider()->CreateLogoVendor(
+      _browserState, _loader));
+  [_consumer setLogoVendor:_doodleController];
+  [self updateShowLogo];
+
+  // Set up most visited sites.  This call may have the side effect of
+  // triggering -onMostVisitedURLsAvailable immediately, which can load the
+  // view before dataSource is set.
+  _mostVisitedSites =
+      IOSMostVisitedSitesFactory::NewForBrowserState(_browserState);
+  _mostVisitedObserverBridge.reset(
+      new ntp_tiles::MostVisitedSitesObserverBridge(self));
+  _mostVisitedSites->SetMostVisitedURLsObserver(
+      _mostVisitedObserverBridge.get(), [GoogleLandingMediator maxSitesShown]);
+
+  // Set up notifications;
+  NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
+  [defaultCenter
+      addObserver:_consumer
+         selector:@selector(locationBarBecomesFirstResponder)
+             name:ios_internal::kLocationBarBecomesFirstResponderNotification
+           object:nil];
+  [defaultCenter
+      addObserver:_consumer
+         selector:@selector(locationBarResignsFirstResponder)
+             name:ios_internal::kLocationBarResignsFirstResponderNotification
+           object:nil];
+
+  // Set up what's new.
+  _notification_promo.reset(
+      new NotificationPromoWhatsNew(GetApplicationContext()->GetLocalState()));
+  _notification_promo->Init();
+  [_consumer setPromoText:[base::SysUTF8ToNSString(
+                              _notification_promo->promo_text()) copy]];
+  [_consumer setPromoIcon:_notification_promo->icon()];
+  [_consumer setPromoCanShow:_notification_promo->CanShow()];
+}
+
+- (void)updateShowLogo {
+  BOOL showLogo = NO;
+  TemplateURL* defaultURL = _templateURLService->GetDefaultSearchProvider();
+  if (defaultURL) {
+    showLogo =
+        defaultURL->GetEngineType(_templateURLService->search_terms_data()) ==
+        SEARCH_ENGINE_GOOGLE;
+  }
+  [self.consumer setLogoIsShowing:showLogo];
+}
+
++ (NSUInteger)maxSitesShown {
+  return kMaxNumMostVisitedFavicons;
+}
+
+#pragma mark - MostVisitedSitesObserving
+
+- (void)onMostVisitedURLsAvailable:(const ntp_tiles::NTPTilesVector&)data {
+  _mostVisitedData = data;
+  [self.consumer mostVisitedDataUpdated];
+
+  if (data.size() && !_recordedPageImpression) {
+    _recordedPageImpression = YES;
+    std::vector<ntp_tiles::metrics::TileImpression> tiles;
+    for (const ntp_tiles::NTPTile& ntpTile : data) {
+      tiles.emplace_back(ntpTile.source, ntp_tiles::UNKNOWN_TILE_TYPE,
+                         ntpTile.url);
+    }
+    ntp_tiles::metrics::RecordPageImpression(
+        tiles, GetApplicationContext()->GetRapporServiceImpl());
+  }
+}
+
+- (void)onIconMadeAvailable:(const GURL&)siteUrl {
+  for (size_t i = 0; i < _mostVisitedData.size(); ++i) {
+    const ntp_tiles::NTPTile& ntpTile = _mostVisitedData[i];
+    if (ntpTile.url == siteUrl) {
+      [self.consumer mostVisitedIconMadeAvailableAtIndex:i];
+      break;
+    }
+  }
+}
+
+#pragma mark - GoogleLandingDataSource
+
+- (void)addBlacklistedURL:(const GURL&)url {
+  _mostVisitedSites->AddOrRemoveBlacklistedUrl(url, true);
+}
+
+- (void)removeBlacklistedURL:(const GURL&)url {
+  _mostVisitedSites->AddOrRemoveBlacklistedUrl(url, false);
+}
+
+- (ntp_tiles::NTPTile)mostVisitedAtIndex:(NSUInteger)index {
+  return _mostVisitedData[index];
+}
+
+- (NSUInteger)mostVisitedSize {
+  return _mostVisitedData.size();
+}
+
+- (void)logMostVisitedClick:(const NSUInteger)visitedIndex
+                   tileType:(ntp_tiles::TileVisualType)tileType {
+  new_tab_page_uma::RecordAction(
+      _browserState, new_tab_page_uma::ACTION_OPENED_MOST_VISITED_ENTRY);
+  base::RecordAction(UserMetricsAction("MobileNTPMostVisited"));
+  const ntp_tiles::NTPTile& tile = _mostVisitedData[visitedIndex];
+  ntp_tiles::metrics::RecordTileClick(visitedIndex, tile.source, tileType);
+}
+
+- (ReadingListModel*)readingListModel {
+  return ReadingListModelFactory::GetForBrowserState(_browserState);
+}
+
+- (LargeIconCache*)largeIconCache {
+  return IOSChromeLargeIconCacheFactory::GetForBrowserState(_browserState);
+}
+
+- (favicon::LargeIconService*)largeIconService {
+  return IOSChromeLargeIconServiceFactory::GetForBrowserState(_browserState);
+}
+
+- (id<WebToolbarDelegate>)toolbarDelegate {
+  return _webToolbarDelegate;
+}
+
+- (TabModel*)tabModel {
+  return _tabModel;
+}
+
+- (void)promoViewed {
+  DCHECK(_notification_promo);
+  _notification_promo->HandleViewed();
+  [self.consumer setPromoCanShow:_notification_promo->CanShow()];
+}
+
+- (void)promoTapped {
+  DCHECK(_notification_promo);
+  _notification_promo->HandleClosed();
+  [self.consumer setPromoCanShow:_notification_promo->CanShow()];
+
+  if (_notification_promo->IsURLPromo()) {
+    [_loader webPageOrderedOpen:_notification_promo->url()
+                       referrer:web::Referrer()
+                   inBackground:NO
+                       appendTo:kCurrentTab];
+    return;
+  }
+
+  if (_notification_promo->IsChromeCommand()) {
+    base::scoped_nsobject<GenericChromeCommand> command(
+        [[GenericChromeCommand alloc]
+            initWithTag:_notification_promo->command_id()]);
+    [self.consumer chromeExecuteCommand:command];
+    return;
+  }
+  NOTREACHED();
+}
+
+#pragma mark - UrlLoader
+
+- (void)loadURL:(const GURL&)url
+             referrer:(const web::Referrer&)referrer
+           transition:(ui::PageTransition)transition
+    rendererInitiated:(BOOL)rendererInitiated {
+  [_loader loadURL:url
+               referrer:referrer
+             transition:transition
+      rendererInitiated:rendererInitiated];
+}
+
+- (void)webPageOrderedOpen:(const GURL&)url
+                  referrer:(const web::Referrer&)referrer
+              inBackground:(BOOL)inBackground
+                  appendTo:(OpenPosition)appendTo {
+  [_loader webPageOrderedOpen:url
+                     referrer:referrer
+                 inBackground:inBackground
+                     appendTo:appendTo];
+}
+
+- (void)webPageOrderedOpen:(const GURL&)url
+                  referrer:(const web::Referrer&)referrer
+               inIncognito:(BOOL)inIncognito
+              inBackground:(BOOL)inBackground
+                  appendTo:(OpenPosition)appendTo {
+  [_loader webPageOrderedOpen:url
+                     referrer:referrer
+                  inIncognito:inIncognito
+                 inBackground:inBackground
+                     appendTo:appendTo];
+}
+
+- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
+  NOTREACHED();
+}
+
+- (void)loadJavaScriptFromLocationBar:(NSString*)script {
+  NOTREACHED();
+}
+
+#pragma mark - OmniboxFocuser
+
+- (void)focusOmnibox {
+  [_focuser focusOmnibox];
+}
+
+- (void)cancelOmniboxEdit {
+  [_focuser cancelOmniboxEdit];
+}
+
+- (void)focusFakebox {
+  [_focuser focusFakebox];
+}
+
+- (void)onFakeboxBlur {
+  [_focuser onFakeboxBlur];
+}
+
+- (void)onFakeboxAnimationComplete {
+  [_focuser onFakeboxAnimationComplete];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/ntp/most_visited_cell.h b/ios/chrome/browser/ui/ntp/most_visited_cell.h
index 691245a..873674f 100644
--- a/ios/chrome/browser/ui/ntp/most_visited_cell.h
+++ b/ios/chrome/browser/ui/ntp/most_visited_cell.h
@@ -10,17 +10,13 @@
 #include "components/ntp_tiles/metrics.h"
 #include "url/gurl.h"
 
-namespace ios {
-class ChromeBrowserState;
-}
+@protocol GoogleLandingDataSource;
 
 // Cell showing each most visited image, favicon and title.
 @interface MostVisitedCell : UICollectionViewCell
 
 // URL of the top site.
 @property(nonatomic, assign) GURL URL;
-// Reference to the relevant ChromeBrowserState
-@property(nonatomic, readonly) ios::ChromeBrowserState* browserState;
 // Type of tile (icon, scrabble tile, default)
 @property(nonatomic, readonly) ntp_tiles::TileVisualType tileType;
 
@@ -34,7 +30,7 @@
 // Setup the display state of the cell.
 - (void)setupWithURL:(GURL)URL
                title:(NSString*)title
-        browserState:(ios::ChromeBrowserState*)browserState;
+          dataSource:(id<GoogleLandingDataSource>)dataSource;
 
 // Preferred maximum cell size.
 + (CGSize)maximumSize;
diff --git a/ios/chrome/browser/ui/ntp/most_visited_cell.mm b/ios/chrome/browser/ui/ntp/most_visited_cell.mm
index df2b223..c4bde11 100644
--- a/ios/chrome/browser/ui/ntp/most_visited_cell.mm
+++ b/ios/chrome/browser/ui/ntp/most_visited_cell.mm
@@ -20,12 +20,10 @@
 #include "components/suggestions/suggestions_service.h"
 #import "ios/chrome/browser/favicon/favicon_loader.h"
 #include "ios/chrome/browser/favicon/favicon_service_factory.h"
-#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
-#include "ios/chrome/browser/favicon/ios_chrome_large_icon_cache_factory.h"
-#include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
 #include "ios/chrome/browser/favicon/large_icon_cache.h"
 #include "ios/chrome/browser/history/top_sites_factory.h"
 #include "ios/chrome/browser/suggestions/suggestions_service_factory.h"
+#import "ios/chrome/browser/ui/ntp/google_landing_data_source.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
 #import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h"
 #import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
@@ -43,8 +41,8 @@
 @interface MostVisitedCell () {
   // Backs property with the same name.
   GURL _URL;
-  // Weak reference to the relevant BrowserState.
-  ios::ChromeBrowserState* _browserState;
+  // Weak reference to the relevant GoogleLandingDataSource.
+  base::WeakNSProtocol<id<GoogleLandingDataSource>> _dataSource;
   // Backs property with the same name.
   ntp_tiles::TileVisualType _tileType;
 
@@ -66,7 +64,6 @@
 @implementation MostVisitedCell
 
 @synthesize URL = _URL;
-@synthesize browserState = _browserState;
 @synthesize tileType = _tileType;
 
 - (instancetype)initWithFrame:(CGRect)frame {
@@ -148,8 +145,8 @@
 
 - (void)setupWithURL:(GURL)URL
                title:(NSString*)title
-        browserState:(ios::ChromeBrowserState*)browserState {
-  _browserState = browserState;
+          dataSource:(id<GoogleLandingDataSource>)dataSource {
+  _dataSource.reset(dataSource);
   _tileType = ntp_tiles::TileVisualType::NONE;
   [self setText:title];
   [self setURL:URL];
@@ -183,14 +180,14 @@
         }
 
         if (result.bitmap.is_valid() || result.fallback_icon_style) {
-          IOSChromeLargeIconCacheFactory::GetForBrowserState(
-              [strongSelf browserState])
-              ->SetCachedResult(URL, result);
+          LargeIconCache* largeIconCache =
+              [strongSelf.get()->_dataSource largeIconCache];
+          if (largeIconCache)
+            largeIconCache->SetCachedResult(URL, result);
         }
       };
 
-  LargeIconCache* cache =
-      IOSChromeLargeIconCacheFactory::GetForBrowserState(self.browserState);
+  LargeIconCache* cache = [_dataSource largeIconCache];
   std::unique_ptr<favicon_base::LargeIconResult> cached_result =
       cache->GetCachedResult(URL);
   if (cached_result) {
@@ -199,7 +196,7 @@
 
   // Always call LargeIconService in case the favicon was updated.
   favicon::LargeIconService* large_icon_service =
-      IOSChromeLargeIconServiceFactory::GetForBrowserState(self.browserState);
+      [_dataSource largeIconService];
   CGFloat faviconSize = [UIScreen mainScreen].scale * kFaviconSize;
   CGFloat faviconMinSize = [UIScreen mainScreen].scale * kFaviconMinSize;
   large_icon_service->GetLargeIconOrFallbackStyle(
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm b/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
index 5b3f1c7..33f546cd 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
@@ -27,6 +27,7 @@
 #import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
 #include "ios/chrome/browser/ui/commands/ios_command_ids.h"
 #import "ios/chrome/browser/ui/ntp/google_landing_controller.h"
+#import "ios/chrome/browser/ui/ntp/google_landing_mediator.h"
 #import "ios/chrome/browser/ui/ntp/incognito_panel_controller.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_bar_item.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_view.h"
@@ -114,6 +115,8 @@
 
   NewTabPageView* newTabPageView_;
 
+  base::scoped_nsobject<GoogleLandingMediator> googleLandingMediator_;
+
   base::scoped_nsobject<RecentTabsPanelController> openTabsController_;
   // Has the scrollView been initialized.
   BOOL scrollInitialized_;
@@ -533,12 +536,15 @@
     [bookmarkController_ setDelegate:self];
   } else if (item.identifier == NewTabPage::kMostVisitedPanel) {
     if (!googleLandingController_) {
-      googleLandingController_.reset([[GoogleLandingController alloc]
-              initWithLoader:loader_
+      googleLandingController_.reset([[GoogleLandingController alloc] init]);
+      googleLandingMediator_.reset([[GoogleLandingMediator alloc]
+            initWithConsumer:googleLandingController_
                 browserState:browserState_
+                      loader:loader_
                      focuser:focuser_
           webToolbarDelegate:webToolbarDelegate_
                     tabModel:tabModel_]);
+      [googleLandingController_ setDataSource:googleLandingMediator_];
     }
     panelController = googleLandingController_;
     view = [googleLandingController_ view];
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_header_view.h b/ios/chrome/browser/ui/ntp/new_tab_page_header_view.h
index edc4dba..b7a2fd7 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_header_view.h
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_header_view.h
@@ -9,11 +9,7 @@
 
 #import "ios/chrome/browser/ui/toolbar/toolbar_owner.h"
 
-@protocol OmniboxFocuser;
-@class TabModel;
-@protocol WebToolbarDelegate;
-
-class ReadingListModel;
+@protocol GoogleLandingDataSource;
 
 // Header view for the Material Design NTP. The header view contains all views
 // that are displayed above the list of most visited sites, which includes the
@@ -25,10 +21,7 @@
 
 // Creates a NewTabPageToolbarController using the given |toolbarDelegate|,
 // |focuser| and |readingListModel|, and adds the toolbar view to self.
-- (void)addToolbarWithDelegate:(id<WebToolbarDelegate>)toolbarDelegate
-                       focuser:(id<OmniboxFocuser>)focuser
-                      tabModel:(TabModel*)tabModel
-              readingListModel:(ReadingListModel*)readingListModel;
+- (void)addToolbarWithDataSource:(id<GoogleLandingDataSource>)dataSource;
 
 // Changes the frame of |searchField| based on its |initialFrame| and the scroll
 // view's y |offset|. Also adjust the alpha values for |_searchBoxBorder| and
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_header_view.mm b/ios/chrome/browser/ui/ntp/new_tab_page_header_view.mm
index 5364fe2..4659015 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_header_view.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_header_view.mm
@@ -9,6 +9,7 @@
 #import "ios/chrome/browser/tabs/tab_model.h"
 #import "ios/chrome/browser/tabs/tab_model_observer.h"
 #import "ios/chrome/browser/ui/image_util.h"
+#import "ios/chrome/browser/ui/ntp/google_landing_data_source.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_header_constants.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_toolbar_controller.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
@@ -65,19 +66,16 @@
   [self addSubview:[_toolbarController view]];
 }
 
-- (void)addToolbarWithDelegate:(id<WebToolbarDelegate>)toolbarDelegate
-                       focuser:(id<OmniboxFocuser>)focuser
-                      tabModel:(TabModel*)tabModel
-              readingListModel:(ReadingListModel*)readingListModel {
+- (void)addToolbarWithDataSource:(id<GoogleLandingDataSource>)dataSource {
   DCHECK(!_toolbarController);
-  DCHECK(focuser);
+  DCHECK(dataSource);
 
   _toolbarController.reset([[NewTabPageToolbarController alloc]
-      initWithToolbarDelegate:toolbarDelegate
-                      focuser:focuser]);
-  _toolbarController.get().readingListModel = readingListModel;
+      initWithToolbarDelegate:[dataSource toolbarDelegate]
+                      focuser:dataSource]);
+  _toolbarController.get().readingListModel = [dataSource readingListModel];
   [_tabModel removeObserver:self];
-  _tabModel.reset([tabModel retain]);
+  _tabModel.reset([[dataSource tabModel] retain]);
   [self addTabModelObserver];
 
   UIView* toolbarView = [_toolbarController view];
diff --git a/ios/clean/chrome/browser/ui/ntp/ntp_home_coordinator.mm b/ios/clean/chrome/browser/ui/ntp/ntp_home_coordinator.mm
index b5175c90..352000bf 100644
--- a/ios/clean/chrome/browser/ui/ntp/ntp_home_coordinator.mm
+++ b/ios/clean/chrome/browser/ui/ntp/ntp_home_coordinator.mm
@@ -5,6 +5,7 @@
 #import "ios/clean/chrome/browser/ui/ntp/ntp_home_coordinator.h"
 
 #import "ios/chrome/browser/ui/ntp/google_landing_controller.h"
+#import "ios/chrome/browser/ui/ntp/google_landing_mediator.h"
 #import "ios/clean/chrome/browser/ui/ntp/ntp_home_mediator.h"
 #import "ios/shared/chrome/browser/ui/browser_list/browser.h"
 #import "ios/shared/chrome/browser/ui/coordinators/browser_coordinator+internal.h"
@@ -15,23 +16,28 @@
 
 @interface NTPHomeCoordinator ()
 @property(nonatomic, strong) NTPHomeMediator* mediator;
+@property(nonatomic, strong) GoogleLandingMediator* googleLandingMediator;
 @property(nonatomic, strong) GoogleLandingController* viewController;
 @end
 
 @implementation NTPHomeCoordinator
 @synthesize mediator = _mediator;
+@synthesize googleLandingMediator = _googleLandingMediator;
 @synthesize viewController = _viewController;
 
 - (void)start {
-  // PLACEHOLDER: Re-using old view controllers for now.
+  // PLACEHOLDER: self.mediator and self.oldMediator should be merged together.
   self.mediator = [[NTPHomeMediator alloc] init];
   self.mediator.dispatcher = static_cast<id>(self.browser->dispatcher());
-  self.viewController = [[GoogleLandingController alloc]
-          initWithLoader:self.mediator
+  self.viewController = [[GoogleLandingController alloc] init];
+  self.googleLandingMediator = [[GoogleLandingMediator alloc]
+        initWithConsumer:self.viewController
             browserState:self.browser->browser_state()
+                  loader:self.mediator
                  focuser:self.mediator
       webToolbarDelegate:nil
                 tabModel:nil];
+  self.viewController.dataSource = self.googleLandingMediator;
   [super start];
 }
 
diff --git a/media/formats/mpeg/mpeg_audio_stream_parser_base.cc b/media/formats/mpeg/mpeg_audio_stream_parser_base.cc
index 4c377e6..940d0b62 100644
--- a/media/formats/mpeg/mpeg_audio_stream_parser_base.cc
+++ b/media/formats/mpeg/mpeg_audio_stream_parser_base.cc
@@ -54,6 +54,7 @@
                                                      AudioCodec audio_codec,
                                                      int codec_delay)
     : state_(UNINITIALIZED),
+      media_log_(nullptr),
       in_media_segment_(false),
       start_code_mask_(start_code_mask),
       audio_codec_(audio_codec),
diff --git a/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom b/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom
index adc4e7e..3858330a 100644
--- a/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom
+++ b/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom
@@ -45,6 +45,11 @@
   AsyncGetSender() => (associated IntegerSender sender);
 };
 
+interface IntegerSenderConnectionAtBothEnds {
+  GetSender(associated IntegerSender& sender);
+  SetSender(associated IntegerSender sender) => (int32 value);
+};
+
 interface AssociatedPingProvider {
   GetPing(associated PingService& request);
 };
diff --git a/mojo/public/js/associated_bindings.js b/mojo/public/js/associated_bindings.js
index fdfe3c2..18ac452 100644
--- a/mojo/public/js/associated_bindings.js
+++ b/mojo/public/js/associated_bindings.js
@@ -9,9 +9,233 @@
   "mojo/public/js/lib/interface_endpoint_handle",
 ], function(core, types, interfaceEndpointClient, interfaceEndpointHandle) {
 
+  var InterfaceEndpointClient = interfaceEndpointClient.InterfaceEndpointClient;
+
+  // ---------------------------------------------------------------------------
+
+  function makeRequest(associatedInterfacePtrInfo) {
+    var {handle0, handle1} =
+        interfaceEndpointHandle.createPairPendingAssociation();
+
+    associatedInterfacePtrInfo.interfaceEndpointHandle = handle0;
+    associatedInterfacePtrInfo.version = 0;
+
+    var request = new types.AssociatedInterfaceRequest(handle1);
+    return request;
+  }
+
+  // ---------------------------------------------------------------------------
+
+  // Operations used to setup/configure an associated interface pointer.
+  // Exposed as |ptr| field of generated associated interface pointer classes.
+  // |associatedPtrInfo| could be omitted and passed into bind() later.
+  //
+  // Example:
+  //    // IntegerSenderImpl implements mojom.IntegerSender
+  //    function IntegerSenderImpl() { ... }
+  //    IntegerSenderImpl.prototype.echo = function() { ... }
+  //
+  //    // IntegerSenderConnectionImpl implements mojom.IntegerSenderConnection
+  //    function IntegerSenderConnectionImpl() {
+  //      this.senderBinding_ = null;
+  //    }
+  //    IntegerSenderConnectionImpl.prototype.getSender = function(
+  //        associatedRequest) {
+  //      this.senderBinding_ = new AssociatedBinding(mojom.IntegerSender,
+  //          new IntegerSenderImpl(),
+  //          associatedRequest);
+  //    }
+  //
+  //    var integerSenderConnection = new mojom.IntegerSenderConnectionPtr();
+  //    var integerSenderConnectionBinding = new Binding(
+  //        mojom.IntegerSenderConnection,
+  //        new IntegerSenderConnectionImpl(),
+  //        bindings.makeRequest(integerSenderConnection));
+  //
+  //    // A locally-created associated interface pointer can only be used to
+  //    // make calls when the corresponding associated request is sent over
+  //    // another interface (either the master interface or another
+  //    // associated interface).
+  //    var associatedInterfacePtrInfo = new AssociatedInterfacePtrInfo();
+  //    var associatedRequest = makeRequest(interfacePtrInfo);
+  //
+  //    integerSenderConnection.getSender(associatedRequest);
+  //
+  //    // Create an associated interface and bind the associated handle.
+  //    var integerSender = new mojom.AssociatedIntegerSenderPtr();
+  //    integerSender.ptr.bind(associatedInterfacePtrInfo);
+  //    integerSender.echo();
+
+  function AssociatedInterfacePtrController(interfaceType, associatedPtrInfo) {
+    this.version = 0;
+
+    this.interfaceType_ = interfaceType;
+    this.interfaceEndpointClient_ = null;
+    this.proxy_ = null;
+
+    if (associatedPtrInfo) {
+      this.bind(associatedPtrInfo);
+    }
+  }
+
+  AssociatedInterfacePtrController.prototype.bind = function(
+      associatedPtrInfo) {
+    this.reset();
+    this.version = associatedPtrInfo.version;
+
+    this.interfaceEndpointClient_ = new InterfaceEndpointClient(
+        associatedPtrInfo.interfaceEndpointHandle);
+
+    this.interfaceEndpointClient_ .setPayloadValidators([
+        this.interfaceType_.validateResponse]);
+    this.proxy_ = new this.interfaceType_.proxyClass(
+        this.interfaceEndpointClient_);
+  };
+
+  AssociatedInterfacePtrController.prototype.isBound = function() {
+    return this.interfaceEndpointClient_ !== null;
+  };
+
+  AssociatedInterfacePtrController.prototype.reset = function() {
+    this.version = 0;
+    if (this.interfaceEndpointClient_) {
+      this.interfaceEndpointClient_.close();
+      this.interfaceEndpointClient_ = null;
+    }
+    if (this.proxy_) {
+      this.proxy_ = null;
+    }
+  };
+
+  AssociatedInterfacePtrController.prototype.resetWithReason = function(
+      reason) {
+    if (this.isBound()) {
+      this.interfaceEndpointClient_.close(reason);
+      this.interfaceEndpointClient_ = null;
+    }
+    this.reset();
+  };
+
+  AssociatedInterfacePtrController.prototype.setConnectionErrorHandler =
+      function(callback) {
+    if (!this.isBound()) {
+      throw new Error("Cannot set connection error handler if not bound.");
+    }
+
+    this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
+  };
+
+  AssociatedInterfacePtrController.prototype.passInterface = function() {
+    if (!this.isBound()) {
+      return new types.AssociatedInterfacePtrInfo(null);
+    }
+
+    var result = new types.AssociatedInterfacePtrInfo(
+        this.interfaceEndpointClient_.passHandle(), this.version);
+    this.reset();
+    return result;
+  };
+
+  AssociatedInterfacePtrController.prototype.getProxy = function() {
+    return this.proxy_;
+  };
+
+  AssociatedInterfacePtrController.prototype.queryVersion = function() {
+    function onQueryVersion(version) {
+      this.version = version;
+      return version;
+    }
+
+    return this.interfaceEndpointClient_.queryVersion().then(
+      onQueryVersion.bind(this));
+  };
+
+  AssociatedInterfacePtrController.prototype.requireVersion = function(
+      version) {
+    if (this.version >= version) {
+      return;
+    }
+    this.version = version;
+    this.interfaceEndpointClient_.requireVersion(version);
+  };
+
+  // ---------------------------------------------------------------------------
+
+  // |associatedInterfaceRequest| could be omitted and passed into bind()
+  // later.
+  function AssociatedBinding(interfaceType, impl, associatedInterfaceRequest) {
+    this.interfaceType_ = interfaceType;
+    this.impl_ = impl;
+    this.interfaceEndpointClient_ = null;
+    this.stub_ = null;
+
+    if (associatedInterfaceRequest) {
+      this.bind(associatedInterfaceRequest);
+    }
+  }
+
+  AssociatedBinding.prototype.isBound = function() {
+    return this.interfaceEndpointClient_ !== null;
+  };
+
+  AssociatedBinding.prototype.bind = function(associatedInterfaceRequest) {
+    this.close();
+
+    this.stub_ = new this.interfaceType_.stubClass(this.impl_);
+    this.interfaceEndpointClient_ = new InterfaceEndpointClient(
+        associatedInterfaceRequest.interfaceEndpointHandle, this.stub_,
+        this.interfaceType_.kVersion);
+
+    this.interfaceEndpointClient_ .setPayloadValidators([
+        this.interfaceType_.validateRequest]);
+  };
+
+
+  AssociatedBinding.prototype.close = function() {
+    if (!this.isBound()) {
+      return;
+    }
+
+    if (this.interfaceEndpointClient_) {
+      this.interfaceEndpointClient_.close();
+      this.interfaceEndpointClient_ = null;
+    }
+
+    this.stub_ = null;
+  };
+
+  AssociatedBinding.prototype.closeWithReason = function(reason) {
+    if (this.interfaceEndpointClient_) {
+      this.interfaceEndpointClient_.close(reason);
+      this.interfaceEndpointClient_ = null;
+    }
+    this.close();
+  };
+
+  AssociatedBinding.prototype.setConnectionErrorHandler = function(callback) {
+    if (!this.isBound()) {
+      throw new Error("Cannot set connection error handler if not bound.");
+    }
+    this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
+  };
+
+  AssociatedBinding.prototype.unbind = function() {
+    if (!this.isBound()) {
+      return new types.AssociatedInterfaceRequest(null);
+    }
+
+    var result = new types.AssociatedInterfaceRequest(
+        this.interfaceEndpointClient_.passHandle());
+    this.close();
+    return result;
+  };
+
   var exports = {};
   exports.AssociatedInterfacePtrInfo = types.AssociatedInterfacePtrInfo;
   exports.AssociatedInterfaceRequest = types.AssociatedInterfaceRequest;
+  exports.makeRequest = makeRequest;
+  exports.AssociatedInterfacePtrController = AssociatedInterfacePtrController;
+  exports.AssociatedBinding = AssociatedBinding;
 
   return exports;
 });
diff --git a/mojo/public/js/bindings.js b/mojo/public/js/bindings.js
index a944e2f7..ed00554 100644
--- a/mojo/public/js/bindings.js
+++ b/mojo/public/js/bindings.js
@@ -53,7 +53,7 @@
   };
 
   InterfacePtrController.prototype.isBound = function() {
-    return this.router_ !== null || this.handle_ !== null;
+    return this.interfaceEndpointClient_ !== null || this.handle_ !== null;
   };
 
   // Although users could just discard the object, reset() closes the pipe
@@ -77,9 +77,11 @@
   };
 
   InterfacePtrController.prototype.resetWithReason = function(reason) {
-    this.configureProxyIfNecessary_();
-    this.interfaceEndpointClient_.close(reason);
-    this.interfaceEndpointClient_ = null;
+    if (this.isBound()) {
+      this.configureProxyIfNecessary_();
+      this.interfaceEndpointClient_.close(reason);
+      this.interfaceEndpointClient_ = null;
+    }
     this.reset();
   };
 
@@ -123,12 +125,11 @@
     if (!this.handle_)
       return;
 
-    this.router_ = new router.Router(this.handle_);
+    this.router_ = new router.Router(this.handle_, true);
     this.handle_ = null;
 
     this.interfaceEndpointClient_ = new InterfaceEndpointClient(
-        this.router_.createLocalEndpointHandle(types.kMasterInterfaceId),
-        this.router_);
+        this.router_.createLocalEndpointHandle(types.kMasterInterfaceId));
 
     this.interfaceEndpointClient_ .setPayloadValidators([
         this.interfaceType_.validateResponse]);
@@ -207,8 +208,8 @@
     this.stub_ = new this.interfaceType_.stubClass(this.impl_);
     this.interfaceEndpointClient_ = new InterfaceEndpointClient(
         this.router_.createLocalEndpointHandle(types.kMasterInterfaceId),
-        this.router_, this.interfaceType_.kVersion);
-    this.interfaceEndpointClient_.setIncomingReceiver(this.stub_);
+        this.stub_, this.interfaceType_.kVersion);
+
     this.interfaceEndpointClient_ .setPayloadValidators([
         this.interfaceType_.validateRequest]);
   };
@@ -235,8 +236,7 @@
     this.close();
   };
 
-  Binding.prototype.setConnectionErrorHandler
-      = function(callback) {
+  Binding.prototype.setConnectionErrorHandler = function(callback) {
     if (!this.isBound()) {
       throw new Error("Cannot set connection error handler if not bound.");
     }
diff --git a/mojo/public/js/codec.js b/mojo/public/js/codec.js
index b78aac2..a57e94f0 100644
--- a/mojo/public/js/codec.js
+++ b/mojo/public/js/codec.js
@@ -43,9 +43,10 @@
 
   // Decoder ------------------------------------------------------------------
 
-  function Decoder(buffer, handles, base) {
+  function Decoder(buffer, handles, associatedEndpointHandles, base) {
     this.buffer = buffer;
     this.handles = handles;
+    this.associatedEndpointHandles = associatedEndpointHandles;
     this.base = base;
     this.next = base;
   }
@@ -129,13 +130,18 @@
   };
 
   Decoder.prototype.decodeAndCreateDecoder = function(pointer) {
-    return new Decoder(this.buffer, this.handles, pointer);
+    return new Decoder(this.buffer, this.handles,
+        this.associatedEndpointHandles, pointer);
   };
 
   Decoder.prototype.decodeHandle = function() {
     return this.handles[this.readUint32()] || null;
   };
 
+  Decoder.prototype.decodeAssociatedEndpointHandle = function() {
+    return this.associatedEndpointHandles[this.readUint32()] || null;
+  };
+
   Decoder.prototype.decodeString = function() {
     var numberOfBytes = this.readUint32();
     var numberOfElements = this.readUint32();
@@ -214,9 +220,10 @@
 
   // Encoder ------------------------------------------------------------------
 
-  function Encoder(buffer, handles, base) {
+  function Encoder(buffer, handles, associatedEndpointHandles, base) {
     this.buffer = buffer;
     this.handles = handles;
+    this.associatedEndpointHandles = associatedEndpointHandles;
     this.base = base;
     this.next = base;
   }
@@ -303,7 +310,8 @@
   Encoder.prototype.createAndEncodeEncoder = function(size) {
     var pointer = this.buffer.alloc(align(size));
     this.encodePointer(pointer);
-    return new Encoder(this.buffer, this.handles, pointer);
+    return new Encoder(this.buffer, this.handles,
+        this.associatedEndpointHandles, pointer);
   };
 
   Encoder.prototype.encodeHandle = function(handle) {
@@ -315,6 +323,15 @@
     }
   };
 
+  Encoder.prototype.encodeAssociatedEndpointHandle = function(endpointHandle) {
+    if (endpointHandle) {
+      this.associatedEndpointHandles.push(endpointHandle);
+      this.writeUint32(this.associatedEndpointHandles.length - 1);
+    } else {
+      this.writeUint32(kEncodedInvalidHandleValue);
+    }
+  };
+
   Encoder.prototype.encodeString = function(val) {
     var base = this.next + kArrayHeaderSize;
     var numberOfElements = unicode.encodeUtf8String(
@@ -436,9 +453,14 @@
   var kMessageExpectsResponse = 1 << 0;
   var kMessageIsResponse      = 1 << 1;
 
-  function Message(buffer, handles) {
+  function Message(buffer, handles, associatedEndpointHandles) {
+    if (associatedEndpointHandles === undefined) {
+      associatedEndpointHandles = [];
+    }
+
     this.buffer = buffer;
     this.handles = handles;
+    this.associatedEndpointHandles = associatedEndpointHandles;
   }
 
   Message.prototype.getHeaderNumBytes = function() {
@@ -467,6 +489,7 @@
     }
 
     var decoder = new Decoder(this.buffer, this.handles,
+        this.associatedEndpointHandles,
         kMessagePayloadInterfaceIdsPointerOffset);
     var payloadInterfaceIds = decoder.decodeArrayPointer(Uint32);
     return payloadInterfaceIds;
@@ -489,10 +512,70 @@
     this.buffer.setUint32(kMessageInterfaceIdOffset, interfaceId);
   };
 
+  Message.prototype.setPayloadInterfaceIds_ = function(payloadInterfaceIds) {
+    if (this.getHeaderVersion() < 2) {
+      throw new Error(
+          "Version of message does not support payload interface ids");
+    }
 
-  // MessageBuilder -----------------------------------------------------------
+    var decoder = new Decoder(this.buffer, this.handles,
+        this.associatedEndpointHandles,
+        kMessagePayloadInterfaceIdsPointerOffset);
+    var payloadInterfaceIdsOffset = decoder.decodePointer();
+    var encoder = new Encoder(this.buffer, this.handles,
+        this.associatedEndpointHandles,
+        payloadInterfaceIdsOffset);
+    encoder.encodeArray(Uint32, payloadInterfaceIds);
+  };
 
-  function MessageBuilder(messageName, payloadSize) {
+  Message.prototype.serializeAssociatedEndpointHandles = function(
+      associatedGroupController) {
+    if (this.associatedEndpointHandles.length > 0) {
+      if (this.getHeaderVersion() < 2) {
+        throw new Error(
+            "Version of message does not support associated endpoint handles");
+      }
+
+      var data = [];
+      for (var i = 0; i < this.associatedEndpointHandles.length; i++) {
+        var handle = this.associatedEndpointHandles[i];
+        data.push(associatedGroupController.associateInterface(handle));
+      }
+      this.associatedEndpointHandles = [];
+      this.setPayloadInterfaceIds_(data);
+    }
+  };
+
+  Message.prototype.deserializeAssociatedEndpointHandles = function(
+      associatedGroupController) {
+    if (this.getHeaderVersion() < 2) {
+      return true;
+    }
+
+    this.associatedEndpointHandles = [];
+    var ids = this.getPayloadInterfaceIds();
+
+    var result = true;
+    for (var i = 0; i < ids.length; i++) {
+      var handle = associatedGroupController.createLocalEndpointHandle(ids[i]);
+      if (types.isValidInterfaceId(ids[i]) && !handle.isValid()) {
+        // |ids[i]| itself is valid but handle creation failed. In that case,
+        // mark deserialization as failed but continue to deserialize the
+        // rest of handles.
+        result = false;
+      }
+      this.associatedEndpointHandles.push(handle);
+      ids[i] = types.kInvalidInterfaceId;
+    }
+
+    this.setPayloadInterfaceIds_(ids);
+    return result;
+  };
+
+
+  // MessageV0Builder ---------------------------------------------------------
+
+  function MessageV0Builder(messageName, payloadSize) {
     // Currently, we don't compute the payload size correctly ahead of time.
     // Instead, we resize the buffer at the end.
     var numberOfBytes = kMessageV0HeaderSize + payloadSize;
@@ -507,16 +590,16 @@
     encoder.writeUint32(0);  // padding.
   }
 
-  MessageBuilder.prototype.createEncoder = function(size) {
+  MessageV0Builder.prototype.createEncoder = function(size) {
     var pointer = this.buffer.alloc(size);
-    return new Encoder(this.buffer, this.handles, pointer);
+    return new Encoder(this.buffer, this.handles, [], pointer);
   };
 
-  MessageBuilder.prototype.encodeStruct = function(cls, val) {
+  MessageV0Builder.prototype.encodeStruct = function(cls, val) {
     cls.encode(this.createEncoder(cls.encodedSize), val);
   };
 
-  MessageBuilder.prototype.finish = function() {
+  MessageV0Builder.prototype.finish = function() {
     // TODO(abarth): Rather than resizing the buffer at the end, we could
     // compute the size we need ahead of time, like we do in C++.
     this.buffer.trim();
@@ -527,9 +610,9 @@
     return message;
   };
 
-  // MessageWithRequestIDBuilder -----------------------------------------------
+  // MessageV1Builder -----------------------------------------------
 
-  function MessageWithRequestIDBuilder(messageName, payloadSize, flags,
+  function MessageV1Builder(messageName, payloadSize, flags,
                                        requestID) {
     // Currently, we don't compute the payload size correctly ahead of time.
     // Instead, we resize the buffer at the end.
@@ -546,16 +629,71 @@
     encoder.writeUint64(requestID);
   }
 
-  MessageWithRequestIDBuilder.prototype =
-      Object.create(MessageBuilder.prototype);
+  MessageV1Builder.prototype =
+      Object.create(MessageV0Builder.prototype);
 
-  MessageWithRequestIDBuilder.prototype.constructor =
-      MessageWithRequestIDBuilder;
+  MessageV1Builder.prototype.constructor =
+      MessageV1Builder;
+
+  // MessageV2 -----------------------------------------------
+
+  function MessageV2Builder(messageName, payloadSize, flags, requestID) {
+    // Currently, we don't compute the payload size correctly ahead of time.
+    // Instead, we resize the buffer at the end.
+    var numberOfBytes = kMessageV2HeaderSize + payloadSize;
+    this.buffer = new buffer.Buffer(numberOfBytes);
+    this.handles = [];
+
+    this.payload = null;
+    this.associatedEndpointHandles = [];
+
+    this.encoder = this.createEncoder(kMessageV2HeaderSize);
+    this.encoder.writeUint32(kMessageV2HeaderSize);
+    this.encoder.writeUint32(2);  // version.
+    // Gets set to an appropriate interfaceId for the endpoint by the Router.
+    this.encoder.writeUint32(0);  // interface ID.
+    this.encoder.writeUint32(messageName);
+    this.encoder.writeUint32(flags);
+    this.encoder.writeUint32(0);  // padding.
+    this.encoder.writeUint64(requestID);
+  }
+
+  MessageV2Builder.prototype.createEncoder = function(size) {
+    var pointer = this.buffer.alloc(size);
+    return new Encoder(this.buffer, this.handles,
+        this.associatedEndpointHandles, pointer);
+  };
+
+  MessageV2Builder.prototype.setPayload = function(cls, val) {
+    this.payload = {cls: cls, val: val};
+  };
+
+  MessageV2Builder.prototype.finish = function() {
+    if (!this.payload) {
+      throw new Error("Payload needs to be set before calling finish");
+    }
+
+    this.encoder.encodeStructPointer(this.payload.cls, this.payload.val);
+    this.encoder.encodeArrayPointer(Uint32,
+        new Array(this.associatedEndpointHandles.length));
+
+    this.buffer.trim();
+    var message = new Message(this.buffer, this.handles,
+        this.associatedEndpointHandles);
+    this.buffer = null;
+    this.handles = null;
+    this.encoder = null;
+    this.payload = null;
+    this.associatedEndpointHandles = null;
+
+    return message;
+  };
 
   // MessageReader ------------------------------------------------------------
 
   function MessageReader(message) {
-    this.decoder = new Decoder(message.buffer, message.handles, 0);
+    this.decoder = new Decoder(message.buffer, message.handles,
+        message.associatedEndpointHandles, 0);
     var messageHeaderSize = this.decoder.readUint32();
     this.payloadSize = message.buffer.byteLength - messageHeaderSize;
     var version = this.decoder.readUint32();
@@ -858,12 +996,31 @@
 
   AssociatedInterfacePtrInfo.prototype.encodedSize = 8;
 
+  AssociatedInterfacePtrInfo.decode = function(decoder) {
+    return new types.AssociatedInterfacePtrInfo(
+      decoder.decodeAssociatedEndpointHandle(), decoder.readUint32());
+  };
+
+  AssociatedInterfacePtrInfo.encode = function(encoder, val) {
+    var associatedinterfacePtrInfo =
+        val ? val : new types.AssociatedInterfacePtrInfo(null, 0);
+    encoder.encodeAssociatedEndpointHandle(
+        associatedinterfacePtrInfo.interfaceEndpointHandle);
+    encoder.writeUint32(associatedinterfacePtrInfo.version);
+  };
+
   function NullableAssociatedInterfacePtrInfo() {
   }
 
   NullableAssociatedInterfacePtrInfo.encodedSize =
       AssociatedInterfacePtrInfo.encodedSize;
 
+  NullableAssociatedInterfacePtrInfo.decode =
+      AssociatedInterfacePtrInfo.decode;
+
+  NullableAssociatedInterfacePtrInfo.encode =
+      AssociatedInterfacePtrInfo.encode;
+
   function InterfaceRequest() {
   }
 
@@ -889,6 +1046,16 @@
   function AssociatedInterfaceRequest() {
   }
 
+  AssociatedInterfaceRequest.decode = function(decoder) {
+    var handle = decoder.decodeAssociatedEndpointHandle();
+    return new types.AssociatedInterfaceRequest(handle);
+  };
+
+  AssociatedInterfaceRequest.encode = function(encoder, val) {
+    encoder.encodeAssociatedEndpointHandle(
+        val ? val.interfaceEndpointHandle : null);
+  };
+
   AssociatedInterfaceRequest.encodedSize = 4;
 
   function NullableAssociatedInterfaceRequest() {
@@ -897,6 +1064,12 @@
   NullableAssociatedInterfaceRequest.encodedSize =
       AssociatedInterfaceRequest.encodedSize;
 
+  NullableAssociatedInterfaceRequest.decode =
+      AssociatedInterfaceRequest.decode;
+
+  NullableAssociatedInterfaceRequest.encode =
+      AssociatedInterfaceRequest.encode;
+
   function MapOf(keyClass, valueClass) {
     this.keyClass = keyClass;
     this.valueClass = valueClass;
@@ -922,8 +1095,9 @@
   exports.align = align;
   exports.isAligned = isAligned;
   exports.Message = Message;
-  exports.MessageBuilder = MessageBuilder;
-  exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
+  exports.MessageV0Builder = MessageV0Builder;
+  exports.MessageV1Builder = MessageV1Builder;
+  exports.MessageV2Builder = MessageV2Builder;
   exports.MessageReader = MessageReader;
   exports.kArrayHeaderSize = kArrayHeaderSize;
   exports.kMapStructPayloadSize = kMapStructPayloadSize;
diff --git a/mojo/public/js/lib/control_message_handler.js b/mojo/public/js/lib/control_message_handler.js
index 5da306e3..09c0b78c 100644
--- a/mojo/public/js/lib/control_message_handler.js
+++ b/mojo/public/js/lib/control_message_handler.js
@@ -75,7 +75,7 @@
     var messageName = controlMessages.kRunMessageId;
     var payloadSize = controlMessages.RunResponseMessageParams.encodedSize;
     var requestID = reader.requestID;
-    var builder = new codec.MessageWithRequestIDBuilder(messageName,
+    var builder = new codec.MessageV1Builder(messageName,
         payloadSize, codec.kMessageIsResponse, requestID);
     builder.encodeStruct(controlMessages.RunResponseMessageParams,
                          runResponseMessageParams);
diff --git a/mojo/public/js/lib/control_message_proxy.js b/mojo/public/js/lib/control_message_proxy.js
index b6f1d3c8..8cd84c5c3 100644
--- a/mojo/public/js/lib/control_message_proxy.js
+++ b/mojo/public/js/lib/control_message_proxy.js
@@ -17,7 +17,7 @@
 
     var messageName = controlMessages.kRunOrClosePipeMessageId;
     var payloadSize = controlMessages.RunOrClosePipeMessageParams.encodedSize;
-    var builder = new codec.MessageBuilder(messageName, payloadSize);
+    var builder = new codec.MessageV0Builder(messageName, payloadSize);
     builder.encodeStruct(controlMessages.RunOrClosePipeMessageParams,
                          runOrClosePipeMessageParams);
     var message = builder.finish();
@@ -66,7 +66,7 @@
     var messageName = controlMessages.kRunMessageId;
     var payloadSize = controlMessages.RunMessageParams.encodedSize;
     // |requestID| is set to 0, but is later properly set by Router.
-    var builder = new codec.MessageWithRequestIDBuilder(messageName,
+    var builder = new codec.MessageV1Builder(messageName,
         payloadSize, codec.kMessageExpectsResponse, 0);
     builder.encodeStruct(controlMessages.RunMessageParams, runMessageParams);
     var message = builder.finish();
diff --git a/mojo/public/js/lib/interface_endpoint_client.js b/mojo/public/js/lib/interface_endpoint_client.js
index 631c52e..b74b6d2 100644
--- a/mojo/public/js/lib/interface_endpoint_client.js
+++ b/mojo/public/js/lib/interface_endpoint_client.js
@@ -21,8 +21,8 @@
   var ControlMessageHandler = controlMessageHandler.ControlMessageHandler;
   var ControlMessageProxy = controlMessageProxy.ControlMessageProxy;
   var MessageReader = codec.MessageReader;
-  var Validator = validator.Validator;
   var InterfaceEndpointHandle = interfaceEndpointHandle.InterfaceEndpointHandle;
+  var AssociationEvent = interfaceEndpointHandle.AssociationEvent;
 
   function InterfaceEndpointClient(interfaceEndpointHandle, receiver,
       interfaceVersion) {
@@ -62,12 +62,10 @@
 
   InterfaceEndpointClient.prototype.onAssociationEvent = function(
       associationEvent) {
-    if (associationEvent ===
-        InterfaceEndpointHandle.AssociationEvent.ASSOCIATED) {
+    if (associationEvent === AssociationEvent.ASSOCIATED) {
       this.initControllerIfNecessary_();
     } else if (associationEvent ===
-        InterfaceEndpointHandle.AssociationEvent
-                               .PEER_CLOSED_BEFORE_ASSOCIATION) {
+          AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION) {
       timer.createOneShot(0, this.notifyError.bind(this,
           this.handle_.disconnectReason()));
     }
@@ -96,6 +94,11 @@
   };
 
   InterfaceEndpointClient.prototype.accept = function(message) {
+    if (message.associatedEndpointHandles.length > 0) {
+      message.serializeAssociatedEndpointHandles(
+          this.handle_.groupController());
+    }
+
     if (this.encounteredError_) {
       return false;
     }
@@ -106,6 +109,11 @@
 
   InterfaceEndpointClient.prototype.acceptAndExpectResponse = function(
       message) {
+    if (message.associatedEndpointHandles.length > 0) {
+      message.serializeAssociatedEndpointHandles(
+          this.handle_.groupController());
+    }
+
     if (this.encounteredError_) {
       return Promise.reject();
     }
@@ -144,10 +152,9 @@
     this.connectionErrorHandler_ = handler;
   };
 
-  InterfaceEndpointClient.prototype.handleIncomingMessage_ = function(
-      message) {
+  InterfaceEndpointClient.prototype.handleIncomingMessage = function(message,
+      messageValidator) {
     var noError = validator.validationError.NONE;
-    var messageValidator = new Validator(message);
     var err = noError;
     for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
       err = this.payloadValidators_[i](messageValidator);
diff --git a/mojo/public/js/lib/interface_endpoint_handle.js b/mojo/public/js/lib/interface_endpoint_handle.js
index f48b89ba..dda951a 100644
--- a/mojo/public/js/lib/interface_endpoint_handle.js
+++ b/mojo/public/js/lib/interface_endpoint_handle.js
@@ -92,6 +92,20 @@
     }
   };
 
+  State.prototype.notifyAssociation = function(interfaceId,
+                                               peerGroupController) {
+    var cachedPeerState = this.peerState_;
+    this.peerState_ = null;
+
+    this.pendingAssociation = false;
+
+    if (cachedPeerState) {
+      cachedPeerState.onAssociated(interfaceId, peerGroupController);
+      return true;
+    }
+    return false;
+  };
+
   State.prototype.onAssociated = function(interfaceId,
       associatedGroupController) {
     if (!this.pendingAssociation) {
@@ -117,6 +131,14 @@
         AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION);
   };
 
+  function createPairPendingAssociation() {
+    var handle0 = new InterfaceEndpointHandle();
+    var handle1 = new InterfaceEndpointHandle();
+    handle0.state_.initPendingState(handle1.state_);
+    handle1.state_.initPendingState(handle0.state_);
+    return {handle0: handle0, handle1: handle1};
+  }
+
   function InterfaceEndpointHandle(interfaceId, associatedGroupController) {
     this.state_ = new State(interfaceId, associatedGroupController);
   }
@@ -146,13 +168,20 @@
     this.state_.setAssociationEventHandler(handler);
   };
 
+  InterfaceEndpointHandle.prototype.notifyAssociation = function(interfaceId,
+      peerGroupController) {
+    return this.state_.notifyAssociation(interfaceId, peerGroupController);
+  };
+
   InterfaceEndpointHandle.prototype.reset = function(reason) {
     this.state_.close(reason);
     this.state_ = new State();
   };
 
   var exports = {};
+  exports.AssociationEvent = AssociationEvent;
   exports.InterfaceEndpointHandle = InterfaceEndpointHandle;
+  exports.createPairPendingAssociation = createPairPendingAssociation;
 
   return exports;
 });
diff --git a/mojo/public/js/lib/pipe_control_message_proxy.js b/mojo/public/js/lib/pipe_control_message_proxy.js
index 4b8e7a20..71091d0 100644
--- a/mojo/public/js/lib/pipe_control_message_proxy.js
+++ b/mojo/public/js/lib/pipe_control_message_proxy.js
@@ -17,7 +17,7 @@
     var payloadSize =
         pipeControlMessages.RunOrClosePipeMessageParams.encodedSize;
 
-    var builder = new codec.MessageBuilder(messageName, payloadSize);
+    var builder = new codec.MessageV0Builder(messageName, payloadSize);
     builder.encodeStruct(pipeControlMessages.RunOrClosePipeMessageParams,
                          runOrClosePipeMessageParams);
     var message = builder.finish();
diff --git a/mojo/public/js/new_bindings/codec.js b/mojo/public/js/new_bindings/codec.js
index 339fc16..ba07cb7 100644
--- a/mojo/public/js/new_bindings/codec.js
+++ b/mojo/public/js/new_bindings/codec.js
@@ -464,9 +464,9 @@
   };
 
 
-  // MessageBuilder -----------------------------------------------------------
+  // MessageV0Builder ---------------------------------------------------------
 
-  function MessageBuilder(messageName, payloadSize) {
+  function MessageV0Builder(messageName, payloadSize) {
     // Currently, we don't compute the payload size correctly ahead of time.
     // Instead, we resize the buffer at the end.
     var numberOfBytes = kMessageHeaderSize + payloadSize;
@@ -481,16 +481,16 @@
     encoder.writeUint32(0);  // padding.
   }
 
-  MessageBuilder.prototype.createEncoder = function(size) {
+  MessageV0Builder.prototype.createEncoder = function(size) {
     var pointer = this.buffer.alloc(size);
     return new Encoder(this.buffer, this.handles, pointer);
   };
 
-  MessageBuilder.prototype.encodeStruct = function(cls, val) {
+  MessageV0Builder.prototype.encodeStruct = function(cls, val) {
     cls.encode(this.createEncoder(cls.encodedSize), val);
   };
 
-  MessageBuilder.prototype.finish = function() {
+  MessageV0Builder.prototype.finish = function() {
     // TODO(abarth): Rather than resizing the buffer at the end, we could
     // compute the size we need ahead of time, like we do in C++.
     this.buffer.trim();
@@ -501,9 +501,9 @@
     return message;
   };
 
-  // MessageWithRequestIDBuilder -----------------------------------------------
+  // MessageV1Builder -----------------------------------------------
 
-  function MessageWithRequestIDBuilder(messageName, payloadSize, flags,
+  function MessageV1Builder(messageName, payloadSize, flags,
                                        requestID) {
     // Currently, we don't compute the payload size correctly ahead of time.
     // Instead, we resize the buffer at the end.
@@ -520,11 +520,11 @@
     encoder.writeUint64(requestID);
   }
 
-  MessageWithRequestIDBuilder.prototype =
-      Object.create(MessageBuilder.prototype);
+  MessageV1Builder.prototype =
+      Object.create(MessageV0Builder.prototype);
 
-  MessageWithRequestIDBuilder.prototype.constructor =
-      MessageWithRequestIDBuilder;
+  MessageV1Builder.prototype.constructor =
+      MessageV1Builder;
 
   // MessageReader ------------------------------------------------------------
 
@@ -877,8 +877,8 @@
   internal.align = align;
   internal.isAligned = isAligned;
   internal.Message = Message;
-  internal.MessageBuilder = MessageBuilder;
-  internal.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
+  internal.MessageV0Builder = MessageV0Builder;
+  internal.MessageV1Builder = MessageV1Builder;
   internal.MessageReader = MessageReader;
   internal.kArrayHeaderSize = kArrayHeaderSize;
   internal.kMapStructPayloadSize = kMapStructPayloadSize;
diff --git a/mojo/public/js/new_bindings/lib/control_message_handler.js b/mojo/public/js/new_bindings/lib/control_message_handler.js
index 3f122fb..991291eb 100644
--- a/mojo/public/js/new_bindings/lib/control_message_handler.js
+++ b/mojo/public/js/new_bindings/lib/control_message_handler.js
@@ -72,7 +72,7 @@
     var payloadSize =
         mojo.interface_control2.RunResponseMessageParams.encodedSize;
     var requestID = reader.requestID;
-    var builder = new internal.MessageWithRequestIDBuilder(messageName,
+    var builder = new internal.MessageV1Builder(messageName,
         payloadSize, internal.kMessageIsResponse, requestID);
     builder.encodeStruct(mojo.interface_control2.RunResponseMessageParams,
                          runResponseMessageParams);
diff --git a/mojo/public/js/new_bindings/lib/control_message_proxy.js b/mojo/public/js/new_bindings/lib/control_message_proxy.js
index 1d57557..9d646752 100644
--- a/mojo/public/js/new_bindings/lib/control_message_proxy.js
+++ b/mojo/public/js/new_bindings/lib/control_message_proxy.js
@@ -9,7 +9,7 @@
     var messageName = mojo.interface_control2.kRunOrClosePipeMessageId;
     var payloadSize =
         mojo.interface_control2.RunOrClosePipeMessageParams.encodedSize;
-    var builder = new internal.MessageBuilder(messageName, payloadSize);
+    var builder = new internal.MessageV0Builder(messageName, payloadSize);
     builder.encodeStruct(mojo.interface_control2.RunOrClosePipeMessageParams,
                          runOrClosePipeMessageParams);
     var message = builder.finish();
@@ -58,7 +58,7 @@
     var messageName = mojo.interface_control2.kRunMessageId;
     var payloadSize = mojo.interface_control2.RunMessageParams.encodedSize;
     // |requestID| is set to 0, but is later properly set by Router.
-    var builder = new internal.MessageWithRequestIDBuilder(messageName,
+    var builder = new internal.MessageV1Builder(messageName,
         payloadSize, internal.kMessageExpectsResponse, 0);
     builder.encodeStruct(mojo.interface_control2.RunMessageParams,
                          runMessageParams);
diff --git a/mojo/public/js/router.js b/mojo/public/js/router.js
index 89d9a2f6..401a222 100644
--- a/mojo/public/js/router.js
+++ b/mojo/public/js/router.js
@@ -74,11 +74,48 @@
     this.setInterfaceIdNamespaceBit_ = setInterfaceIdNamespaceBit;
     this.controlMessageHandler_ = new PipeControlMessageHandler(this);
     this.controlMessageProxy_ = new PipeControlMessageProxy(this.connector_);
-    this.nextInterfaceIdValue = 1;
+    this.nextInterfaceIdValue_ = 1;
     this.encounteredError_ = false;
     this.endpoints_ = new Map();
   }
 
+  Router.prototype.associateInterface = function(handleToSend) {
+    if (!handleToSend.pendingAssociation()) {
+      return types.kInvalidInterfaceId;
+    }
+
+    var id = 0;
+    do {
+      if (this.nextInterfaceIdValue_ >= types.kInterfaceIdNamespaceMask) {
+        this.nextInterfaceIdValue_ = 1;
+      }
+      id = this.nextInterfaceIdValue_++;
+      if (this.setInterfaceIdNamespaceBit_) {
+        id += types.kInterfaceIdNamespaceMask;
+      }
+    } while (this.endpoints_.has(id));
+
+    var endpoint = new InterfaceEndpoint(this, id);
+    this.endpoints_.set(id, endpoint);
+    if (this.encounteredError_) {
+      this.updateEndpointStateMayRemove(endpoint,
+          EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
+    }
+    endpoint.handleCreated = true;
+
+    if (!handleToSend.notifyAssociation(id, this)) {
+      // The peer handle of |handleToSend|, which is supposed to join this
+      // associated group, has been closed.
+      this.updateEndpointStateMayRemove(endpoint,
+          EndpointStateUpdateType.ENDPOINT_CLOSED);
+
+      pipeControlMessageproxy.notifyPeerEndpointClosed(id,
+          handleToSend.disconnectReason());
+    }
+
+    return id;
+  };
+
   Router.prototype.attachEndpointClient = function(
       interfaceEndpointHandle, interfaceEndpointClient) {
     check(types.isValidInterfaceId(interfaceEndpointHandle.id()));
@@ -149,21 +186,25 @@
     var ok = false;
     if (err !== validator.validationError.NONE) {
       validator.reportValidationError(err);
-    } else if (controlMessageHandler.isPipeControlMessage(message)) {
-      ok = this.controlMessageHandler_.accept(message);
-    } else {
-      var interfaceId = message.getInterfaceId();
-      var endpoint = this.endpoints_.get(interfaceId);
-      if (!endpoint || endpoint.closed) {
-        return true;
-      }
+    } else if (message.deserializeAssociatedEndpointHandles(this)) {
+      if (controlMessageHandler.isPipeControlMessage(message)) {
+        ok = this.controlMessageHandler_.accept(message);
+      } else {
+        var interfaceId = message.getInterfaceId();
+        var endpoint = this.endpoints_.get(interfaceId);
+        if (!endpoint || endpoint.closed) {
+          return true;
+        }
 
-      if (!endpoint.client) {
-        // We need to wait until a client is attached in order to dispatch
-        // further messages.
-        return false;
+        if (!endpoint.client) {
+          // We need to wait until a client is attached in order to dispatch
+          // further messages.
+          // TODO(wangjimmy): Cache the message and send when the appropriate
+          // endpoint client is attached.
+          return false;
+        }
+        ok = endpoint.client.handleIncomingMessage(message, messageValidator);
       }
-      ok = endpoint.client.handleIncomingMessage_(message);
     }
 
     if (!ok) {
diff --git a/mojo/public/js/validator.js b/mojo/public/js/validator.js
index 037f3f46..4abc359 100644
--- a/mojo/public/js/validator.js
+++ b/mojo/public/js/validator.js
@@ -109,6 +109,8 @@
 
   function isNullable(type) {
     return type === codec.NullableString || type === codec.NullableHandle ||
+        type === codec.NullableAssociatedInterfacePtrInfo ||
+        type === codec.NullableAssociatedInterfaceRequest ||
         type === codec.NullableInterface ||
         type === codec.NullableInterfaceRequest ||
         type instanceof codec.NullableArrayOf ||
diff --git a/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
index 11e319c1..85d95d6a 100644
--- a/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
@@ -7,6 +7,16 @@
                                                    handleOrPtrInfo);
   }
 
+  function Associated{{interface.name}}Ptr(associatedInterfacePtrInfo) {
+    this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+        {{interface.name}}, associatedInterfacePtrInfo);
+  }
+
+  Associated{{interface.name}}Ptr.prototype =
+      Object.create({{interface.name}}Ptr.prototype);
+  Associated{{interface.name}}Ptr.prototype.constructor =
+      Associated{{interface.name}}Ptr;
+
   function {{interface.name}}Proxy(receiver) {
     this.receiver_ = receiver;
   }
@@ -28,19 +38,34 @@
 {%- endfor %}
 
 {%- if method.response_parameters == None %}
-    var builder = new codec.MessageBuilder(
+{%-   if method|method_passes_associated_kinds and not use_new_js_bindings %}
+    var builder = new codec.MessageV2Builder(
+        k{{interface.name}}_{{method.name}}_Name,
+        codec.align({{interface.name}}_{{method.name}}_Params.encodedSize));
+    builder.setPayload({{interface.name}}_{{method.name}}_Params, params);
+{%-   else %}
+    var builder = new codec.MessageV0Builder(
         k{{interface.name}}_{{method.name}}_Name,
         codec.align({{interface.name}}_{{method.name}}_Params.encodedSize));
     builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params);
+{%-   endif %}
     var message = builder.finish();
     this.receiver_.accept(message);
 {%- else %}
     return new Promise(function(resolve, reject) {
-      var builder = new codec.MessageWithRequestIDBuilder(
+{%-   if method|method_passes_associated_kinds and not use_new_js_bindings %}
+      var builder = new codec.MessageV2Builder(
+          k{{interface.name}}_{{method.name}}_Name,
+          codec.align({{interface.name}}_{{method.name}}_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.setPayload({{interface.name}}_{{method.name}}_Params, params);
+{%-   else %}
+      var builder = new codec.MessageV1Builder(
           k{{interface.name}}_{{method.name}}_Name,
           codec.align({{interface.name}}_{{method.name}}_Params.encodedSize),
           codec.kMessageExpectsResponse, 0);
       builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params);
+{%-   endif %}
       var message = builder.finish();
       this.receiver_.acceptAndExpectResponse(message).then(function(message) {
         var reader = new codec.MessageReader(message);
@@ -102,12 +127,22 @@
 {%-     for parameter in method.response_parameters %}
         responseParams.{{parameter.name}} = response.{{parameter.name}};
 {%-     endfor %}
-        var builder = new codec.MessageWithRequestIDBuilder(
+{%- if method|method_passes_associated_kinds and not use_new_js_bindings %}
+        var builder = new codec.MessageV2Builder(
+            k{{interface.name}}_{{method.name}}_Name,
+            codec.align({{interface.name}}_{{method.name}}_ResponseParams
+                .encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.setPayload({{interface.name}}_{{method.name}}_ResponseParams,
+                             responseParams);
+{%- else %}
+        var builder = new codec.MessageV1Builder(
             k{{interface.name}}_{{method.name}}_Name,
             codec.align({{interface.name}}_{{method.name}}_ResponseParams.encodedSize),
             codec.kMessageIsResponse, reader.requestID);
         builder.encodeStruct({{interface.name}}_{{method.name}}_ResponseParams,
                              responseParams);
+{%- endif %}
         var message = builder.finish();
         responder.accept(message);
       });
diff --git a/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl
index a119ee9..a18c333e 100644
--- a/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl
@@ -46,4 +46,5 @@
 {%- for interface in interfaces %}
   exports.{{interface.name}} = {{interface.name}};
   exports.{{interface.name}}Ptr = {{interface.name}}Ptr;
+  exports.Associated{{interface.name}}Ptr = Associated{{interface.name}}Ptr;
 {%- endfor %}
diff --git a/mojo/public/tools/bindings/generators/mojom_js_generator.py b/mojo/public/tools/bindings/generators/mojom_js_generator.py
index a42bd59..28b709a 100644
--- a/mojo/public/tools/bindings/generators/mojom_js_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_js_generator.py
@@ -365,6 +365,7 @@
     "is_struct_pointer_field": IsStructPointerField,
     "is_union_field": IsUnionField,
     "js_type": JavaScriptType,
+    "method_passes_associated_kinds": mojom.MethodPassesAssociatedKinds,
     "payload_size": JavaScriptPayloadSize,
     "get_relative_path": GetRelativePath,
     "stylize_method": generator.StudlyCapsToCamel,
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/module.py b/mojo/public/tools/bindings/pylib/mojom/generate/module.py
index 3a5f188e..8fa4e44 100644
--- a/mojo/public/tools/bindings/pylib/mojom/generate/module.py
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/module.py
@@ -817,7 +817,17 @@
 # Finds out whether an interface passes associated interfaces and associated
 # interface requests.
 def PassesAssociatedKinds(interface):
-  def _ContainsAssociatedKinds(kind, visited_kinds):
+  visited_kinds = set()
+  for method in interface.methods:
+    if MethodPassesAssociatedKinds(method, visited_kinds):
+      return True
+  return False
+
+
+# Finds out whether a method passes associated interfaces and associated
+# interface requests.
+def MethodPassesAssociatedKinds(method, visited_kinds=None):
+  def _ContainsAssociatedKinds(kind):
     if kind in visited_kinds:
       # No need to examine the kind again.
       return False
@@ -825,26 +835,27 @@
     if IsAssociatedKind(kind):
       return True
     if IsArrayKind(kind):
-      return _ContainsAssociatedKinds(kind.kind, visited_kinds)
+      return _ContainsAssociatedKinds(kind.kind)
     if IsStructKind(kind) or IsUnionKind(kind):
       for field in kind.fields:
-        if _ContainsAssociatedKinds(field.kind, visited_kinds):
+        if _ContainsAssociatedKinds(field.kind):
           return True
     if IsMapKind(kind):
       # No need to examine the key kind, only primitive kinds and non-nullable
       # string are allowed to be key kinds.
-      return _ContainsAssociatedKinds(kind.value_kind, visited_kinds)
+      return _ContainsAssociatedKinds(kind.value_kind)
     return False
 
-  visited_kinds = set()
-  for method in interface.methods:
-    for param in method.parameters:
-      if _ContainsAssociatedKinds(param.kind, visited_kinds):
+  if visited_kinds is None:
+    visited_kinds = set()
+
+  for param in method.parameters:
+    if _ContainsAssociatedKinds(param.kind):
+      return True
+  if method.response_parameters != None:
+    for param in method.response_parameters:
+      if _ContainsAssociatedKinds(param.kind):
         return True
-    if method.response_parameters != None:
-      for param in method.response_parameters:
-        if _ContainsAssociatedKinds(param.kind, visited_kinds):
-          return True
   return False
 
 
diff --git a/net/cert/ct_log_verifier.cc b/net/cert/ct_log_verifier.cc
index a55921a9..e017e36 100644
--- a/net/cert/ct_log_verifier.cc
+++ b/net/cert/ct_log_verifier.cc
@@ -85,7 +85,7 @@
   DCHECK(!dns_domain_.empty());
 }
 
-bool CTLogVerifier::Verify(const ct::LogEntry& entry,
+bool CTLogVerifier::Verify(const ct::SignedEntryData& entry,
                            const ct::SignedCertificateTimestamp& sct) const {
   if (sct.log_id != key_id()) {
     DVLOG(1) << "SCT is not signed by this log.";
@@ -96,7 +96,7 @@
     return false;
 
   std::string serialized_log_entry;
-  if (!ct::EncodeLogEntry(entry, &serialized_log_entry)) {
+  if (!ct::EncodeSignedEntry(entry, &serialized_log_entry)) {
     DVLOG(1) << "Unable to serialize entry.";
     return false;
   }
diff --git a/net/cert/ct_log_verifier.h b/net/cert/ct_log_verifier.h
index 6e3b938..7c03581b 100644
--- a/net/cert/ct_log_verifier.h
+++ b/net/cert/ct_log_verifier.h
@@ -60,7 +60,7 @@
   const std::string& dns_domain() const { return dns_domain_; }
 
   // Verifies that |sct| is valid for |entry| and was signed by this log.
-  bool Verify(const ct::LogEntry& entry,
+  bool Verify(const ct::SignedEntryData& entry,
               const ct::SignedCertificateTimestamp& sct) const;
 
   // Verifies that |signed_tree_head| is a valid Signed Tree Head (RFC 6962,
diff --git a/net/cert/ct_log_verifier_unittest.cc b/net/cert/ct_log_verifier_unittest.cc
index 9067cb6e3..4ec13ff 100644
--- a/net/cert/ct_log_verifier_unittest.cc
+++ b/net/cert/ct_log_verifier_unittest.cc
@@ -389,8 +389,8 @@
 }
 
 TEST_F(CTLogVerifierTest, VerifiesCertSCT) {
-  ct::LogEntry cert_entry;
-  ct::GetX509CertLogEntry(&cert_entry);
+  ct::SignedEntryData cert_entry;
+  ct::GetX509CertSignedEntry(&cert_entry);
 
   scoped_refptr<ct::SignedCertificateTimestamp> cert_sct;
   ct::GetX509CertSCT(&cert_sct);
@@ -399,8 +399,8 @@
 }
 
 TEST_F(CTLogVerifierTest, VerifiesPrecertSCT) {
-  ct::LogEntry precert_entry;
-  ct::GetPrecertLogEntry(&precert_entry);
+  ct::SignedEntryData precert_entry;
+  ct::GetPrecertSignedEntry(&precert_entry);
 
   scoped_refptr<ct::SignedCertificateTimestamp> precert_sct;
   ct::GetPrecertSCT(&precert_sct);
@@ -409,8 +409,8 @@
 }
 
 TEST_F(CTLogVerifierTest, FailsInvalidTimestamp) {
-  ct::LogEntry cert_entry;
-  ct::GetX509CertLogEntry(&cert_entry);
+  ct::SignedEntryData cert_entry;
+  ct::GetX509CertSignedEntry(&cert_entry);
 
   scoped_refptr<ct::SignedCertificateTimestamp> cert_sct;
   ct::GetX509CertSCT(&cert_sct);
@@ -422,8 +422,8 @@
 }
 
 TEST_F(CTLogVerifierTest, FailsInvalidLogID) {
-  ct::LogEntry cert_entry;
-  ct::GetX509CertLogEntry(&cert_entry);
+  ct::SignedEntryData cert_entry;
+  ct::GetX509CertSignedEntry(&cert_entry);
 
   scoped_refptr<ct::SignedCertificateTimestamp> cert_sct;
   ct::GetX509CertSCT(&cert_sct);
diff --git a/net/cert/ct_objects_extractor.cc b/net/cert/ct_objects_extractor.cc
index 1a6e6e4..7c9eb71 100644
--- a/net/cert/ct_objects_extractor.cc
+++ b/net/cert/ct_objects_extractor.cc
@@ -173,9 +173,9 @@
                                        sct_list);
 }
 
-bool GetPrecertLogEntry(X509Certificate::OSCertHandle leaf,
-                        X509Certificate::OSCertHandle issuer,
-                        LogEntry* result) {
+bool GetPrecertSignedEntry(X509Certificate::OSCertHandle leaf,
+                           X509Certificate::OSCertHandle issuer,
+                           SignedEntryData* result) {
   result->Reset();
 
   bssl::UniquePtr<X509> leaf_x509(OSCertHandleToOpenSSL(leaf));
@@ -228,8 +228,8 @@
   if (!asn1::ExtractSPKIFromDERCert(issuer_der, &issuer_key))
     return false;
 
-  // Fill in the LogEntry.
-  result->type = ct::LogEntry::LOG_ENTRY_TYPE_PRECERT;
+  // Fill in the SignedEntryData.
+  result->type = ct::SignedEntryData::LOG_ENTRY_TYPE_PRECERT;
   result->tbs_certificate.swap(to_be_signed);
   crypto::SHA256HashString(issuer_key, result->issuer_key_hash.data,
                            sizeof(result->issuer_key_hash.data));
@@ -237,7 +237,8 @@
   return true;
 }
 
-bool GetX509LogEntry(X509Certificate::OSCertHandle leaf, LogEntry* result) {
+bool GetX509SignedEntry(X509Certificate::OSCertHandle leaf,
+                        SignedEntryData* result) {
   DCHECK(leaf);
 
   std::string encoded;
@@ -245,7 +246,7 @@
     return false;
 
   result->Reset();
-  result->type = ct::LogEntry::LOG_ENTRY_TYPE_X509;
+  result->type = ct::SignedEntryData::LOG_ENTRY_TYPE_X509;
   result->leaf_certificate.swap(encoded);
   return true;
 }
diff --git a/net/cert/ct_objects_extractor.h b/net/cert/ct_objects_extractor.h
index d5deb5b60..469e315 100644
--- a/net/cert/ct_objects_extractor.h
+++ b/net/cert/ct_objects_extractor.h
@@ -15,7 +15,7 @@
 
 namespace ct {
 
-struct LogEntry;
+struct SignedEntryData;
 
 // Extracts a SignedCertificateTimestampList that has been embedded within a
 // leaf cert as an X.509v3 extension with the OID 1.3.6.1.4.1.11129.2.4.2.
@@ -33,9 +33,10 @@
 // The filled |*result| should be verified using ct::CTLogVerifier::Verify
 // Note: If |leaf| does not contain the required extension, it is treated as
 // a failure.
-NET_EXPORT_PRIVATE bool GetPrecertLogEntry(X509Certificate::OSCertHandle leaf,
-                                           X509Certificate::OSCertHandle issuer,
-                                           LogEntry* result);
+NET_EXPORT_PRIVATE bool GetPrecertSignedEntry(
+    X509Certificate::OSCertHandle leaf,
+    X509Certificate::OSCertHandle issuer,
+    SignedEntryData* result);
 
 // Obtains an X509Chain log entry for |leaf|, an X.509v3 certificate that
 // is not expected to contain an X.509v3 extension with the OID
@@ -43,8 +44,8 @@
 // On success, fills |result| with the data for an X509Chain log entry and
 // returns true.
 // The filled |*result| should be verified using ct::CTLogVerifier::Verify
-NET_EXPORT_PRIVATE bool GetX509LogEntry(X509Certificate::OSCertHandle leaf,
-                                        LogEntry* result);
+NET_EXPORT_PRIVATE bool GetX509SignedEntry(X509Certificate::OSCertHandle leaf,
+                                           SignedEntryData* result);
 
 // Extracts a SignedCertificateTimestampList that has been embedded within
 // an OCSP response as an extension with the OID 1.3.6.1.4.1.11129.2.4.5.
diff --git a/net/cert/ct_objects_extractor_unittest.cc b/net/cert/ct_objects_extractor_unittest.cc
index 1073645..5f870c1 100644
--- a/net/cert/ct_objects_extractor_unittest.cc
+++ b/net/cert/ct_objects_extractor_unittest.cc
@@ -72,12 +72,12 @@
 }
 
 TEST_F(CTObjectsExtractorTest, ExtractPrecert) {
-  LogEntry entry;
-  ASSERT_TRUE(GetPrecertLogEntry(precert_chain_[0]->os_cert_handle(),
-                                 precert_chain_[1]->os_cert_handle(),
-                                 &entry));
+  SignedEntryData entry;
+  ASSERT_TRUE(GetPrecertSignedEntry(precert_chain_[0]->os_cert_handle(),
+                                    precert_chain_[1]->os_cert_handle(),
+                                    &entry));
 
-  ASSERT_EQ(ct::LogEntry::LOG_ENTRY_TYPE_PRECERT, entry.type);
+  ASSERT_EQ(ct::SignedEntryData::LOG_ENTRY_TYPE_PRECERT, entry.type);
   // Should have empty leaf cert for this log entry type.
   ASSERT_TRUE(entry.leaf_certificate.empty());
   // Compare hash values of issuer spki.
@@ -87,10 +87,10 @@
 }
 
 TEST_F(CTObjectsExtractorTest, ExtractOrdinaryX509Cert) {
-  LogEntry entry;
-  ASSERT_TRUE(GetX509LogEntry(test_cert_->os_cert_handle(), &entry));
+  SignedEntryData entry;
+  ASSERT_TRUE(GetX509SignedEntry(test_cert_->os_cert_handle(), &entry));
 
-  ASSERT_EQ(ct::LogEntry::LOG_ENTRY_TYPE_X509, entry.type);
+  ASSERT_EQ(ct::SignedEntryData::LOG_ENTRY_TYPE_X509, entry.type);
   // Should have empty tbs_certificate for this log entry type.
   ASSERT_TRUE(entry.tbs_certificate.empty());
   // Length of leaf_certificate should be 718, see the CT Serialization tests.
@@ -103,23 +103,23 @@
       new ct::SignedCertificateTimestamp());
   ExtractEmbeddedSCT(precert_chain_[0], &sct);
 
-  LogEntry entry;
-  ASSERT_TRUE(GetPrecertLogEntry(precert_chain_[0]->os_cert_handle(),
-                                 precert_chain_[1]->os_cert_handle(),
-                                 &entry));
+  SignedEntryData entry;
+  ASSERT_TRUE(GetPrecertSignedEntry(precert_chain_[0]->os_cert_handle(),
+                                    precert_chain_[1]->os_cert_handle(),
+                                    &entry));
 
   EXPECT_TRUE(log_->Verify(entry, *sct.get()));
 }
 
-// Test that an externally-provided SCT verifies over the LogEntry
+// Test that an externally-provided SCT verifies over the SignedEntryData
 // of a regular X.509 Certificate
 TEST_F(CTObjectsExtractorTest, ComplementarySCTVerifies) {
   scoped_refptr<ct::SignedCertificateTimestamp> sct(
       new ct::SignedCertificateTimestamp());
   GetX509CertSCT(&sct);
 
-  LogEntry entry;
-  ASSERT_TRUE(GetX509LogEntry(test_cert_->os_cert_handle(), &entry));
+  SignedEntryData entry;
+  ASSERT_TRUE(GetX509SignedEntry(test_cert_->os_cert_handle(), &entry));
 
   EXPECT_TRUE(log_->Verify(entry, *sct.get()));
 }
diff --git a/net/cert/ct_serialization.cc b/net/cert/ct_serialization.cc
index 64a6ff5..7bc70838 100644
--- a/net/cert/ct_serialization.cc
+++ b/net/cert/ct_serialization.cc
@@ -28,7 +28,7 @@
 
 // Common V1 struct members
 const size_t kTimestampLength = 8;
-const size_t kLogEntryTypeLength = 2;
+const size_t kSignedEntryTypeLength = 2;
 const size_t kAsn1CertificateLengthBytes = 3;
 const size_t kTbsCertificateLengthBytes = 3;
 const size_t kExtensionsLengthBytes = 2;
@@ -247,20 +247,22 @@
   return true;
 }
 
-// Writes a LogEntry of type X.509 cert to |output|.
-// |input| is the LogEntry containing the certificate.
-// Returns true if the leaf_certificate in the LogEntry does not exceed
+// Writes a SignedEntryData of type X.509 cert to |output|.
+// |input| is the SignedEntryData containing the certificate.
+// Returns true if the leaf_certificate in the SignedEntryData does not exceed
 // kMaxAsn1CertificateLength and so can be written to |output|.
-bool EncodeAsn1CertLogEntry(const LogEntry& input, std::string* output) {
+bool EncodeAsn1CertSignedEntry(const SignedEntryData& input,
+                               std::string* output) {
   return WriteVariableBytes(kAsn1CertificateLengthBytes,
                             input.leaf_certificate, output);
 }
 
-// Writes a LogEntry of type PreCertificate to |output|.
-// |input| is the LogEntry containing the TBSCertificate and issuer key hash.
-// Returns true if the TBSCertificate component in the LogEntry does not
-// exceed kMaxTbsCertificateLength and so can be written to |output|.
-bool EncodePrecertLogEntry(const LogEntry& input, std::string* output) {
+// Writes a SignedEntryData of type PreCertificate to |output|.
+// |input| is the SignedEntryData containing the TBSCertificate and issuer key
+// hash. Returns true if the TBSCertificate component in the SignedEntryData
+// does not exceed kMaxTbsCertificateLength and so can be written to |output|.
+bool EncodePrecertSignedEntry(const SignedEntryData& input,
+                              std::string* output) {
   WriteEncodedBytes(
       base::StringPiece(
           reinterpret_cast<const char*>(input.issuer_key_hash.data),
@@ -308,13 +310,13 @@
   return true;
 }
 
-bool EncodeLogEntry(const LogEntry& input, std::string* output) {
-  WriteUint(kLogEntryTypeLength, input.type, output);
+bool EncodeSignedEntry(const SignedEntryData& input, std::string* output) {
+  WriteUint(kSignedEntryTypeLength, input.type, output);
   switch (input.type) {
-    case LogEntry::LOG_ENTRY_TYPE_X509:
-      return EncodeAsn1CertLogEntry(input, output);
-    case LogEntry::LOG_ENTRY_TYPE_PRECERT:
-      return EncodePrecertLogEntry(input, output);
+    case SignedEntryData::LOG_ENTRY_TYPE_X509:
+      return EncodeAsn1CertSignedEntry(input, output);
+    case SignedEntryData::LOG_ENTRY_TYPE_PRECERT:
+      return EncodePrecertSignedEntry(input, output);
   }
   return false;
 }
@@ -349,7 +351,7 @@
   WriteUint(kVersionLength, 0, output);         // version: 1
   WriteUint(kMerkleLeafTypeLength, 0, output);  // type: timestamped entry
   WriteTimeSinceEpoch(leaf.timestamp, output);
-  if (!EncodeLogEntry(leaf.log_entry, output))
+  if (!EncodeSignedEntry(leaf.signed_entry, output))
     return false;
   if (!WriteVariableBytes(kExtensionsLengthBytes, leaf.extensions, output))
     return false;
diff --git a/net/cert/ct_serialization.h b/net/cert/ct_serialization.h
index b5b3d77d..5dfb438 100644
--- a/net/cert/ct_serialization.h
+++ b/net/cert/ct_serialization.h
@@ -20,9 +20,9 @@
 namespace ct {
 
 struct DigitallySigned;
-struct LogEntry;
 struct MerkleTreeLeaf;
 struct SignedCertificateTimestamp;
+struct SignedEntryData;
 struct SignedTreeHead;
 
 // If |input.signature_data| is less than kMaxSignatureLength, encodes the
@@ -36,10 +36,10 @@
 NET_EXPORT_PRIVATE bool DecodeDigitallySigned(base::StringPiece* input,
                                               DigitallySigned* output);
 
-// Encodes the |input| LogEntry to |output|. Returns true if the entry size
-// does not exceed allowed size in RFC6962, false otherwise.
-NET_EXPORT_PRIVATE bool EncodeLogEntry(const LogEntry& input,
-                                       std::string* output);
+// Encodes the |input| SignedEntryData to |output|. Returns true if the entry
+// size does not exceed allowed size in RFC6962, false otherwise.
+NET_EXPORT_PRIVATE bool EncodeSignedEntry(const SignedEntryData& input,
+                                          std::string* output);
 
 // Serialises the Merkle tree |leaf|, appending it to |output|.
 // These bytes can be hashed for use with audit proof fetching.
diff --git a/net/cert/ct_serialization_unittest.cc b/net/cert/ct_serialization_unittest.cc
index 6ddb329..285643d 100644
--- a/net/cert/ct_serialization_unittest.cc
+++ b/net/cert/ct_serialization_unittest.cc
@@ -79,13 +79,12 @@
   EXPECT_EQ(test_digitally_signed_, encoded);
 }
 
-
-TEST_F(CtSerializationTest, EncodesLogEntryForX509Cert) {
-  ct::LogEntry entry;
-  ct::GetX509CertLogEntry(&entry);
+TEST_F(CtSerializationTest, EncodesSignedEntryForX509Cert) {
+  ct::SignedEntryData entry;
+  ct::GetX509CertSignedEntry(&entry);
 
   std::string encoded;
-  ASSERT_TRUE(ct::EncodeLogEntry(entry, &encoded));
+  ASSERT_TRUE(ct::EncodeSignedEntry(entry, &encoded));
   EXPECT_EQ((718U + 5U), encoded.size());
   // First two bytes are log entry type. Next, length:
   // Length is 718 which is 512 + 206, which is 0x2ce
@@ -95,12 +94,12 @@
   EXPECT_EQ(expected_prefix, encoded.substr(0, 5));
 }
 
-TEST_F(CtSerializationTest, EncodesLogEntryForPrecert) {
-  ct::LogEntry entry;
-  ct::GetPrecertLogEntry(&entry);
+TEST_F(CtSerializationTest, EncodesSignedEntryForPrecert) {
+  ct::SignedEntryData entry;
+  ct::GetPrecertSignedEntry(&entry);
 
   std::string encoded;
-  ASSERT_TRUE(ct::EncodeLogEntry(entry, &encoded));
+  ASSERT_TRUE(ct::EncodeSignedEntry(entry, &encoded));
   EXPECT_EQ(604u, encoded.size());
   // First two bytes are the log entry type.
   EXPECT_EQ(std::string("\x00\x01", 2), encoded.substr(0, 2));
@@ -203,8 +202,8 @@
       "Log entry type encoded incorrectly";
   EXPECT_EQ(std::string("\x00\x02\xce", 3), encoded.substr(12, 3)) <<
       "Certificate length encoded incorrectly";
-  EXPECT_EQ(tree_leaf.log_entry.leaf_certificate, encoded.substr(15, 718)) <<
-      "Certificate encoded incorrectly";
+  EXPECT_EQ(tree_leaf.signed_entry.leaf_certificate, encoded.substr(15, 718))
+      << "Certificate encoded incorrectly";
   EXPECT_EQ(std::string("\x00\x06", 2), encoded.substr(733, 2)) <<
       "CT extensions length encoded incorrectly";
   EXPECT_EQ(tree_leaf.extensions, encoded.substr(735, 6)) <<
@@ -228,12 +227,12 @@
   EXPECT_EQ(std::string("\x00\x01", 2), encoded.substr(10, 2)) <<
       "Log entry type encoded incorrectly";
   EXPECT_THAT(encoded.substr(12, 32),
-              ElementsAreArray(tree_leaf.log_entry.issuer_key_hash.data)) <<
-      "Issuer key hash encoded incorrectly";
+              ElementsAreArray(tree_leaf.signed_entry.issuer_key_hash.data))
+      << "Issuer key hash encoded incorrectly";
   EXPECT_EQ(std::string("\x00\x02\x37", 3), encoded.substr(44, 3)) <<
       "TBS certificate length encoded incorrectly";
-  EXPECT_EQ(tree_leaf.log_entry.tbs_certificate, encoded.substr(47, 567)) <<
-      "TBS certificate encoded incorrectly";
+  EXPECT_EQ(tree_leaf.signed_entry.tbs_certificate, encoded.substr(47, 567))
+      << "TBS certificate encoded incorrectly";
   EXPECT_EQ(std::string("\x00\x06", 2), encoded.substr(614, 2)) <<
       "CT extensions length encoded incorrectly";
   EXPECT_EQ(tree_leaf.extensions, encoded.substr(616, 6)) <<
diff --git a/net/cert/merkle_tree_leaf.cc b/net/cert/merkle_tree_leaf.cc
index 5dfe08e..5fb2eba 100644
--- a/net/cert/merkle_tree_leaf.cc
+++ b/net/cert/merkle_tree_leaf.cc
@@ -36,14 +36,14 @@
                        MerkleTreeLeaf* merkle_tree_leaf) {
   if (sct->origin == SignedCertificateTimestamp::SCT_EMBEDDED) {
     if (cert->GetIntermediateCertificates().empty() ||
-        !GetPrecertLogEntry(cert->os_cert_handle(),
-                            cert->GetIntermediateCertificates().front(),
-                            &merkle_tree_leaf->log_entry)) {
+        !GetPrecertSignedEntry(cert->os_cert_handle(),
+                               cert->GetIntermediateCertificates().front(),
+                               &merkle_tree_leaf->signed_entry)) {
       return false;
     }
   } else {
-    if (!GetX509LogEntry(cert->os_cert_handle(),
-                         &merkle_tree_leaf->log_entry)) {
+    if (!GetX509SignedEntry(cert->os_cert_handle(),
+                            &merkle_tree_leaf->signed_entry)) {
       return false;
     }
   }
diff --git a/net/cert/merkle_tree_leaf.h b/net/cert/merkle_tree_leaf.h
index 21217bb6..fc566e6 100644
--- a/net/cert/merkle_tree_leaf.h
+++ b/net/cert/merkle_tree_leaf.h
@@ -31,7 +31,7 @@
 // new types are planned.
 // * The timestamped_entry's |timestamp| and |extensions| fields are directly
 // accessible.
-// * The timestamped_entry's entry_type can be deduced from |log_entry|.type
+// * The timestamped_entry's entry_type can be deduced from |signed_entry|.type
 struct NET_EXPORT MerkleTreeLeaf {
   MerkleTreeLeaf();
   MerkleTreeLeaf(const MerkleTreeLeaf& other);
@@ -39,7 +39,7 @@
   ~MerkleTreeLeaf();
 
   // Certificate / Precertificate and indication of entry type.
-  LogEntry log_entry;
+  SignedEntryData signed_entry;
 
   // Timestamp from the SCT.
   base::Time timestamp;
diff --git a/net/cert/merkle_tree_leaf_unittest.cc b/net/cert/merkle_tree_leaf_unittest.cc
index 29c4c18..59ac7dce 100644
--- a/net/cert/merkle_tree_leaf_unittest.cc
+++ b/net/cert/merkle_tree_leaf_unittest.cc
@@ -75,9 +75,9 @@
   MerkleTreeLeaf leaf;
   ASSERT_TRUE(GetMerkleTreeLeaf(test_cert_.get(), x509_sct_.get(), &leaf));
 
-  EXPECT_EQ(LogEntry::LOG_ENTRY_TYPE_X509, leaf.log_entry.type);
-  EXPECT_FALSE(leaf.log_entry.leaf_certificate.empty());
-  EXPECT_TRUE(leaf.log_entry.tbs_certificate.empty());
+  EXPECT_EQ(SignedEntryData::LOG_ENTRY_TYPE_X509, leaf.signed_entry.type);
+  EXPECT_FALSE(leaf.signed_entry.leaf_certificate.empty());
+  EXPECT_TRUE(leaf.signed_entry.tbs_certificate.empty());
 
   EXPECT_EQ(x509_sct_->timestamp, leaf.timestamp);
   EXPECT_EQ(x509_sct_->extensions, leaf.extensions);
@@ -88,9 +88,9 @@
   ASSERT_TRUE(
       GetMerkleTreeLeaf(test_precert_.get(), precert_sct_.get(), &leaf));
 
-  EXPECT_EQ(LogEntry::LOG_ENTRY_TYPE_PRECERT, leaf.log_entry.type);
-  EXPECT_FALSE(leaf.log_entry.tbs_certificate.empty());
-  EXPECT_TRUE(leaf.log_entry.leaf_certificate.empty());
+  EXPECT_EQ(SignedEntryData::LOG_ENTRY_TYPE_PRECERT, leaf.signed_entry.type);
+  EXPECT_FALSE(leaf.signed_entry.tbs_certificate.empty());
+  EXPECT_TRUE(leaf.signed_entry.leaf_certificate.empty());
 
   EXPECT_EQ(precert_sct_->timestamp, leaf.timestamp);
   EXPECT_EQ(precert_sct_->extensions, leaf.extensions);
diff --git a/net/cert/multi_log_ct_verifier.cc b/net/cert/multi_log_ct_verifier.cc
index a372b05c..e6b6c85 100644
--- a/net/cert/multi_log_ct_verifier.cc
+++ b/net/cert/multi_log_ct_verifier.cc
@@ -97,11 +97,11 @@
       ct::ExtractEmbeddedSCTList(
           cert->os_cert_handle(),
           &embedded_scts)) {
-    ct::LogEntry precert_entry;
+    ct::SignedEntryData precert_entry;
 
-    if (ct::GetPrecertLogEntry(cert->os_cert_handle(),
-                               cert->GetIntermediateCertificates().front(),
-                               &precert_entry)) {
+    if (ct::GetPrecertSignedEntry(cert->os_cert_handle(),
+                                  cert->GetIntermediateCertificates().front(),
+                                  &precert_entry)) {
       VerifySCTs(embedded_scts, precert_entry,
                  ct::SignedCertificateTimestamp::SCT_EMBEDDED, cert,
                  output_scts);
@@ -125,8 +125,8 @@
   net_log.AddEvent(NetLogEventType::SIGNED_CERTIFICATE_TIMESTAMPS_RECEIVED,
                    net_log_callback);
 
-  ct::LogEntry x509_entry;
-  if (ct::GetX509LogEntry(cert->os_cert_handle(), &x509_entry)) {
+  ct::SignedEntryData x509_entry;
+  if (ct::GetX509SignedEntry(cert->os_cert_handle(), &x509_entry)) {
     VerifySCTs(sct_list_from_ocsp, x509_entry,
                ct::SignedCertificateTimestamp::SCT_FROM_OCSP_RESPONSE, cert,
                output_scts);
@@ -147,7 +147,7 @@
 
 void MultiLogCTVerifier::VerifySCTs(
     base::StringPiece encoded_sct_list,
-    const ct::LogEntry& expected_entry,
+    const ct::SignedEntryData& expected_entry,
     ct::SignedCertificateTimestamp::Origin origin,
     X509Certificate* cert,
     SignedCertificateTimestampAndStatusList* output_scts) {
@@ -177,7 +177,7 @@
 
 bool MultiLogCTVerifier::VerifySingleSCT(
     scoped_refptr<ct::SignedCertificateTimestamp> sct,
-    const ct::LogEntry& expected_entry,
+    const ct::SignedEntryData& expected_entry,
     X509Certificate* cert,
     SignedCertificateTimestampAndStatusList* output_scts) {
   // Assume this SCT is untrusted until proven otherwise.
diff --git a/net/cert/multi_log_ct_verifier.h b/net/cert/multi_log_ct_verifier.h
index d4cb56f..3a69b5e2 100644
--- a/net/cert/multi_log_ct_verifier.h
+++ b/net/cert/multi_log_ct_verifier.h
@@ -18,7 +18,7 @@
 namespace net {
 
 namespace ct {
-struct LogEntry;
+struct SignedEntryData;
 }  // namespace ct
 
 class CTLogVerifier;
@@ -48,14 +48,14 @@
   // placing the verification results in |output_scts|. The SCTs in the list
   // come from |origin| (as will be indicated in the origin field of each SCT).
   void VerifySCTs(base::StringPiece encoded_sct_list,
-                  const ct::LogEntry& expected_entry,
+                  const ct::SignedEntryData& expected_entry,
                   ct::SignedCertificateTimestamp::Origin origin,
                   X509Certificate* cert,
                   SignedCertificateTimestampAndStatusList* output_scts);
 
   // Verifies a single, parsed SCT against all logs.
   bool VerifySingleSCT(scoped_refptr<ct::SignedCertificateTimestamp> sct,
-                       const ct::LogEntry& expected_entry,
+                       const ct::SignedEntryData& expected_entry,
                        X509Certificate* cert,
                        SignedCertificateTimestampAndStatusList* output_scts);
 
diff --git a/net/cert/signed_certificate_timestamp.cc b/net/cert/signed_certificate_timestamp.cc
index 489c88c..9096fde 100644
--- a/net/cert/signed_certificate_timestamp.cc
+++ b/net/cert/signed_certificate_timestamp.cc
@@ -78,12 +78,12 @@
   return sct;
 }
 
-LogEntry::LogEntry() : type(LOG_ENTRY_TYPE_X509) {}
+SignedEntryData::SignedEntryData() : type(LOG_ENTRY_TYPE_X509) {}
 
-LogEntry::~LogEntry() {}
+SignedEntryData::~SignedEntryData() {}
 
-void LogEntry::Reset() {
-  type = LogEntry::LOG_ENTRY_TYPE_X509;
+void SignedEntryData::Reset() {
+  type = SignedEntryData::LOG_ENTRY_TYPE_X509;
   leaf_certificate.clear();
   tbs_certificate.clear();
 }
diff --git a/net/cert/signed_certificate_timestamp.h b/net/cert/signed_certificate_timestamp.h
index 96eded2..a23b708 100644
--- a/net/cert/signed_certificate_timestamp.h
+++ b/net/cert/signed_certificate_timestamp.h
@@ -24,16 +24,29 @@
 // Structures related to Certificate Transparency (RFC6962).
 namespace ct {
 
-// LogEntry struct in RFC 6962, Section 3.1
-struct NET_EXPORT LogEntry {
+// Contains the data necessary to reconstruct the signed_entry of a
+// SignedCertificateTimestamp, from RFC 6962, Section 3.2.
+//
+// All the data necessary to validate a SignedCertificateTimestamp is present
+// within the SignedCertificateTimestamp, except for the signature_type,
+// entry_type, and the actual entry. The only supported signature_type at
+// present is certificate_timestamp.  The entry_type is implicit from the
+// context in which it is received (those in the X.509 extension are
+// precert_entry, all others are x509_entry). The signed_entry itself is
+// reconstructed from the certificate being verified, or from the corresponding
+// precertificate.
+//
+// The SignedEntryData contains this reconstructed data, and can be used to
+// either generate or verify the signature in SCTs.
+struct NET_EXPORT SignedEntryData {
   // LogEntryType enum in RFC 6962, Section 3.1
   enum Type {
     LOG_ENTRY_TYPE_X509 = 0,
     LOG_ENTRY_TYPE_PRECERT = 1
   };
 
-  LogEntry();
-  ~LogEntry();
+  SignedEntryData();
+  ~SignedEntryData();
   void Reset();
 
   Type type;
diff --git a/net/test/ct_test_util.cc b/net/test/ct_test_util.cc
index f9862e0..138f1386 100644
--- a/net/test/ct_test_util.cc
+++ b/net/test/ct_test_util.cc
@@ -173,21 +173,21 @@
 
 }  // namespace
 
-void GetX509CertLogEntry(LogEntry* entry) {
-  entry->type = ct::LogEntry::LOG_ENTRY_TYPE_X509;
+void GetX509CertSignedEntry(SignedEntryData* entry) {
+  entry->type = ct::SignedEntryData::LOG_ENTRY_TYPE_X509;
   entry->leaf_certificate = HexToBytes(kDefaultDerCert);
 }
 
 void GetX509CertTreeLeaf(MerkleTreeLeaf* tree_leaf) {
   tree_leaf->timestamp = base::Time::FromJsTime(kTestTimestamp);
-  GetX509CertLogEntry(&tree_leaf->log_entry);
+  GetX509CertSignedEntry(&tree_leaf->signed_entry);
   tree_leaf->extensions = HexToBytes(kDefaultExtensions);
 }
 
 std::string GetDerEncodedX509Cert() { return HexToBytes(kDefaultDerCert); }
 
-void GetPrecertLogEntry(LogEntry* entry) {
-  entry->type = ct::LogEntry::LOG_ENTRY_TYPE_PRECERT;
+void GetPrecertSignedEntry(SignedEntryData* entry) {
+  entry->type = ct::SignedEntryData::LOG_ENTRY_TYPE_PRECERT;
   std::string issuer_hash(HexToBytes(kDefaultIssuerKeyHash));
   memcpy(entry->issuer_key_hash.data, issuer_hash.data(), issuer_hash.size());
   entry->tbs_certificate = HexToBytes(kDefaultDerTbsCert);
@@ -195,7 +195,7 @@
 
 void GetPrecertTreeLeaf(MerkleTreeLeaf* tree_leaf) {
   tree_leaf->timestamp = base::Time::FromJsTime(kTestTimestamp);
-  GetPrecertLogEntry(&tree_leaf->log_entry);
+  GetPrecertSignedEntry(&tree_leaf->signed_entry);
   tree_leaf->extensions = HexToBytes(kDefaultExtensions);
 }
 
diff --git a/net/test/ct_test_util.h b/net/test/ct_test_util.h
index d39079690..a784ca2b 100644
--- a/net/test/ct_test_util.h
+++ b/net/test/ct_test_util.h
@@ -20,15 +20,15 @@
 namespace ct {
 
 struct DigitallySigned;
-struct LogEntry;
 struct MerkleTreeLeaf;
+struct SignedEntryData;
 struct SignedTreeHead;
 
 // Note: unless specified otherwise, all test data is taken from Certificate
 // Transparency test data repository.
 
 // Fills |entry| with test data for an X.509 entry.
-void GetX509CertLogEntry(LogEntry* entry);
+void GetX509CertSignedEntry(SignedEntryData* entry);
 
 // Fills |tree_leaf| with test data for an X.509 Merkle tree leaf.
 void GetX509CertTreeLeaf(MerkleTreeLeaf* tree_leaf);
@@ -38,7 +38,7 @@
 std::string GetDerEncodedX509Cert();
 
 // Fills |entry| with test data for a Precertificate entry.
-void GetPrecertLogEntry(LogEntry* entry);
+void GetPrecertSignedEntry(SignedEntryData* entry);
 
 // Fills |tree_leaf| with test data for a Precertificate Merkle tree leaf.
 void GetPrecertTreeLeaf(MerkleTreeLeaf* tree_leaf);
diff --git a/third_party/WebKit/LayoutTests/mojo/associated_interface_ptr.html b/third_party/WebKit/LayoutTests/mojo/associated_interface_ptr.html
new file mode 100644
index 0000000..361cff3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/mojo/associated_interface_ptr.html
@@ -0,0 +1,226 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/mojo-helpers.js"></script>
+<script>
+'use strict';
+
+setup({ explicit_done: true });
+
+define([
+    "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom",
+    "mojo/public/js/associated_bindings",
+    "mojo/public/js/bindings",
+], function(testAssociatedInterfaces, associatedBindings, bindings) {
+
+  function IntegerSenderImpl(callback) {
+    this.callback = callback;
+  }
+
+  IntegerSenderImpl.prototype.echo = function(value) {
+    return Promise.resolve({value: value});
+  };
+
+  IntegerSenderImpl.prototype.send = function(value) {
+    if (this.callback) {
+      this.callback(value);
+    }
+  };
+
+  function IntegerSenderConnectionImpl() {
+    this.integerSenderBinding_ = null;
+  }
+
+  IntegerSenderConnectionImpl.prototype.getSender = function(
+      integerSenderRequest) {
+    this.integerSenderBinding_ = new associatedBindings.AssociatedBinding(
+        testAssociatedInterfaces.IntegerSender,
+        new IntegerSenderImpl(),
+        integerSenderRequest);
+  };
+
+  IntegerSenderConnectionImpl.prototype.asyncGetSender = function() {
+    var integerSenderPtrInfo = new
+        associatedBindings.AssociatedInterfacePtrInfo();
+    var integerSenderRequest = associatedBindings.makeRequest(
+        integerSenderPtrInfo);
+    this.getSender(integerSenderRequest);
+    return Promise.resolve({sender: integerSenderPtrInfo});
+  };
+
+  function IntegerSenderConnectionAtBothEndsImpl() {
+    this.integerSender_ = null;
+  }
+
+  IntegerSenderConnectionAtBothEndsImpl.prototype.getSender =
+      IntegerSenderConnectionImpl.prototype.getSender;
+
+  IntegerSenderConnectionAtBothEndsImpl.prototype.setSender = function(
+      integerSenderPtrInfo) {
+    this.integerSender_ = new
+        testAssociatedInterfaces.AssociatedIntegerSenderPtr();
+    this.integerSender_.ptr.bind(integerSenderPtrInfo);
+    return this.integerSender_.echo(456);
+  };
+
+  function IntegerSenderConnectionImplWithConnectionError() {
+    this.integerSenderBinding_ = null;
+  }
+
+  IntegerSenderConnectionImplWithConnectionError.prototype.getSender =
+      function(integerSenderRequest) {
+    this.integerSenderBinding_ = new associatedBindings.AssociatedBinding(
+        testAssociatedInterfaces.IntegerSender,
+        new IntegerSenderImpl(),
+        integerSenderRequest);
+    this.integerSenderBinding_.closeWithReason(
+        {custom_reason: 42, description: 'hey'});
+  };
+
+  promise_test(async () => {
+    var integerSenderConnection = new
+        testAssociatedInterfaces.IntegerSenderConnectionPtr();
+    var integerSenderConnectionBinding = new bindings.Binding(
+        testAssociatedInterfaces.IntegerSenderConnection,
+        new IntegerSenderConnectionImpl(),
+        bindings.makeRequest(integerSenderConnection));
+
+    // Sending AssociatedInterfaceRequest.
+    var integerSenderPtrInfo0 = new
+        associatedBindings.AssociatedInterfacePtrInfo();
+    var integerSenderRequest0 = associatedBindings.makeRequest(
+        integerSenderPtrInfo0);
+
+    var integerSender0 = new
+        testAssociatedInterfaces.AssociatedIntegerSenderPtr();
+    integerSender0.ptr.bind(integerSenderPtrInfo0);
+
+    integerSenderConnection.getSender(integerSenderRequest0);
+    assert_equals((await integerSender0.echo(123)).value, 123);
+
+    // Recieving AssociatedInterfacePtrInfo.
+    var integerSenderPtrInfo1 =
+        (await integerSenderConnection.asyncGetSender()).sender;
+    var integerSender1 = new
+        testAssociatedInterfaces.AssociatedIntegerSenderPtr();
+    integerSender1.ptr.bind(integerSenderPtrInfo1);
+    assert_equals((await integerSender1.echo(456)).value, 456);
+  }, 'pass associated interfaces');
+
+  // Bind to the same pipe two associated interfaces, whose implementation
+  // lives at different ends. Test that the two don't interfere.
+  promise_test(async () => {
+    var integerSenderConnectionAtBothEnds = new
+        testAssociatedInterfaces.IntegerSenderConnectionAtBothEndsPtr();
+    var integerSenderConnectionAtBothEndsBinding = new bindings.Binding(
+        testAssociatedInterfaces.IntegerSenderConnectionAtBothEnds,
+        new IntegerSenderConnectionAtBothEndsImpl(),
+        bindings.makeRequest(integerSenderConnectionAtBothEnds));
+
+    // Associated Interface whose Binding Impl lives on the other side.
+    // Sending AssociatedInterfaceRequest.
+    var integerSenderPtrInfo0 = new
+        associatedBindings.AssociatedInterfacePtrInfo();
+    var integerSenderRequest0 = associatedBindings.makeRequest(
+        integerSenderPtrInfo0);
+
+    var integerSender0 = new
+        testAssociatedInterfaces.AssociatedIntegerSenderPtr();
+    integerSender0.ptr.bind(integerSenderPtrInfo0);
+
+    integerSenderConnectionAtBothEnds.getSender(integerSenderRequest0);
+    assert_equals((await integerSender0.echo(123)).value, 123);
+
+    // Associated Interface whose Binding Impl lives on this side.
+    // Sending AssociatedInterfacePtrInfo.
+    var integerSenderPtrInfo1 = new
+        associatedBindings.AssociatedInterfacePtrInfo();
+    var integerSenderRequest1 = associatedBindings.makeRequest(
+        integerSenderPtrInfo1);
+
+    var integerSenderBinding = new associatedBindings.AssociatedBinding(
+        testAssociatedInterfaces.IntegerSender,
+        new IntegerSenderImpl(),
+        integerSenderRequest1);
+
+    assert_equals((await integerSenderConnectionAtBothEnds.setSender(
+        integerSenderPtrInfo1)).value, 456);
+  }, 'associated interfaces on both ends');
+
+  promise_test(async () => {
+    var integerSenderConnection = new
+        testAssociatedInterfaces.IntegerSenderConnectionPtr();
+    var integerSenderConnectionBinding = new bindings.Binding(
+        testAssociatedInterfaces.IntegerSenderConnection,
+        new IntegerSenderConnectionImplWithConnectionError(),
+        bindings.makeRequest(integerSenderConnection));
+
+    // Sending AssociatedInterfaceRequest.
+    var integerSenderPtrInfo0 = new
+        associatedBindings.AssociatedInterfacePtrInfo();
+    var integerSenderRequest0 = associatedBindings.makeRequest(
+        integerSenderPtrInfo0);
+
+    var integerSender0 = new
+        testAssociatedInterfaces.AssociatedIntegerSenderPtr();
+    integerSender0.ptr.bind(integerSenderPtrInfo0);
+
+    integerSenderConnection.getSender(integerSenderRequest0);
+    await new Promise((resolve, reject) => {
+      integerSender0.ptr.setConnectionErrorHandler(function({custom_reason,
+          description}) {
+        assert_equals(custom_reason, 42);
+        assert_equals(description, 'hey');
+        resolve();
+      });
+    });
+  }, 'connection error with reason');
+
+  promise_test(async () => {
+    var integerSenderConnection = new
+        testAssociatedInterfaces.IntegerSenderConnectionPtr();
+    var integerSenderConnectionBinding = new bindings.Binding(
+        testAssociatedInterfaces.IntegerSenderConnection,
+        new IntegerSenderConnectionImpl(),
+        bindings.makeRequest(integerSenderConnection));
+
+    // Sending AssociatedInterfaceRequest.
+    var integerSenderPtrInfo0 = new
+        associatedBindings.AssociatedInterfacePtrInfo();
+    var integerSenderRequest0 = associatedBindings.makeRequest(
+        integerSenderPtrInfo0);
+    var integerSender0 = new
+        testAssociatedInterfaces.AssociatedIntegerSenderPtr();
+    integerSender0.ptr.bind(integerSenderPtrInfo0);
+    integerSenderConnection.getSender(integerSenderRequest0);
+
+    // Recieving AssociatedInterfacePtrInfo.
+    var integerSenderPtrInfo1 =
+        (await integerSenderConnection.asyncGetSender()).sender;
+    var integerSender1 = new
+        testAssociatedInterfaces.AssociatedIntegerSenderPtr();
+    integerSender1.ptr.bind(integerSenderPtrInfo1);
+
+    // Master InterfacePtrController reset triggers connection error handler on
+    // interface endpoint clients for all associated endpoints.
+    var connectionErrorHandler0 = new Promise((resolve, reject) => {
+      integerSender0.ptr.setConnectionErrorHandler(() => {
+        resolve();
+      });
+    });
+
+    var connectionErrorHandler1 = new Promise((resolve, reject) => {
+      integerSender1.ptr.setConnectionErrorHandler(() => {
+        resolve();
+      });
+    });
+
+    setTimeout(integerSenderConnection.ptr.reset.bind(
+        integerSenderConnection.ptr), 0);
+    await Promise.all([connectionErrorHandler0, connectionErrorHandler1]);
+  }, 'all endpoints connectionErrorHandler called on master interface reset');
+
+  done();
+});
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/mojo/codec.html b/third_party/WebKit/LayoutTests/mojo/codec.html
index 0cf09bc..a02ea1ef 100644
--- a/third_party/WebKit/LayoutTests/mojo/codec.html
+++ b/third_party/WebKit/LayoutTests/mojo/codec.html
@@ -26,7 +26,7 @@
     var messageName = 42;
     var payloadSize = sample.Bar.encodedSize;
 
-    var builder = new codec.MessageBuilder(messageName, payloadSize);
+    var builder = new codec.MessageV0Builder(messageName, payloadSize);
     builder.encodeStruct(sample.Bar, bar);
 
     var message = builder.finish();
@@ -98,7 +98,7 @@
     var messageName = 31;
     var payloadSize = 304;
 
-    var builder = new codec.MessageBuilder(messageName, payloadSize);
+    var builder = new codec.MessageV0Builder(messageName, payloadSize);
     builder.encodeStruct(sample.Foo, foo);
 
     var message = builder.finish();
@@ -165,7 +165,7 @@
     r.name = "rectangle";
     r.rects = new Array(createRect(1, 2, 3, 4), createRect(10, 20, 30, 40));
 
-    var builder = new codec.MessageBuilder(1, structs.NamedRegion.encodedSize);
+    var builder = new codec.MessageV0Builder(1, structs.NamedRegion.encodedSize);
     builder.encodeStruct(structs.NamedRegion, r);
     var reader = new codec.MessageReader(builder.finish());
     var result = reader.decodeStruct(structs.NamedRegion);
@@ -181,7 +181,7 @@
     var single_bool = new structs.SingleBoolStruct();
     single_bool.value = true;
 
-    var builder = new codec.MessageBuilder(
+    var builder = new codec.MessageV0Builder(
         1, structs.SingleBoolStruct.encodedSize);
     builder.encodeStruct(structs.SingleBoolStruct, single_bool);
     var reader = new codec.MessageReader(builder.finish());
@@ -195,7 +195,7 @@
       var messageName = 42;
       var payloadSize = encodedSize || cls.encodedSize;
 
-      var builder = new codec.MessageBuilder(messageName, payloadSize);
+      var builder = new codec.MessageV0Builder(messageName, payloadSize);
       builder.encodeStruct(cls, input)
       var message = builder.finish();
 
@@ -253,7 +253,7 @@
     var messageName = 42;
     var payloadSize = 24;
 
-    var builder = new codec.MessageBuilder(messageName, payloadSize);
+    var builder = new codec.MessageV0Builder(messageName, payloadSize);
     var encoder = builder.createEncoder(8);
     encoder.encodeStringPointer(str);
     var message = builder.finish();
@@ -276,7 +276,7 @@
   }, 'utf8');
 
   test(() => {
-    var encoder = new codec.MessageBuilder(42, 24).createEncoder(8);
+    var encoder = new codec.MessageV0Builder(42, 24).createEncoder(8);
     function DummyClass() {};
     var testCases = [
       // method, args, invalid examples, valid examples
@@ -294,7 +294,7 @@
       var invalidExamples = test[2];
       var validExamples = test[3];
 
-      var encoder = new codec.MessageBuilder(42, 24).createEncoder(8);
+      var encoder = new codec.MessageV0Builder(42, 24).createEncoder(8);
       invalidExamples.forEach(function(invalid) {
         assert_throws(null, function() {
           method.apply(encoder, baseArgs.concat(invalid));
@@ -302,7 +302,7 @@
       });
 
       validExamples.forEach(function(valid) {
-        var encoder = new codec.MessageBuilder(42, 24).createEncoder(8);
+        var encoder = new codec.MessageV0Builder(42, 24).createEncoder(8);
         method.apply(encoder, baseArgs.concat(valid));
       });
     });
diff --git a/third_party/WebKit/LayoutTests/mojo/struct.html b/third_party/WebKit/LayoutTests/mojo/struct.html
index 0e9c53ba..7a82e0c 100644
--- a/third_party/WebKit/LayoutTests/mojo/struct.html
+++ b/third_party/WebKit/LayoutTests/mojo/struct.html
@@ -100,7 +100,7 @@
 
   function structEncodeDecode(struct) {
     var structClass = struct.constructor;
-    var builder = new codec.MessageBuilder(1234, structClass.encodedSize);
+    var builder = new codec.MessageV0Builder(1234, structClass.encodedSize);
     builder.encodeStruct(structClass, struct);
     var message = builder.finish();
 
diff --git a/third_party/WebKit/LayoutTests/mojo/union.html b/third_party/WebKit/LayoutTests/mojo/union.html
index 96f2b7e..167bd365 100644
--- a/third_party/WebKit/LayoutTests/mojo/union.html
+++ b/third_party/WebKit/LayoutTests/mojo/union.html
@@ -47,7 +47,7 @@
 
   function structEncodeDecode(struct) {
     var structClass = struct.constructor;
-    var builder = new codec.MessageBuilder(1234, structClass.encodedSize);
+    var builder = new codec.MessageV0Builder(1234, structClass.encodedSize);
     builder.encodeStruct(structClass, struct);
 
     var message = builder.finish();
@@ -146,7 +146,7 @@
 
   function structValidate(struct) {
     var structClass = struct.constructor;
-    var builder = new codec.MessageBuilder(1234, structClass.encodedSize);
+    var builder = new codec.MessageV0Builder(1234, structClass.encodedSize);
     builder.encodeStruct(structClass, struct);
 
     var message = builder.finish();
diff --git a/third_party/WebKit/LayoutTests/vibration/vibration-expected.txt b/third_party/WebKit/LayoutTests/vibration/vibration-expected.txt
index e258033..2b6fbea 100644
--- a/third_party/WebKit/LayoutTests/vibration/vibration-expected.txt
+++ b/third_party/WebKit/LayoutTests/vibration/vibration-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE ERROR: line 301: Uncaught TypeError: Cannot read property 'then' of undefined
+CONSOLE ERROR: line 311: Uncaught TypeError: Cannot read property 'then' of undefined
 This is a testharness.js-based test.
 Harness Error. harness_status.status = 1 , harness_status.message = Uncaught TypeError: Cannot read property 'then' of undefined
 PASS VibrationManager Mojo bindings and mock interfaces are available to tests. 
diff --git a/third_party/WebKit/Source/core/dom/Node.cpp b/third_party/WebKit/Source/core/dom/Node.cpp
index e3cfde2..590efff 100644
--- a/third_party/WebKit/Source/core/dom/Node.cpp
+++ b/third_party/WebKit/Source/core/dom/Node.cpp
@@ -1871,7 +1871,7 @@
     }
   }
 
-  old_document.Markers().RemoveMarkers(this);
+  old_document.Markers().RemoveMarkersForNode(this);
   if (GetDocument().GetPage() &&
       GetDocument().GetPage() != old_document.GetPage()) {
     GetDocument().GetPage()->GetEventHandlerRegistry().DidMoveIntoPage(*this);
diff --git a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
index 5f47aa8..dfd39cf 100644
--- a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
@@ -45,18 +45,6 @@
 
 namespace blink {
 
-MarkerRemoverPredicate::MarkerRemoverPredicate(const Vector<String>& words)
-    : words_(words) {}
-
-bool MarkerRemoverPredicate::operator()(const DocumentMarker& document_marker,
-                                        const Text& text_node) const {
-  unsigned start = document_marker.StartOffset();
-  unsigned length = document_marker.EndOffset() - document_marker.StartOffset();
-
-  String marker_text = text_node.data().Substring(start, length);
-  return words_.Contains(marker_text);
-}
-
 namespace {
 
 DocumentMarker::MarkerTypeIndex MarkerTypeToMarkerIndex(
@@ -486,7 +474,7 @@
   SynchronousMutationObserver::Trace(visitor);
 }
 
-void DocumentMarkerController::RemoveMarkers(
+void DocumentMarkerController::RemoveMarkersForNode(
     Node* node,
     DocumentMarker::MarkerTypes marker_types) {
   if (!PossiblyHasMarkers(marker_types))
@@ -498,8 +486,8 @@
     RemoveMarkersFromList(iterator, marker_types);
 }
 
-void DocumentMarkerController::RemoveMarkers(
-    const MarkerRemoverPredicate& should_remove_marker) {
+void DocumentMarkerController::RemoveSpellingMarkersUnderWords(
+    const Vector<String>& words) {
   for (auto& node_markers : markers_) {
     const Node& node = *node_markers.key;
     if (!node.IsTextNode())  // MarkerRemoverPredicate requires a Text node.
@@ -511,8 +499,13 @@
         continue;
       bool removed_markers = false;
       for (size_t j = list->size(); j > 0; --j) {
-        if (should_remove_marker(*list->at(j - 1),
-                                 static_cast<const Text&>(node))) {
+        const DocumentMarker& marker = *list->at(j - 1);
+
+        const unsigned start = marker.StartOffset();
+        const unsigned length = marker.EndOffset() - marker.StartOffset();
+
+        const String marker_text = ToText(node).data().Substring(start, length);
+        if (words.Contains(marker_text)) {
           list->erase(j - 1);
           removed_markers = true;
         }
diff --git a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.h b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.h
index c14b01d..011d958 100644
--- a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.h
+++ b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.h
@@ -42,16 +42,6 @@
 
 class Node;
 class RenderedDocumentMarker;
-class Text;
-
-class MarkerRemoverPredicate final {
- public:
-  explicit MarkerRemoverPredicate(const Vector<String>& words);
-  bool operator()(const DocumentMarker&, const Text&) const;
-
- private:
-  Vector<String> words_;
-};
 
 class CORE_EXPORT DocumentMarkerController final
     : public GarbageCollected<DocumentMarkerController>,
@@ -80,10 +70,10 @@
   void RemoveMarkersInRange(const EphemeralRange&, DocumentMarker::MarkerTypes);
   void RemoveMarkers(
       DocumentMarker::MarkerTypes = DocumentMarker::AllMarkers());
-  void RemoveMarkers(
+  void RemoveMarkersForNode(
       Node*,
       DocumentMarker::MarkerTypes = DocumentMarker::AllMarkers());
-  void RemoveMarkers(const MarkerRemoverPredicate& should_remove_marker);
+  void RemoveSpellingMarkersUnderWords(const Vector<String>& words);
   void RepaintMarkers(
       DocumentMarker::MarkerTypes = DocumentMarker::AllMarkers());
   // Returns true if markers within a range are found.
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp b/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
index d2ae2ab..236d2bb 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
+++ b/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
@@ -788,8 +788,10 @@
   DocumentMarker::MarkerTypes marker_types(DocumentMarker::kSpelling);
   marker_types.Add(DocumentMarker::kGrammar);
   for (Node& node : NodeTraversal::InclusiveDescendantsOf(element)) {
-    if (elements_type == ElementsType::kAll || !HasEditableStyle(node))
-      GetFrame().GetDocument()->Markers().RemoveMarkers(&node, marker_types);
+    if (elements_type == ElementsType::kAll || !HasEditableStyle(node)) {
+      GetFrame().GetDocument()->Markers().RemoveMarkersForNode(&node,
+                                                               marker_types);
+    }
   }
 }
 
@@ -926,11 +928,9 @@
 
 void SpellChecker::RemoveSpellingMarkersUnderWords(
     const Vector<String>& words) {
-  MarkerRemoverPredicate remover_predicate(words);
-
   DocumentMarkerController& marker_controller =
       GetFrame().GetDocument()->Markers();
-  marker_controller.RemoveMarkers(remover_predicate);
+  marker_controller.RemoveSpellingMarkersUnderWords(words);
   marker_controller.RepaintMarkers();
 }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/ListWidget.js b/third_party/WebKit/Source/devtools/front_end/ui/ListWidget.js
index 6a3a19e7..2d1aa06 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/ListWidget.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/ListWidget.js
@@ -14,6 +14,7 @@
     this._delegate = delegate;
 
     this._list = this.contentElement.createChild('div', 'list');
+    this.element.tabIndex = -1;
 
     /** @type {?UI.ListWidget.Editor} */
     this._editor = null;
@@ -144,6 +145,7 @@
      */
     function onRemoveClicked() {
       var index = this._elements.indexOf(element);
+      this.element.focus();
       this._delegate.removeItemRequested(this._items[index], index);
     }
   }
@@ -176,6 +178,7 @@
       return;
 
     this._stopEditing();
+    this._focusRestorer = new UI.ElementFocusRestorer(this.element);
 
     this._list.classList.add('list-editing');
     this._editItem = item;
@@ -202,6 +205,8 @@
 
   _stopEditing() {
     this._list.classList.remove('list-editing');
+    if (this._focusRestorer)
+      this._focusRestorer.restore();
     if (this._editElement)
       this._editElement.classList.remove('hidden');
     if (this._editor && this._editor.element.parentElement)
diff --git a/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp b/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp
index 89531ff..31b094c8 100644
--- a/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp
+++ b/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp
@@ -426,6 +426,8 @@
   if (isContextLost())
     return;
 
+  DrawingBuffer::ScopedRGBEmulationForBlitFramebuffer emulation(
+      GetDrawingBuffer());
   ContextGL()->BlitFramebufferCHROMIUM(src_x0, src_y0, src_x1, src_y1, dst_x0,
                                        dst_y0, dst_x1, dst_y1, mask, filter);
 }
diff --git a/third_party/WebKit/Source/platform/fonts/skia/FontCacheSkia.cpp b/third_party/WebKit/Source/platform/fonts/skia/FontCacheSkia.cpp
index 68b5c2c..6d2ce53 100644
--- a/third_party/WebKit/Source/platform/fonts/skia/FontCacheSkia.cpp
+++ b/third_party/WebKit/Source/platform/fonts/skia/FontCacheSkia.cpp
@@ -243,7 +243,7 @@
 #if OS(WIN)
   if (sideloaded_fonts_) {
     HashMap<String, sk_sp<SkTypeface>>::iterator sideloaded_font =
-        sideloaded_fonts_->Find(name.Data());
+        sideloaded_fonts_->Find(name.data());
     if (sideloaded_font != sideloaded_fonts_->end())
       return sideloaded_font->value;
   }
diff --git a/third_party/WebKit/Source/platform/fonts/win/FontCacheSkiaWin.cpp b/third_party/WebKit/Source/platform/fonts/win/FontCacheSkiaWin.cpp
index be25c3a6..f4e3ff6 100644
--- a/third_party/WebKit/Source/platform/fonts/win/FontCacheSkiaWin.cpp
+++ b/third_party/WebKit/Source/platform/fonts/win/FontCacheSkiaWin.cpp
@@ -152,7 +152,7 @@
     CString family_name = font_description.Family().Family().Utf8();
 
     SkTypeface* typeface = font_manager_->matchFamilyStyleCharacter(
-        family_name.Data(), font_description.SkiaFontStyle(), &bcp47_locale,
+        family_name.data(), font_description.SkiaFontStyle(), &bcp47_locale,
         locale_count, character);
     if (typeface) {
       SkString skia_family;
@@ -391,7 +391,7 @@
 
   std::unique_ptr<FontPlatformData> result =
       WTF::WrapUnique(new FontPlatformData(
-          tf, name.Data(), font_size,
+          tf, name.data(), font_size,
           (font_description.Weight() >= kFontWeight600 && !tf->isBold()) ||
               font_description.IsSyntheticBold(),
           ((font_description.Style() == kFontStyleItalic ||
diff --git a/third_party/WebKit/Source/platform/fonts/win/FontFallbackWin.cpp b/third_party/WebKit/Source/platform/fonts/win/FontFallbackWin.cpp
index 1e54e49..7c933e0 100644
--- a/third_party/WebKit/Source/platform/fonts/win/FontFallbackWin.cpp
+++ b/third_party/WebKit/Source/platform/fonts/win/FontFallbackWin.cpp
@@ -50,7 +50,7 @@
                                  SkFontMgr* font_manager) {
   String family = font_name;
   sk_sp<SkTypeface> tf(
-      font_manager->matchFamilyStyle(family.Utf8().Data(), SkFontStyle()));
+      font_manager->matchFamilyStyle(family.Utf8().data(), SkFontStyle()));
   if (!tf)
     return false;
 
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
index 15555873..d4f3845 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
@@ -576,6 +576,19 @@
   return CreateColorBuffer(size_);
 }
 
+DrawingBuffer::ScopedRGBEmulationForBlitFramebuffer::
+    ScopedRGBEmulationForBlitFramebuffer(DrawingBuffer* drawing_buffer)
+    : drawing_buffer_(drawing_buffer) {
+  doing_work_ = drawing_buffer->SetupRGBEmulationForBlitFramebuffer();
+}
+
+DrawingBuffer::ScopedRGBEmulationForBlitFramebuffer::
+    ~ScopedRGBEmulationForBlitFramebuffer() {
+  if (doing_work_) {
+    drawing_buffer_->CleanupRGBEmulationForBlitFramebuffer();
+  }
+}
+
 DrawingBuffer::ColorBuffer::ColorBuffer(
     DrawingBuffer* drawing_buffer,
     const ColorBufferParameters& parameters,
@@ -599,6 +612,10 @@
   if (image_id) {
     gl->BindTexture(parameters.target, texture_id);
     gl->ReleaseTexImage2DCHROMIUM(parameters.target, image_id);
+    if (rgb_workaround_texture_id) {
+      gl->BindTexture(parameters.target, rgb_workaround_texture_id);
+      gl->ReleaseTexImage2DCHROMIUM(parameters.target, image_id);
+    }
     gl->DestroyImageCHROMIUM(image_id);
     switch (parameters.target) {
       case GL_TEXTURE_2D:
@@ -618,6 +635,7 @@
     gpu_memory_buffer.reset();
   }
   gl->DeleteTextures(1, &texture_id);
+  gl->DeleteTextures(1, &rgb_workaround_texture_id);
 }
 
 bool DrawingBuffer::Initialize(const IntSize& size, bool use_multisampling) {
@@ -1258,6 +1276,78 @@
   return GL_RGB8_OES;
 }
 
+bool DrawingBuffer::SetupRGBEmulationForBlitFramebuffer() {
+  // We only need to do this work if:
+  //  - The user has selected alpha:false and antialias:false
+  //  - We are using CHROMIUM_image with RGB emulation
+  // macOS is the only platform on which this is necessary.
+
+  if (want_alpha_channel_ || anti_aliasing_mode_ != kNone)
+    return false;
+
+  if (!(ShouldUseChromiumImage() &&
+        ContextProvider()->GetCapabilities().chromium_image_rgb_emulation))
+    return false;
+
+  if (!back_color_buffer_)
+    return false;
+
+  // If for some reason the back buffer doesn't have a CHROMIUM_image,
+  // don't proceed with this workaround.
+  if (!back_color_buffer_->image_id)
+    return false;
+
+  // Before allowing the BlitFramebuffer call to go through, it's necessary
+  // to swap out the RGBA texture that's bound to the CHROMIUM_image
+  // instance with an RGB texture. BlitFramebuffer requires the internal
+  // formats of the source and destination to match when doing a
+  // multisample resolve, and the best way to achieve this without adding
+  // more full-screen blits is to hook up a true RGB texture to the
+  // underlying IOSurface. Unfortunately, on macOS, this rendering path
+  // destroys the alpha channel and requires a fixup afterward, which is
+  // why it isn't used all the time.
+
+  GLuint rgb_texture = back_color_buffer_->rgb_workaround_texture_id;
+  GLenum target = GC3D_TEXTURE_RECTANGLE_ARB;
+  if (!rgb_texture) {
+    gl_->GenTextures(1, &rgb_texture);
+    gl_->BindTexture(target, rgb_texture);
+    gl_->TexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    gl_->TexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    gl_->TexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    gl_->TexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+    // Bind this texture to the CHROMIUM_image instance that the color
+    // buffer owns. This is an expensive operation, so it's important that
+    // the result be cached.
+    gl_->BindTexImage2DWithInternalformatCHROMIUM(target, GL_RGB,
+                                                  back_color_buffer_->image_id);
+    back_color_buffer_->rgb_workaround_texture_id = rgb_texture;
+  }
+
+  gl_->FramebufferTexture2D(GL_DRAW_FRAMEBUFFER_ANGLE, GL_COLOR_ATTACHMENT0,
+                            target, rgb_texture, 0);
+  return true;
+}
+
+void DrawingBuffer::CleanupRGBEmulationForBlitFramebuffer() {
+  // This will only be called if SetupRGBEmulationForBlitFramebuffer was.
+  // Put the framebuffer back the way it was, and clear the alpha channel.
+  DCHECK(back_color_buffer_);
+  DCHECK(back_color_buffer_->image_id);
+  GLenum target = GC3D_TEXTURE_RECTANGLE_ARB;
+  gl_->FramebufferTexture2D(GL_DRAW_FRAMEBUFFER_ANGLE, GL_COLOR_ATTACHMENT0,
+                            target, back_color_buffer_->texture_id, 0);
+  // Clear the alpha channel.
+  gl_->ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
+  gl_->Disable(GL_SCISSOR_TEST);
+  gl_->ClearColor(0, 0, 0, 1);
+  gl_->Clear(GL_COLOR_BUFFER_BIT);
+  DCHECK(client_);
+  client_->DrawingBufferClientRestoreScissorTest();
+  client_->DrawingBufferClientRestoreMaskAndClearValues();
+}
+
 DrawingBuffer::ScopedStateRestorer::ScopedStateRestorer(
     DrawingBuffer* drawing_buffer)
     : drawing_buffer_(drawing_buffer) {
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h
index 56f1eea..b99c5e7 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h
@@ -232,6 +232,19 @@
     new_mailbox_callback_ = std::move(closure);
   }
 
+  // This class helps implement correct semantics for BlitFramebuffer
+  // when the DrawingBuffer is using a CHROMIUM image for its backing
+  // store and RGB emulation is in use (basically, macOS only).
+  class PLATFORM_EXPORT ScopedRGBEmulationForBlitFramebuffer {
+   public:
+    ScopedRGBEmulationForBlitFramebuffer(DrawingBuffer*);
+    ~ScopedRGBEmulationForBlitFramebuffer();
+
+   private:
+    RefPtr<DrawingBuffer> drawing_buffer_;
+    bool doing_work_ = false;
+  };
+
  protected:  // For unittests
   DrawingBuffer(std::unique_ptr<WebGraphicsContext3DProvider>,
                 std::unique_ptr<Extensions3DUtil>,
@@ -257,6 +270,7 @@
   Vector<RecycledBitmap> recycled_bitmaps_;
 
  private:
+  friend class ScopedRGBEmulationForBlitFramebuffer;
   friend class ScopedStateRestorer;
   friend class ColorBuffer;
 
@@ -317,6 +331,16 @@
     const GLuint image_id = 0;
     std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer;
 
+    // If we're emulating an RGB back buffer using an RGBA Chromium
+    // image (essentially macOS only), then when performing
+    // BlitFramebuffer calls, we have to swap in an RGB texture in
+    // place of the RGBA texture bound to the image. The reason is
+    // that BlitFramebuffer requires the internal formats of the
+    // source and destination to match (e.g. RGB8 on both sides).
+    // There are bugs in the semantics of RGB8 textures in this
+    // situation (the alpha channel is zeroed), requiring more fixups.
+    GLuint rgb_workaround_texture_id = 0;
+
     // The mailbox used to send this buffer to the compositor.
     gpu::Mailbox mailbox;
 
@@ -428,6 +452,11 @@
   // The format to use when creating a multisampled renderbuffer.
   GLenum GetMultisampledRenderbufferFormat();
 
+  // Helpers to ensure correct behavior of BlitFramebuffer when using
+  // an emulated RGB CHROMIUM_image back buffer.
+  bool SetupRGBEmulationForBlitFramebuffer();
+  void CleanupRGBEmulationForBlitFramebuffer();
+
   // Weak, reset by beginDestruction.
   Client* client_ = nullptr;
 
@@ -448,9 +477,9 @@
 
   std::unique_ptr<WTF::Closure> new_mailbox_callback_;
 
-  // The current state restorer, which is used to track state dirtying. It is in
+  // The current state restorer, which is used to track state dirtying. It is an
   // error to dirty state shared with WebGL while there is no existing state
-  // restorer. It is also in error to instantiate two state restorers at once.
+  // restorer.
   ScopedStateRestorer* state_restorer_ = nullptr;
 
   // This is used when the user requests either a depth or stencil buffer.
diff --git a/third_party/WebKit/Source/platform/text/HyphenationTest.cpp b/third_party/WebKit/Source/platform/text/HyphenationTest.cpp
index f60bbf3..533c8e6 100644
--- a/third_party/WebKit/Source/platform/text/HyphenationTest.cpp
+++ b/third_party/WebKit/Source/platform/text/HyphenationTest.cpp
@@ -73,7 +73,7 @@
     if (location_index > 0 && location == locations[location_index - 1])
       location_index--;
     EXPECT_EQ(locations[location_index], location) << String::Format(
-        "lastHyphenLocation(%s, %zd)", word.Utf8().Data(), before_index);
+        "lastHyphenLocation(%s, %zd)", word.Utf8().data(), before_index);
   }
 
   EXPECT_EQ(location_index, 0u)
diff --git a/third_party/WebKit/Source/platform/text/LocaleMacTest.cpp b/third_party/WebKit/Source/platform/text/LocaleMacTest.cpp
index 9091a75..e90c7ac 100644
--- a/third_party/WebKit/Source/platform/text/LocaleMacTest.cpp
+++ b/third_party/WebKit/Source/platform/text/LocaleMacTest.cpp
@@ -193,74 +193,74 @@
 
 TEST_F(LocaleMacTest, formatWeek) {
   ScopedTestingPlatformSupport<LocalePlatformSupport> support;
-  EXPECT_STREQ("Week 04, 2005", FormatWeek("en_US", "2005-W04").Utf8().Data());
-  EXPECT_STREQ("Week 52, 2005", FormatWeek("en_US", "2005-W52").Utf8().Data());
+  EXPECT_STREQ("Week 04, 2005", FormatWeek("en_US", "2005-W04").Utf8().data());
+  EXPECT_STREQ("Week 52, 2005", FormatWeek("en_US", "2005-W52").Utf8().data());
 }
 
 TEST_F(LocaleMacTest, formatMonth) {
   EXPECT_STREQ("April 2005",
-               FormatMonth("en_US", "2005-04", false).Utf8().Data());
+               FormatMonth("en_US", "2005-04", false).Utf8().data());
   EXPECT_STREQ("avril 2005",
-               FormatMonth("fr_FR", "2005-04", false).Utf8().Data());
+               FormatMonth("fr_FR", "2005-04", false).Utf8().data());
   EXPECT_STREQ(
       "2005\xE5\xB9\xB4"
       "04\xE6\x9C\x88",
-      FormatMonth("ja_JP", "2005-04", false).Utf8().Data());
+      FormatMonth("ja_JP", "2005-04", false).Utf8().data());
 
-  EXPECT_STREQ("Apr 2005", FormatMonth("en_US", "2005-04", true).Utf8().Data());
+  EXPECT_STREQ("Apr 2005", FormatMonth("en_US", "2005-04", true).Utf8().data());
   EXPECT_STREQ("avr. 2005",
-               FormatMonth("fr_FR", "2005-04", true).Utf8().Data());
+               FormatMonth("fr_FR", "2005-04", true).Utf8().data());
   EXPECT_STREQ(
       "2005\xE5\xB9\xB4"
       "04\xE6\x9C\x88",
-      FormatMonth("ja_JP", "2005-04", true).Utf8().Data());
+      FormatMonth("ja_JP", "2005-04", true).Utf8().data());
 }
 
 TEST_F(LocaleMacTest, formatDate) {
   EXPECT_STREQ("04/27/2005",
-               FormatDate("en_US", 2005, kApril, 27).Utf8().Data());
+               FormatDate("en_US", 2005, kApril, 27).Utf8().data());
   EXPECT_STREQ("27/04/2005",
-               FormatDate("fr_FR", 2005, kApril, 27).Utf8().Data());
+               FormatDate("fr_FR", 2005, kApril, 27).Utf8().data());
   // Do not test ja_JP locale. OS X 10.8 and 10.7 have different formats.
 }
 
 TEST_F(LocaleMacTest, formatTime) {
   EXPECT_STREQ("1:23 PM",
-               FormatTime("en_US", 13, 23, 00, 000, true).Utf8().Data());
+               FormatTime("en_US", 13, 23, 00, 000, true).Utf8().data());
   EXPECT_STREQ("13:23",
-               FormatTime("fr_FR", 13, 23, 00, 000, true).Utf8().Data());
+               FormatTime("fr_FR", 13, 23, 00, 000, true).Utf8().data());
   EXPECT_STREQ("13:23",
-               FormatTime("ja_JP", 13, 23, 00, 000, true).Utf8().Data());
+               FormatTime("ja_JP", 13, 23, 00, 000, true).Utf8().data());
   EXPECT_STREQ("\xD9\xA1:\xD9\xA2\xD9\xA3 \xD9\x85",
-               FormatTime("ar", 13, 23, 00, 000, true).Utf8().Data());
+               FormatTime("ar", 13, 23, 00, 000, true).Utf8().data());
   EXPECT_STREQ("\xDB\xB1\xDB\xB3:\xDB\xB2\xDB\xB3",
-               FormatTime("fa", 13, 23, 00, 000, true).Utf8().Data());
+               FormatTime("fa", 13, 23, 00, 000, true).Utf8().data());
 
   EXPECT_STREQ("12:00 AM",
-               FormatTime("en_US", 00, 00, 00, 000, true).Utf8().Data());
+               FormatTime("en_US", 00, 00, 00, 000, true).Utf8().data());
   EXPECT_STREQ("00:00",
-               FormatTime("fr_FR", 00, 00, 00, 000, true).Utf8().Data());
+               FormatTime("fr_FR", 00, 00, 00, 000, true).Utf8().data());
   EXPECT_STREQ("0:00",
-               FormatTime("ja_JP", 00, 00, 00, 000, true).Utf8().Data());
+               FormatTime("ja_JP", 00, 00, 00, 000, true).Utf8().data());
   EXPECT_STREQ("\xD9\xA1\xD9\xA2:\xD9\xA0\xD9\xA0 \xD8\xB5",
-               FormatTime("ar", 00, 00, 00, 000, true).Utf8().Data());
+               FormatTime("ar", 00, 00, 00, 000, true).Utf8().data());
   EXPECT_STREQ("\xDB\xB0:\xDB\xB0\xDB\xB0",
-               FormatTime("fa", 00, 00, 00, 000, true).Utf8().Data());
+               FormatTime("fa", 00, 00, 00, 000, true).Utf8().data());
 
   EXPECT_STREQ("7:07:07.007 AM",
-               FormatTime("en_US", 07, 07, 07, 007, false).Utf8().Data());
+               FormatTime("en_US", 07, 07, 07, 007, false).Utf8().data());
   EXPECT_STREQ("07:07:07,007",
-               FormatTime("fr_FR", 07, 07, 07, 007, false).Utf8().Data());
+               FormatTime("fr_FR", 07, 07, 07, 007, false).Utf8().data());
   EXPECT_STREQ("7:07:07.007",
-               FormatTime("ja_JP", 07, 07, 07, 007, false).Utf8().Data());
+               FormatTime("ja_JP", 07, 07, 07, 007, false).Utf8().data());
   EXPECT_STREQ(
       "\xD9\xA7:\xD9\xA0\xD9\xA7:"
       "\xD9\xA0\xD9\xA7\xD9\xAB\xD9\xA0\xD9\xA0\xD9\xA7 \xD8\xB5",
-      FormatTime("ar", 07, 07, 07, 007, false).Utf8().Data());
+      FormatTime("ar", 07, 07, 07, 007, false).Utf8().data());
   EXPECT_STREQ(
       "\xDB\xB7:\xDB\xB0\xDB\xB7:"
       "\xDB\xB0\xDB\xB7\xD9\xAB\xDB\xB0\xDB\xB0\xDB\xB7",
-      FormatTime("fa", 07, 07, 07, 007, false).Utf8().Data());
+      FormatTime("fa", 07, 07, 07, 007, false).Utf8().data());
 }
 
 TEST_F(LocaleMacTest, firstDayOfWeek) {
@@ -270,37 +270,37 @@
 }
 
 TEST_F(LocaleMacTest, monthLabels) {
-  EXPECT_STREQ("January", MonthLabel("en_US", kJanuary).Utf8().Data());
-  EXPECT_STREQ("June", MonthLabel("en_US", kJune).Utf8().Data());
-  EXPECT_STREQ("December", MonthLabel("en_US", kDecember).Utf8().Data());
+  EXPECT_STREQ("January", MonthLabel("en_US", kJanuary).Utf8().data());
+  EXPECT_STREQ("June", MonthLabel("en_US", kJune).Utf8().data());
+  EXPECT_STREQ("December", MonthLabel("en_US", kDecember).Utf8().data());
 
-  EXPECT_STREQ("janvier", MonthLabel("fr_FR", kJanuary).Utf8().Data());
-  EXPECT_STREQ("juin", MonthLabel("fr_FR", kJune).Utf8().Data());
+  EXPECT_STREQ("janvier", MonthLabel("fr_FR", kJanuary).Utf8().data());
+  EXPECT_STREQ("juin", MonthLabel("fr_FR", kJune).Utf8().data());
   EXPECT_STREQ(
       "d\xC3\xA9"
       "cembre",
-      MonthLabel("fr_FR", kDecember).Utf8().Data());
+      MonthLabel("fr_FR", kDecember).Utf8().data());
 
-  EXPECT_STREQ("1\xE6\x9C\x88", MonthLabel("ja_JP", kJanuary).Utf8().Data());
-  EXPECT_STREQ("6\xE6\x9C\x88", MonthLabel("ja_JP", kJune).Utf8().Data());
-  EXPECT_STREQ("12\xE6\x9C\x88", MonthLabel("ja_JP", kDecember).Utf8().Data());
+  EXPECT_STREQ("1\xE6\x9C\x88", MonthLabel("ja_JP", kJanuary).Utf8().data());
+  EXPECT_STREQ("6\xE6\x9C\x88", MonthLabel("ja_JP", kJune).Utf8().data());
+  EXPECT_STREQ("12\xE6\x9C\x88", MonthLabel("ja_JP", kDecember).Utf8().data());
 }
 
 TEST_F(LocaleMacTest, weekDayShortLabels) {
-  EXPECT_STREQ("Sun", WeekDayShortLabel("en_US", kSunday).Utf8().Data());
-  EXPECT_STREQ("Wed", WeekDayShortLabel("en_US", kWednesday).Utf8().Data());
-  EXPECT_STREQ("Sat", WeekDayShortLabel("en_US", kSaturday).Utf8().Data());
+  EXPECT_STREQ("Sun", WeekDayShortLabel("en_US", kSunday).Utf8().data());
+  EXPECT_STREQ("Wed", WeekDayShortLabel("en_US", kWednesday).Utf8().data());
+  EXPECT_STREQ("Sat", WeekDayShortLabel("en_US", kSaturday).Utf8().data());
 
-  EXPECT_STREQ("dim.", WeekDayShortLabel("fr_FR", kSunday).Utf8().Data());
-  EXPECT_STREQ("mer.", WeekDayShortLabel("fr_FR", kWednesday).Utf8().Data());
-  EXPECT_STREQ("sam.", WeekDayShortLabel("fr_FR", kSaturday).Utf8().Data());
+  EXPECT_STREQ("dim.", WeekDayShortLabel("fr_FR", kSunday).Utf8().data());
+  EXPECT_STREQ("mer.", WeekDayShortLabel("fr_FR", kWednesday).Utf8().data());
+  EXPECT_STREQ("sam.", WeekDayShortLabel("fr_FR", kSaturday).Utf8().data());
 
   EXPECT_STREQ("\xE6\x97\xA5",
-               WeekDayShortLabel("ja_JP", kSunday).Utf8().Data());
+               WeekDayShortLabel("ja_JP", kSunday).Utf8().data());
   EXPECT_STREQ("\xE6\xB0\xB4",
-               WeekDayShortLabel("ja_JP", kWednesday).Utf8().Data());
+               WeekDayShortLabel("ja_JP", kWednesday).Utf8().data());
   EXPECT_STREQ("\xE5\x9C\x9F",
-               WeekDayShortLabel("ja_JP", kSaturday).Utf8().Data());
+               WeekDayShortLabel("ja_JP", kSaturday).Utf8().data());
 }
 
 TEST_F(LocaleMacTest, isRTL) {
@@ -311,9 +311,9 @@
 }
 
 TEST_F(LocaleMacTest, monthFormat) {
-  EXPECT_STREQ("MMMM yyyy", MonthFormat("en_US").Utf8().Data());
+  EXPECT_STREQ("MMMM yyyy", MonthFormat("en_US").Utf8().data());
   EXPECT_STREQ("yyyy\xE5\xB9\xB4M\xE6\x9C\x88",
-               MonthFormat("ja_JP").Utf8().Data());
+               MonthFormat("ja_JP").Utf8().data());
 
   // fr_FR and ru return different results on OS versions.
   //  "MMM yyyy" "LLL yyyy" on 10.6 and 10.7
@@ -321,96 +321,96 @@
 }
 
 TEST_F(LocaleMacTest, timeFormat) {
-  EXPECT_STREQ("h:mm:ss a", TimeFormat("en_US").Utf8().Data());
-  EXPECT_STREQ("HH:mm:ss", TimeFormat("fr_FR").Utf8().Data());
-  EXPECT_STREQ("H:mm:ss", TimeFormat("ja_JP").Utf8().Data());
+  EXPECT_STREQ("h:mm:ss a", TimeFormat("en_US").Utf8().data());
+  EXPECT_STREQ("HH:mm:ss", TimeFormat("fr_FR").Utf8().data());
+  EXPECT_STREQ("H:mm:ss", TimeFormat("ja_JP").Utf8().data());
 }
 
 TEST_F(LocaleMacTest, shortTimeFormat) {
-  EXPECT_STREQ("h:mm a", ShortTimeFormat("en_US").Utf8().Data());
-  EXPECT_STREQ("HH:mm", ShortTimeFormat("fr_FR").Utf8().Data());
-  EXPECT_STREQ("H:mm", ShortTimeFormat("ja_JP").Utf8().Data());
+  EXPECT_STREQ("h:mm a", ShortTimeFormat("en_US").Utf8().data());
+  EXPECT_STREQ("HH:mm", ShortTimeFormat("fr_FR").Utf8().data());
+  EXPECT_STREQ("H:mm", ShortTimeFormat("ja_JP").Utf8().data());
 }
 
 TEST_F(LocaleMacTest, standAloneMonthLabels) {
   EXPECT_STREQ("January",
-               StandAloneMonthLabel("en_US", kJanuary).Utf8().Data());
-  EXPECT_STREQ("June", StandAloneMonthLabel("en_US", kJune).Utf8().Data());
+               StandAloneMonthLabel("en_US", kJanuary).Utf8().data());
+  EXPECT_STREQ("June", StandAloneMonthLabel("en_US", kJune).Utf8().data());
   EXPECT_STREQ("December",
-               StandAloneMonthLabel("en_US", kDecember).Utf8().Data());
+               StandAloneMonthLabel("en_US", kDecember).Utf8().data());
 
   EXPECT_STREQ("janvier",
-               StandAloneMonthLabel("fr_FR", kJanuary).Utf8().Data());
-  EXPECT_STREQ("juin", StandAloneMonthLabel("fr_FR", kJune).Utf8().Data());
+               StandAloneMonthLabel("fr_FR", kJanuary).Utf8().data());
+  EXPECT_STREQ("juin", StandAloneMonthLabel("fr_FR", kJune).Utf8().data());
   EXPECT_STREQ(
       "d\xC3\xA9"
       "cembre",
-      StandAloneMonthLabel("fr_FR", kDecember).Utf8().Data());
+      StandAloneMonthLabel("fr_FR", kDecember).Utf8().data());
 
   EXPECT_STREQ("1\xE6\x9C\x88",
-               StandAloneMonthLabel("ja_JP", kJanuary).Utf8().Data());
+               StandAloneMonthLabel("ja_JP", kJanuary).Utf8().data());
   EXPECT_STREQ("6\xE6\x9C\x88",
-               StandAloneMonthLabel("ja_JP", kJune).Utf8().Data());
+               StandAloneMonthLabel("ja_JP", kJune).Utf8().data());
   EXPECT_STREQ("12\xE6\x9C\x88",
-               StandAloneMonthLabel("ja_JP", kDecember).Utf8().Data());
+               StandAloneMonthLabel("ja_JP", kDecember).Utf8().data());
 }
 
 TEST_F(LocaleMacTest, shortMonthLabels) {
-  EXPECT_STREQ("Jan", ShortMonthLabel("en_US", 0).Utf8().Data());
-  EXPECT_STREQ("Jan", ShortStandAloneMonthLabel("en_US", 0).Utf8().Data());
-  EXPECT_STREQ("Dec", ShortMonthLabel("en_US", 11).Utf8().Data());
-  EXPECT_STREQ("Dec", ShortStandAloneMonthLabel("en_US", 11).Utf8().Data());
+  EXPECT_STREQ("Jan", ShortMonthLabel("en_US", 0).Utf8().data());
+  EXPECT_STREQ("Jan", ShortStandAloneMonthLabel("en_US", 0).Utf8().data());
+  EXPECT_STREQ("Dec", ShortMonthLabel("en_US", 11).Utf8().data());
+  EXPECT_STREQ("Dec", ShortStandAloneMonthLabel("en_US", 11).Utf8().data());
 
-  EXPECT_STREQ("janv.", ShortMonthLabel("fr_FR", 0).Utf8().Data());
-  EXPECT_STREQ("janv.", ShortStandAloneMonthLabel("fr_FR", 0).Utf8().Data());
+  EXPECT_STREQ("janv.", ShortMonthLabel("fr_FR", 0).Utf8().data());
+  EXPECT_STREQ("janv.", ShortStandAloneMonthLabel("fr_FR", 0).Utf8().data());
   EXPECT_STREQ(
       "d\xC3\xA9"
       "c.",
-      ShortMonthLabel("fr_FR", 11).Utf8().Data());
+      ShortMonthLabel("fr_FR", 11).Utf8().data());
   EXPECT_STREQ(
       "d\xC3\xA9"
       "c.",
-      ShortStandAloneMonthLabel("fr_FR", 11).Utf8().Data());
+      ShortStandAloneMonthLabel("fr_FR", 11).Utf8().data());
 
-  EXPECT_STREQ("1\xE6\x9C\x88", ShortMonthLabel("ja_JP", 0).Utf8().Data());
+  EXPECT_STREQ("1\xE6\x9C\x88", ShortMonthLabel("ja_JP", 0).Utf8().data());
   EXPECT_STREQ("1\xE6\x9C\x88",
-               ShortStandAloneMonthLabel("ja_JP", 0).Utf8().Data());
-  EXPECT_STREQ("12\xE6\x9C\x88", ShortMonthLabel("ja_JP", 11).Utf8().Data());
+               ShortStandAloneMonthLabel("ja_JP", 0).Utf8().data());
+  EXPECT_STREQ("12\xE6\x9C\x88", ShortMonthLabel("ja_JP", 11).Utf8().data());
   EXPECT_STREQ("12\xE6\x9C\x88",
-               ShortStandAloneMonthLabel("ja_JP", 11).Utf8().Data());
+               ShortStandAloneMonthLabel("ja_JP", 11).Utf8().data());
 
   EXPECT_STREQ("\xD0\xBC\xD0\xB0\xD1\x80\xD1\x82\xD0\xB0",
-               ShortMonthLabel("ru_RU", 2).Utf8().Data());
+               ShortMonthLabel("ru_RU", 2).Utf8().data());
   EXPECT_STREQ("\xD0\xBC\xD0\xB0\xD1\x8F",
-               ShortMonthLabel("ru_RU", 4).Utf8().Data());
+               ShortMonthLabel("ru_RU", 4).Utf8().data());
   // The ru_RU locale returns different stand-alone month labels on OS versions.
   //  "\xD0\xBC\xD0\xB0\xD1\x80\xD1\x82" "\xD0\xBC\xD0\xB0\xD0\xB9" on 10.7
   //  "\xD0\x9C\xD0\xB0\xD1\x80\xD1\x82" "\xD0\x9C\xD0\xB0\xD0\xB9" on 10.8
 }
 
 TEST_F(LocaleMacTest, timeAMPMLabels) {
-  EXPECT_STREQ("AM", TimeAMPMLabel("en_US", 0).Utf8().Data());
-  EXPECT_STREQ("PM", TimeAMPMLabel("en_US", 1).Utf8().Data());
+  EXPECT_STREQ("AM", TimeAMPMLabel("en_US", 0).Utf8().data());
+  EXPECT_STREQ("PM", TimeAMPMLabel("en_US", 1).Utf8().data());
 
-  EXPECT_STREQ("AM", TimeAMPMLabel("fr_FR", 0).Utf8().Data());
-  EXPECT_STREQ("PM", TimeAMPMLabel("fr_FR", 1).Utf8().Data());
+  EXPECT_STREQ("AM", TimeAMPMLabel("fr_FR", 0).Utf8().data());
+  EXPECT_STREQ("PM", TimeAMPMLabel("fr_FR", 1).Utf8().data());
 
   EXPECT_STREQ("\xE5\x8D\x88\xE5\x89\x8D",
-               TimeAMPMLabel("ja_JP", 0).Utf8().Data());
+               TimeAMPMLabel("ja_JP", 0).Utf8().data());
   EXPECT_STREQ("\xE5\x8D\x88\xE5\xBE\x8C",
-               TimeAMPMLabel("ja_JP", 1).Utf8().Data());
+               TimeAMPMLabel("ja_JP", 1).Utf8().data());
 }
 
 TEST_F(LocaleMacTest, decimalSeparator) {
-  EXPECT_STREQ(".", DecimalSeparator("en_US").Utf8().Data());
-  EXPECT_STREQ(",", DecimalSeparator("fr_FR").Utf8().Data());
+  EXPECT_STREQ(".", DecimalSeparator("en_US").Utf8().data());
+  EXPECT_STREQ(",", DecimalSeparator("fr_FR").Utf8().data());
 }
 
 TEST_F(LocaleMacTest, invalidLocale) {
-  EXPECT_STREQ(MonthLabel("en_US", kJanuary).Utf8().Data(),
-               MonthLabel("foo", kJanuary).Utf8().Data());
-  EXPECT_STREQ(DecimalSeparator("en_US").Utf8().Data(),
-               DecimalSeparator("foo").Utf8().Data());
+  EXPECT_STREQ(MonthLabel("en_US", kJanuary).Utf8().data(),
+               MonthLabel("foo", kJanuary).Utf8().data());
+  EXPECT_STREQ(DecimalSeparator("en_US").Utf8().data(),
+               DecimalSeparator("foo").Utf8().data());
 }
 
 static void TestNumberIsReversible(const AtomicString& locale_string,
@@ -421,7 +421,7 @@
   if (should_have)
     EXPECT_TRUE(localized.Contains(should_have));
   String converted = locale->ConvertFromLocalizedNumber(localized);
-  EXPECT_STREQ(original, converted.Utf8().Data());
+  EXPECT_STREQ(original, converted.Utf8().data());
 }
 
 void TestNumbers(const AtomicString& locale_string,
diff --git a/third_party/WebKit/Source/platform/text/LocaleWinTest.cpp b/third_party/WebKit/Source/platform/text/LocaleWinTest.cpp
index 460c534..0b4685f2 100644
--- a/third_party/WebKit/Source/platform/text/LocaleWinTest.cpp
+++ b/third_party/WebKit/Source/platform/text/LocaleWinTest.cpp
@@ -161,11 +161,11 @@
 
 TEST_F(LocaleWinTest, formatDate) {
   EXPECT_STREQ("04/27/2005",
-               FormatDate(kEnglishUS, 2005, kApril, 27).Utf8().Data());
+               FormatDate(kEnglishUS, 2005, kApril, 27).Utf8().data());
   EXPECT_STREQ("27/04/2005",
-               FormatDate(kFrenchFR, 2005, kApril, 27).Utf8().Data());
+               FormatDate(kFrenchFR, 2005, kApril, 27).Utf8().data());
   EXPECT_STREQ("2005/04/27",
-               FormatDate(kJapaneseJP, 2005, kApril, 27).Utf8().Data());
+               FormatDate(kJapaneseJP, 2005, kApril, 27).Utf8().data());
 }
 
 TEST_F(LocaleWinTest, firstDayOfWeek) {
@@ -175,39 +175,39 @@
 }
 
 TEST_F(LocaleWinTest, monthLabels) {
-  EXPECT_STREQ("January", MonthLabel(kEnglishUS, kJanuary).Utf8().Data());
-  EXPECT_STREQ("June", MonthLabel(kEnglishUS, kJune).Utf8().Data());
-  EXPECT_STREQ("December", MonthLabel(kEnglishUS, kDecember).Utf8().Data());
+  EXPECT_STREQ("January", MonthLabel(kEnglishUS, kJanuary).Utf8().data());
+  EXPECT_STREQ("June", MonthLabel(kEnglishUS, kJune).Utf8().data());
+  EXPECT_STREQ("December", MonthLabel(kEnglishUS, kDecember).Utf8().data());
 
-  EXPECT_STREQ("janvier", MonthLabel(kFrenchFR, kJanuary).Utf8().Data());
-  EXPECT_STREQ("juin", MonthLabel(kFrenchFR, kJune).Utf8().Data());
+  EXPECT_STREQ("janvier", MonthLabel(kFrenchFR, kJanuary).Utf8().data());
+  EXPECT_STREQ("juin", MonthLabel(kFrenchFR, kJune).Utf8().data());
   EXPECT_STREQ(
       "d\xC3\xA9"
       "cembre",
-      MonthLabel(kFrenchFR, kDecember).Utf8().Data());
+      MonthLabel(kFrenchFR, kDecember).Utf8().data());
 
   EXPECT_STREQ("1\xE6\x9C\x88",
-               MonthLabel(kJapaneseJP, kJanuary).Utf8().Data());
-  EXPECT_STREQ("6\xE6\x9C\x88", MonthLabel(kJapaneseJP, kJune).Utf8().Data());
+               MonthLabel(kJapaneseJP, kJanuary).Utf8().data());
+  EXPECT_STREQ("6\xE6\x9C\x88", MonthLabel(kJapaneseJP, kJune).Utf8().data());
   EXPECT_STREQ("12\xE6\x9C\x88",
-               MonthLabel(kJapaneseJP, kDecember).Utf8().Data());
+               MonthLabel(kJapaneseJP, kDecember).Utf8().data());
 }
 
 TEST_F(LocaleWinTest, weekDayShortLabels) {
-  EXPECT_STREQ("Sun", WeekDayShortLabel(kEnglishUS, kSunday).Utf8().Data());
-  EXPECT_STREQ("Wed", WeekDayShortLabel(kEnglishUS, kWednesday).Utf8().Data());
-  EXPECT_STREQ("Sat", WeekDayShortLabel(kEnglishUS, kSaturday).Utf8().Data());
+  EXPECT_STREQ("Sun", WeekDayShortLabel(kEnglishUS, kSunday).Utf8().data());
+  EXPECT_STREQ("Wed", WeekDayShortLabel(kEnglishUS, kWednesday).Utf8().data());
+  EXPECT_STREQ("Sat", WeekDayShortLabel(kEnglishUS, kSaturday).Utf8().data());
 
-  EXPECT_STREQ("dim.", WeekDayShortLabel(kFrenchFR, kSunday).Utf8().Data());
-  EXPECT_STREQ("mer.", WeekDayShortLabel(kFrenchFR, kWednesday).Utf8().Data());
-  EXPECT_STREQ("sam.", WeekDayShortLabel(kFrenchFR, kSaturday).Utf8().Data());
+  EXPECT_STREQ("dim.", WeekDayShortLabel(kFrenchFR, kSunday).Utf8().data());
+  EXPECT_STREQ("mer.", WeekDayShortLabel(kFrenchFR, kWednesday).Utf8().data());
+  EXPECT_STREQ("sam.", WeekDayShortLabel(kFrenchFR, kSaturday).Utf8().data());
 
   EXPECT_STREQ("\xE6\x97\xA5",
-               WeekDayShortLabel(kJapaneseJP, kSunday).Utf8().Data());
+               WeekDayShortLabel(kJapaneseJP, kSunday).Utf8().data());
   EXPECT_STREQ("\xE6\xB0\xB4",
-               WeekDayShortLabel(kJapaneseJP, kWednesday).Utf8().Data());
+               WeekDayShortLabel(kJapaneseJP, kWednesday).Utf8().data());
   EXPECT_STREQ("\xE5\x9C\x9F",
-               WeekDayShortLabel(kJapaneseJP, kSaturday).Utf8().Data());
+               WeekDayShortLabel(kJapaneseJP, kSaturday).Utf8().data());
 }
 
 TEST_F(LocaleWinTest, isRTL) {
@@ -216,13 +216,13 @@
 }
 
 TEST_F(LocaleWinTest, dateFormat) {
-  EXPECT_STREQ("y-M-d", LocaleWin::DateFormat("y-M-d").Utf8().Data());
+  EXPECT_STREQ("y-M-d", LocaleWin::DateFormat("y-M-d").Utf8().data());
   EXPECT_STREQ("''yy'-'''MM'''-'dd",
-               LocaleWin::DateFormat("''yy-''MM''-dd").Utf8().Data());
+               LocaleWin::DateFormat("''yy-''MM''-dd").Utf8().data());
   EXPECT_STREQ("yyyy'-''''-'MMM'''''-'dd",
-               LocaleWin::DateFormat("yyyy-''''-MMM''''-dd").Utf8().Data());
+               LocaleWin::DateFormat("yyyy-''''-MMM''''-dd").Utf8().data());
   EXPECT_STREQ("yyyy'-'''''MMMM-dd",
-               LocaleWin::DateFormat("yyyy-''''MMMM-dd").Utf8().Data());
+               LocaleWin::DateFormat("yyyy-''''MMMM-dd").Utf8().data());
 }
 
 TEST_F(LocaleWinTest, monthFormat) {
@@ -230,52 +230,52 @@
   //  "MMMM, yyyy" on Windows 7 or older.
   //  "MMMM yyyy" on Window 8 or later.
   EXPECT_STREQ("MMMM yyyy",
-               MonthFormat(kEnglishUS).Replace(',', "").Utf8().Data());
-  EXPECT_STREQ("MMMM yyyy", MonthFormat(kFrenchFR).Utf8().Data());
+               MonthFormat(kEnglishUS).Replace(',', "").Utf8().data());
+  EXPECT_STREQ("MMMM yyyy", MonthFormat(kFrenchFR).Utf8().data());
   EXPECT_STREQ("yyyy\xE5\xB9\xB4M\xE6\x9C\x88",
-               MonthFormat(kJapaneseJP).Utf8().Data());
+               MonthFormat(kJapaneseJP).Utf8().data());
 }
 
 TEST_F(LocaleWinTest, timeFormat) {
-  EXPECT_STREQ("h:mm:ss a", TimeFormat(kEnglishUS).Utf8().Data());
-  EXPECT_STREQ("HH:mm:ss", TimeFormat(kFrenchFR).Utf8().Data());
-  EXPECT_STREQ("H:mm:ss", TimeFormat(kJapaneseJP).Utf8().Data());
+  EXPECT_STREQ("h:mm:ss a", TimeFormat(kEnglishUS).Utf8().data());
+  EXPECT_STREQ("HH:mm:ss", TimeFormat(kFrenchFR).Utf8().data());
+  EXPECT_STREQ("H:mm:ss", TimeFormat(kJapaneseJP).Utf8().data());
 }
 
 TEST_F(LocaleWinTest, shortTimeFormat) {
-  EXPECT_STREQ("h:mm a", ShortTimeFormat(kEnglishUS).Utf8().Data());
-  EXPECT_STREQ("HH:mm", ShortTimeFormat(kFrenchFR).Utf8().Data());
-  EXPECT_STREQ("H:mm", ShortTimeFormat(kJapaneseJP).Utf8().Data());
+  EXPECT_STREQ("h:mm a", ShortTimeFormat(kEnglishUS).Utf8().data());
+  EXPECT_STREQ("HH:mm", ShortTimeFormat(kFrenchFR).Utf8().data());
+  EXPECT_STREQ("H:mm", ShortTimeFormat(kJapaneseJP).Utf8().data());
 }
 
 TEST_F(LocaleWinTest, shortMonthLabels) {
-  EXPECT_STREQ("Jan", ShortMonthLabel(kEnglishUS, 0).Utf8().Data());
-  EXPECT_STREQ("Dec", ShortMonthLabel(kEnglishUS, 11).Utf8().Data());
-  EXPECT_STREQ("janv.", ShortMonthLabel(kFrenchFR, 0).Utf8().Data());
+  EXPECT_STREQ("Jan", ShortMonthLabel(kEnglishUS, 0).Utf8().data());
+  EXPECT_STREQ("Dec", ShortMonthLabel(kEnglishUS, 11).Utf8().data());
+  EXPECT_STREQ("janv.", ShortMonthLabel(kFrenchFR, 0).Utf8().data());
   EXPECT_STREQ(
       "d\xC3\xA9"
       "c.",
-      ShortMonthLabel(kFrenchFR, 11).Utf8().Data());
-  EXPECT_STREQ("1", ShortMonthLabel(kJapaneseJP, 0).Utf8().Data());
-  EXPECT_STREQ("12", ShortMonthLabel(kJapaneseJP, 11).Utf8().Data());
+      ShortMonthLabel(kFrenchFR, 11).Utf8().data());
+  EXPECT_STREQ("1", ShortMonthLabel(kJapaneseJP, 0).Utf8().data());
+  EXPECT_STREQ("12", ShortMonthLabel(kJapaneseJP, 11).Utf8().data());
 }
 
 TEST_F(LocaleWinTest, timeAMPMLabels) {
-  EXPECT_STREQ("AM", TimeAMPMLabel(kEnglishUS, 0).Utf8().Data());
-  EXPECT_STREQ("PM", TimeAMPMLabel(kEnglishUS, 1).Utf8().Data());
+  EXPECT_STREQ("AM", TimeAMPMLabel(kEnglishUS, 0).Utf8().data());
+  EXPECT_STREQ("PM", TimeAMPMLabel(kEnglishUS, 1).Utf8().data());
 
-  EXPECT_STREQ("", TimeAMPMLabel(kFrenchFR, 0).Utf8().Data());
-  EXPECT_STREQ("", TimeAMPMLabel(kFrenchFR, 1).Utf8().Data());
+  EXPECT_STREQ("", TimeAMPMLabel(kFrenchFR, 0).Utf8().data());
+  EXPECT_STREQ("", TimeAMPMLabel(kFrenchFR, 1).Utf8().data());
 
   EXPECT_STREQ("\xE5\x8D\x88\xE5\x89\x8D",
-               TimeAMPMLabel(kJapaneseJP, 0).Utf8().Data());
+               TimeAMPMLabel(kJapaneseJP, 0).Utf8().data());
   EXPECT_STREQ("\xE5\x8D\x88\xE5\xBE\x8C",
-               TimeAMPMLabel(kJapaneseJP, 1).Utf8().Data());
+               TimeAMPMLabel(kJapaneseJP, 1).Utf8().data());
 }
 
 TEST_F(LocaleWinTest, decimalSeparator) {
-  EXPECT_STREQ(".", DecimalSeparator(kEnglishUS).Utf8().Data());
-  EXPECT_STREQ(",", DecimalSeparator(kFrenchFR).Utf8().Data());
+  EXPECT_STREQ(".", DecimalSeparator(kEnglishUS).Utf8().data());
+  EXPECT_STREQ(",", DecimalSeparator(kFrenchFR).Utf8().data());
 }
 
 static void TestNumberIsReversible(LCID lcid,
@@ -287,7 +287,7 @@
   if (should_have)
     EXPECT_TRUE(localized.Contains(should_have));
   String converted = locale->ConvertFromLocalizedNumber(localized);
-  EXPECT_STREQ(original, converted.Utf8().Data());
+  EXPECT_STREQ(original, converted.Utf8().data());
 }
 
 void TestNumbers(LCID lcid) {
diff --git a/third_party/WebKit/Source/platform/wtf/Vector.h b/third_party/WebKit/Source/platform/wtf/Vector.h
index 07d10c01..71d9179 100644
--- a/third_party/WebKit/Source/platform/wtf/Vector.h
+++ b/third_party/WebKit/Source/platform/wtf/Vector.h
@@ -1016,10 +1016,6 @@
   T* data() { return Base::Buffer(); }
   const T* data() const { return Base::Buffer(); }
 
-  // TODO(dcheng): Temporary alias to make removing this easier.
-  T* Data() { return data(); }
-  const T* Data() const { return data(); }
-
   // Iterators and reverse iterators. They are invalidated on a reallocation.
   iterator begin() { return data(); }
   iterator end() { return begin() + size_; }
diff --git a/third_party/WebKit/Source/platform/wtf/VectorTest.cpp b/third_party/WebKit/Source/platform/wtf/VectorTest.cpp
index 9b8a789..aadaa94 100644
--- a/third_party/WebKit/Source/platform/wtf/VectorTest.cpp
+++ b/third_party/WebKit/Source/platform/wtf/VectorTest.cpp
@@ -365,7 +365,7 @@
   vector_a.push_back(10);
   vector_a.ReserveCapacity(32);
 
-  volatile int* int_pointer_a = vector_a.Data();
+  volatile int* int_pointer_a = vector_a.data();
   EXPECT_DEATH(int_pointer_a[1] = 11, "container-overflow");
   vector_a.push_back(11);
   int_pointer_a[1] = 11;
@@ -373,28 +373,28 @@
   EXPECT_DEATH((void)int_pointer_a[2], "container-overflow");
   vector_a.ShrinkToFit();
   vector_a.ReserveCapacity(16);
-  int_pointer_a = vector_a.Data();
+  int_pointer_a = vector_a.data();
   EXPECT_DEATH((void)int_pointer_a[2], "container-overflow");
 
   Vector<int> vector_b(vector_a);
   vector_b.ReserveCapacity(16);
-  volatile int* int_pointer_b = vector_b.Data();
+  volatile int* int_pointer_b = vector_b.data();
   EXPECT_DEATH((void)int_pointer_b[2], "container-overflow");
 
   Vector<int> vector_c((Vector<int>(vector_a)));
-  volatile int* int_pointer_c = vector_c.Data();
+  volatile int* int_pointer_c = vector_c.data();
   EXPECT_DEATH((void)int_pointer_c[2], "container-overflow");
   vector_c.push_back(13);
   vector_c.Swap(vector_b);
 
-  volatile int* int_pointer_b2 = vector_b.Data();
-  volatile int* int_pointer_c2 = vector_c.Data();
+  volatile int* int_pointer_b2 = vector_b.data();
+  volatile int* int_pointer_c2 = vector_c.data();
   int_pointer_b2[2] = 13;
   EXPECT_DEATH((void)int_pointer_b2[3], "container-overflow");
   EXPECT_DEATH((void)int_pointer_c2[2], "container-overflow");
 
   vector_b = vector_c;
-  volatile int* int_pointer_b3 = vector_b.Data();
+  volatile int* int_pointer_b3 = vector_b.data();
   EXPECT_DEATH((void)int_pointer_b3[2], "container-overflow");
 }
 #endif  // defined(ANNOTATE_CONTIGUOUS_CONTAINER)
diff --git a/third_party/WebKit/Source/platform/wtf/text/AtomicStringCF.cpp b/third_party/WebKit/Source/platform/wtf/text/AtomicStringCF.cpp
index 90f7224..edd953d7 100644
--- a/third_party/WebKit/Source/platform/wtf/text/AtomicStringCF.cpp
+++ b/third_party/WebKit/Source/platform/wtf/text/AtomicStringCF.cpp
@@ -49,9 +49,9 @@
         reinterpret_cast<const UChar*>(ptr), length);
 
   Vector<UniChar, 1024> uchar_buffer(length);
-  CFStringGetCharacters(string, CFRangeMake(0, length), uchar_buffer.Data());
+  CFStringGetCharacters(string, CFRangeMake(0, length), uchar_buffer.data());
   return AtomicStringTable::Instance().Add(
-      reinterpret_cast<const UChar*>(uchar_buffer.Data()), length);
+      reinterpret_cast<const UChar*>(uchar_buffer.data()), length);
 }
 
 }  // namespace WTF
diff --git a/third_party/WebKit/Source/platform/wtf/text/CString.h b/third_party/WebKit/Source/platform/wtf/text/CString.h
index 79ccbf5b..08712ae 100644
--- a/third_party/WebKit/Source/platform/wtf/text/CString.h
+++ b/third_party/WebKit/Source/platform/wtf/text/CString.h
@@ -84,8 +84,6 @@
 
   // The bytes of the string, always NUL terminated. May be null.
   const char* data() const { return buffer_ ? buffer_->data() : 0; }
-  // TODO(dcheng): Temporary alias to make removing this easier.
-  const char* Data() const { return data(); }
 
   // The length of the data(), *not* including the NUL terminator.
   size_t length() const { return buffer_ ? buffer_->length() : 0; }
diff --git a/third_party/WebKit/Source/platform/wtf/text/StringMac.mm b/third_party/WebKit/Source/platform/wtf/text/StringMac.mm
index 029581f4..fd97f7c 100644
--- a/third_party/WebKit/Source/platform/wtf/text/StringMac.mm
+++ b/third_party/WebKit/Source/platform/wtf/text/StringMac.mm
@@ -37,16 +37,16 @@
     CFIndex convertedsize =
         CFStringGetBytes(reinterpret_cast<CFStringRef>(str),
                          CFRangeMake(0, size), kCFStringEncodingISOLatin1, 0,
-                         false, lchar_buffer.Data(), size, &used_buf_len);
+                         false, lchar_buffer.data(), size, &used_buf_len);
     if ((convertedsize == size) && (used_buf_len == size)) {
-      impl_ = StringImpl::Create(lchar_buffer.Data(), size);
+      impl_ = StringImpl::Create(lchar_buffer.data(), size);
       return;
     }
 
     Vector<UChar, 1024> uchar_buffer(size);
     CFStringGetCharacters(reinterpret_cast<CFStringRef>(str),
-                          CFRangeMake(0, size), uchar_buffer.Data());
-    impl_ = StringImpl::Create(uchar_buffer.Data(), size);
+                          CFRangeMake(0, size), uchar_buffer.data());
+    impl_ = StringImpl::Create(uchar_buffer.data(), size);
   }
 }
 
diff --git a/third_party/polymer/v1_0/chromium.patch b/third_party/polymer/v1_0/chromium.patch
index 8b99b23..79c151d 100644
--- a/third_party/polymer/v1_0/chromium.patch
+++ b/third_party/polymer/v1_0/chromium.patch
@@ -24,3 +24,21 @@
      },
  
      _ariaDescribedByChanged: function(ariaDescribedBy) {
+diff --git a/components-chromium/paper-icon-button/paper-icon-button-light-extracted.js b/components-chromium/paper-icon-button/paper-icon-button-light-extracted.js
+index bac589c7274c..bc25f54320fb 100644
+--- a/components-chromium/paper-icon-button/paper-icon-button-light-extracted.js
++++ b/components-chromium/paper-icon-button/paper-icon-button-light-extracted.js
+@@ -14,11 +14,11 @@ Polymer({
+       },
+
+       _rippleDown: function() {
+-        this.getRipple().downAction();
++        this.getRipple().uiDownAction();
+       },
+
+       _rippleUp: function() {
+-        this.getRipple().upAction();
++        this.getRipple().uiUpAction();
+       },
+
+       /**
diff --git a/third_party/polymer/v1_0/components-chromium/paper-icon-button/paper-icon-button-light-extracted.js b/third_party/polymer/v1_0/components-chromium/paper-icon-button/paper-icon-button-light-extracted.js
index bac589c..bcf06c2 100644
--- a/third_party/polymer/v1_0/components-chromium/paper-icon-button/paper-icon-button-light-extracted.js
+++ b/third_party/polymer/v1_0/components-chromium/paper-icon-button/paper-icon-button-light-extracted.js
@@ -14,11 +14,11 @@
       },
 
       _rippleDown: function() {
-        this.getRipple().downAction();
+        this.getRipple().uiDownAction();
       },
 
       _rippleUp: function() {
-        this.getRipple().upAction();
+        this.getRipple().uiUpAction();
       },
 
       /**
diff --git a/tools/codesearch/OWNERS b/tools/codesearch/OWNERS
new file mode 100644
index 0000000..cd64b333d
--- /dev/null
+++ b/tools/codesearch/OWNERS
@@ -0,0 +1,4 @@
+asanka@chromium.org
+jkarlin@chromium.org
+
+# COMPONENT: Tools
diff --git a/tools/codesearch/README.md b/tools/codesearch/README.md
new file mode 100644
index 0000000..a2888caa
--- /dev/null
+++ b/tools/codesearch/README.md
@@ -0,0 +1,49 @@
+Chromium CodeSearch Library
+===========================
+
+The `codesearch` Python library provides an interface for talking to the
+Chromium CodeSearch backend at https://cs.chromium.org/
+
+The primary entry point into the library is the `CodeSearch` class. Various
+message classes you are likely to encounter are defined in `messages.py`.
+
+A quick example:
+
+```py
+import codesearch
+
+# The plugin needs to locate a local Chromium checkout. We are passing '.' as a
+# path inside the source directory, which works if the current directory is
+# inside the Chromium checkout.
+cs = codesearch.CodeSearch(a_path_inside_source_dir='.')
+
+# The backend takes CompoundRequests ...
+results = cs.SendRequestToServer(codesearch.CompoundRequest(
+    search_request=[
+        codesearch.SearchRequest(query='hello world')
+    ]))
+
+# ... and returns a CompoundResponse
+assert isinstance(results, codesearch.CompoundResponse)
+
+# both CompoundRequest and CompoundResponse are documented in messages.py.
+
+for search_result in results.search_response[0].search_result:
+    assert isinstance(search_result, codesearch.SearchResult)
+
+    if not hasattr(search_result, 'snippet'):
+        continue
+
+    for snippet in search_result.snippet:
+        assert isinstance(snippet, codesearch.Snippet)
+
+	# Just print the text of the search result snippet.
+        print snippet.text.text
+```
+
+NOTE: This isn't quite production quality code and the infra folks may pull the rug
+from under the library at any point.
+
+If you run into any bugs, or you'd like to contribute, please let someone in the
+`OWNERS` file know.
+
diff --git a/tools/codesearch/__init__.py b/tools/codesearch/__init__.py
new file mode 100644
index 0000000..ca8ba6f
--- /dev/null
+++ b/tools/codesearch/__init__.py
@@ -0,0 +1,5 @@
+# Copyright 2017 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.
+
+__all__ = ['codesearch']
diff --git a/tools/codesearch/ccs b/tools/codesearch/ccs
new file mode 100755
index 0000000..5500cf7
--- /dev/null
+++ b/tools/codesearch/ccs
@@ -0,0 +1,8 @@
+#! /usr/bin/env python
+
+# Copyright 2017 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.
+
+import runpy
+runpy.run_module('codesearch')
diff --git a/tools/codesearch/codesearch/__init__.py b/tools/codesearch/codesearch/__init__.py
new file mode 100644
index 0000000..5ea5adc
--- /dev/null
+++ b/tools/codesearch/codesearch/__init__.py
@@ -0,0 +1,22 @@
+# Copyright 2017 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.
+
+from __future__ import absolute_import
+
+from .client_api import CodeSearch
+from .messages import Message, AnnotationTypeValue, AnnotationType, \
+        InternalLink, XrefSignature, NodeEnumKind, KytheNodeEnumKind, \
+        Annotation, FileSpec, FormatType, FormatRange, AnnotatedText, \
+        CodeBlockType, Modifiers, CodeBlock, FileInfo, FileInfoResponse, \
+        FileInfoRequest, AnnotationResponse, MatchReason, Snippet, Node, \
+        CallGraphResponse, CallGraphRequest, EdgeEnumKind, XrefTypeCount, \
+        XrefSingleMatch, XrefSearchResult, XrefSearchResponse, \
+        XrefSearchRequest, VanityGitOnBorgHostname, InternalPackage, \
+        StatusResponse, GobInfo, DirInfoResponseChild, DirInfoResponse, \
+        DirInfoRequest, FileResult, SingleMatch, SearchResult, SearchResponse, \
+        SearchRequest, StatusRequest, CompoundResponse, CompoundRequest, \
+        CodeSearchProtoJsonEncoder, CodeSearchProtoJsonSymbolizedEncoder
+from .paths import GetPackageRelativePath, GetSourceRoot
+
+__all__ = []
diff --git a/tools/codesearch/codesearch/__main__.py b/tools/codesearch/codesearch/__main__.py
new file mode 100644
index 0000000..9560abb
--- /dev/null
+++ b/tools/codesearch/codesearch/__main__.py
@@ -0,0 +1,245 @@
+# Copyright 2017 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.
+"""Command line interface to Chromium Code Search.
+
+Currently emits JSON formatted responses from the Chromium CodeSearch backend at
+https://cs.chromium.org.
+"""
+
+from __future__ import absolute_import
+
+import os
+import argparse
+import sys
+import json
+import logging
+
+from codesearch import CodeSearch, CompoundRequest, \
+        CodeSearchProtoJsonSymbolizedEncoder, CodeSearchProtoJsonEncoder, \
+        XrefSearchRequest, SearchRequest, FileInfoRequest, DirInfoRequest, \
+        CallGraphRequest
+
+
+def print_result(results, args):
+  if args.pretty:
+    print json.dumps(
+        results,
+        indent=4,
+        ensure_ascii=True,
+        cls=CodeSearchProtoJsonSymbolizedEncoder)
+  else:
+    print json.dumps(results, cls=CodeSearchProtoJsonEncoder)
+
+
+def get_signature(cs, args):
+  if args.signature:
+    return args.signature
+
+  if not (args.path and args.word):
+    print('Both PATH and WORD must be specified')
+    sys.exit(2)
+
+  return cs.GetSignatureForSymbol(args.path, args.word)
+
+
+def setup_logging(cs, args):
+  if args.loglevel:
+    if args.loglevel == 'info':
+      level = logging.INFO
+    else:
+      level = logging.DEBUG
+    cs.GetLogger().setLevel(level)
+    logging.basicConfig()
+
+
+parser = argparse.ArgumentParser(description=__doc__)
+
+subcommands = parser.add_subparsers(help='Subcommands')
+
+path_specifiers = argparse.ArgumentParser(add_help=False)
+path_specifiers.add_argument('path', help='Path to file.', metavar='PATH')
+
+signature_specifiers = argparse.ArgumentParser(
+    description='Used to specify a target for the query.', add_help=False)
+
+subgroup = signature_specifiers.add_argument_group(
+    'arguments for specifying a target')
+subgroup.add_argument('-p', '--path', help='Path to file.')
+subgroup.add_argument(
+    '-w',
+    '--word',
+    help=
+    '''The word to search for in the file denoted by the path argument. You must
+    also specify -p''')
+subgroup.add_argument(
+    '-s',
+    '--signature',
+    help='''A signature provided from a previous search. No -p or -w arguments
+    required.''')
+
+common_args = argparse.ArgumentParser(
+    description='Common options', add_help=False)
+common_args.add_argument(
+    '--pretty',
+    help='Whether to pretty print the resulting JSON',
+    default=True,
+    action='store_true')
+common_args.add_argument(
+    '--loglevel', '-l', help='Log level', choices=['info', 'debug'])
+common_args.add_argument('--cache', '-C', help='Cache directory')
+
+# sig
+signature_command = subcommands.add_parser(
+    'sig', help='Query signature', parents=[signature_specifiers, common_args])
+signature_command.set_defaults(
+    func=lambda cs, a: {'signature': get_signature(cs, a)})
+
+# xrefs
+xrefs_command = subcommands.add_parser(
+    'xrefs',
+    help='Query cross-references',
+    parents=[signature_specifiers, common_args])
+xrefs_command.set_defaults(func=lambda cs, a: cs.SendRequestToServer(
+    CompoundRequest(
+        xref_search_request=[
+            XrefSearchRequest(
+                query=get_signature(cs, a),
+                file_spec=cs.GetFileSpec('.'),
+                max_num_results=100
+            )
+        ]
+    )))
+
+# callers
+callers_command = subcommands.add_parser(
+    'callers',
+    help='Query callers for a signature',
+    parents=[signature_specifiers, common_args])
+callers_command.set_defaults(
+    func=lambda cs, a: cs.SendRequestToServer(
+        CompoundRequest(
+            call_graph_request=[
+                CallGraphRequest(
+                    signature=get_signature(cs, a),
+                    file_spec=cs.GetFileSpec('.'),
+                    max_num_results=100)
+            ]
+        )))
+
+# annot
+annotate_command = subcommands.add_parser(
+    'annot',
+    help='Get annotations for file',
+    parents=[path_specifiers, common_args])
+annotate_command.add_argument(
+    '--type',
+    '-t',
+    help='Type',
+    action='append',
+    choices=['LINK_TO_DEFINITION', 'LINK_TO_URL', 'XREF_SIGNATURE'],
+    default=[])
+annotate_command.set_defaults(
+    func=lambda cs, a: cs.GetAnnotationsForFile(
+        a.path, [{'id': x} for x in a.type]))
+
+# file_info
+file_info_command = subcommands.add_parser(
+    'fileinfo', help='Get file info', parents=[path_specifiers, common_args])
+file_info_command.add_argument(
+    '--outline',
+    '-o',
+    help='Get outlining metadata.',
+    default=False,
+    action='store_true')
+file_info_command.add_argument(
+    '--html', '-H', help='Get HTML.', default=False, action='store_true')
+file_info_command.add_argument(
+    '--folding',
+    '-f',
+    help='Get folding metadata.',
+    default=False,
+    action='store_true')
+file_info_command.set_defaults(
+    func=lambda cs, a: cs.SendRequestToServer(
+        CompoundRequest(
+            file_info_request=[
+                FileInfoRequest(
+                    file_spec=cs.GetFileSpec(a.path),
+                    fetch_html_content=a.html,
+                    fetch_outline=a.outline,
+                    fetch_folding=a.folding,
+                    fetch_generated_from=False
+                )
+            ]
+        )))
+
+# dir_info
+dir_info_command = subcommands.add_parser(
+    'dirinfo',
+    help='Get directory info',
+    parents=[path_specifiers, common_args])
+dir_info_command.set_defaults(
+    func=lambda cs, a: cs.SendRequestToServer(
+        CompoundRequest(
+            dir_info_request=[
+                DirInfoRequest(
+                    file_spec=cs.GetFileSpec(a.path)
+                )
+            ]
+        )))
+
+# search
+search_command = subcommands.add_parser(
+    'q', help='Search', parents=[common_args])
+search_command.add_argument('query', help='Search terms.', metavar='QUERY')
+search_command.add_argument(
+    '--max_results',
+    '-N',
+    help='Maximum number of results to return.',
+    type=int,
+    default=50)
+search_command.add_argument(
+    '--snippets', '-S', help='Include snippets.', action='store_true')
+search_command.add_argument(
+    '--decorate',
+    '-D',
+    help='Decorate snippets with syntactic hints',
+    action='store_true')
+search_command.add_argument(
+    '--context',
+    '-U',
+    help='Lines of context to inclued in snippets.',
+    type=int,
+    default=3)
+search_command.set_defaults(
+    func=lambda cs, a: cs.SendRequestToServer(
+        CompoundRequest(
+            search_request=[
+                SearchRequest(
+                    query=a.query,
+                    return_snippets=(a.snippets or a.decorate),
+                    return_decorated_snippets=a.decorate,
+                    max_num_results=a.max_results,
+                    lines_context=a.context
+                )
+            ]
+        )))
+
+# status
+status_command = subcommands.add_parser(
+    'status', help='CodeSearch server status', parents=[common_args])
+status_command.set_defaults(
+    func=
+    lambda cs, a: cs.SendRequestToServer(CompoundRequest(status_request=[{}])))
+
+arguments = parser.parse_args()
+
+try:
+  codesearch_instance = CodeSearch(
+      a_path_inside_source_dir=os.getcwd(),
+      cache_dir=arguments.cache if arguments.cache else None)
+  setup_logging(codesearch_instance, arguments)
+  print_result(arguments.func(codesearch_instance, arguments), arguments)
+finally:
+  codesearch_instance.TeardownCache()
diff --git a/tools/codesearch/codesearch/client_api.py b/tools/codesearch/codesearch/client_api.py
new file mode 100644
index 0000000..ef916f9
--- /dev/null
+++ b/tools/codesearch/codesearch/client_api.py
@@ -0,0 +1,218 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""This file defines the entry point for most external consumers of the Python
+Codesearch library.
+"""
+
+from __future__ import absolute_import
+
+import logging
+import os
+
+from .file_cache import FileCache
+from .messages import CompoundRequest, AnnotationType, AnnotationTypeValue, \
+        CompoundResponse, FileInfoRequest, FileSpec, AnnotationRequest, \
+        EdgeEnumKind, XrefSearchRequest
+from .paths import GetSourceRoot
+
+try:
+  from urllib.request import urlopen
+  from urllib.parse import urlencode
+except ImportError:
+  from urllib2 import urlopen
+  from urllib import urlencode
+
+
+class CodeSearch(object):
+
+  def __init__(self,
+               should_cache=False,
+               cache_dir=None,
+               a_path_inside_source_dir=None,
+               package_name='chromium',
+               codesearch_host='https://cs.chromium.org',
+               request_timeout_in_seconds=3):
+
+    self.file_cache = None
+
+    self.logger = logging.getLogger('codesearch')
+
+    self.source_root = ''
+
+    self.package_name = package_name
+
+    self.codesearch_host = codesearch_host
+
+    self.request_timeout_in_seconds = request_timeout_in_seconds
+
+    self.source_root = GetSourceRoot(a_path_inside_source_dir)
+
+    if not should_cache:
+      self.file_cache = None
+      return
+    if self.file_cache:
+      return
+    self.file_cache = FileCache(cache_dir=cache_dir)
+
+  def GetSourceRoot(self):
+    return self.source_root
+
+  def GetLogger(self):
+    return self.logger
+
+  def GetFileSpec(self, path=None):
+    if not path:
+      return FileSpec(name='.', package_name=self.package_name)
+
+    return FileSpec(
+        name=os.path.relpath(os.path.abspath(path), self.source_root),
+        package_name=self.package_name)
+
+  def TeardownCache(self):
+    if self.file_cache:
+      self.file_cache.close()
+
+    self.file_cache = None
+    self.source_root = None
+
+  def _Retrieve(self, url):
+    """Retrieve the URL by first checking the cache and then falling back to
+        using the network."""
+    self.logger.debug('Fetching %s', url)
+
+    if self.file_cache:
+      cached_response = self.file_cache.get(url)
+      self.logger.debug('Found cached response')
+      if (cached_response):
+        return cached_response.decode('utf8')
+    response = urlopen(url, timeout=self.request_timeout_in_seconds)
+    result = response.read()
+    if self.file_cache:
+      self.file_cache.put(url, result)
+    return result.decode('utf8')
+
+  def SendRequestToServer(self, compound_request):
+    if not isinstance(compound_request, CompoundRequest):
+      raise ValueError(
+          '|compound_request| should be an instance of CompoundRequest')
+
+    qs = urlencode(compound_request.AsQueryString(), doseq=True)
+    url = '{host}/codesearch/json?{qs}'.format(host=self.codesearch_host, qs=qs)
+    result = self._Retrieve(url)
+    return CompoundResponse.FromJsonString(result)
+
+  def GetAnnotationsForFile(self, filename, annotation_types):
+    return self.SendRequestToServer(
+        CompoundRequest(annotation_request=[
+            AnnotationRequest(
+                file_spec=self.GetFileSpec(filename), type=annotation_types)
+        ]))
+
+  def GetSignatureForLocation(self, filename, line, column):
+    result = self.GetAnnotationsForFile(
+        filename, [AnnotationType(id=AnnotationTypeValue.XREF_SIGNATURE)])
+    result = result.annotation_response[0]
+
+    if result.return_code != 1:
+      raise Exception('Request failed. Response=%s' % (result.AsQueryString()))
+
+    for annotation in result.annotation:
+      if not annotation.range.Contains(line, column):
+        continue
+
+      if hasattr(annotation, 'xref_signature'):
+        return annotation.xref_signature.signature
+
+      if hasattr(annotation, 'internal_link'):
+        return annotation.internal_link.signature
+
+    raise Exception("Can't determine signature for %s at %d:%d" %
+                    (filename, line, column))
+
+  def GetSignatureForSymbol(self, filename, symbol):
+    result = self.GetAnnotationsForFile(
+        filename, [AnnotationType(id=AnnotationTypeValue.XREF_SIGNATURE)])
+    result = result.annotation_response[0]
+
+    if result.return_code != 1:
+      raise Exception('Request failed. Response=%s' % (result.AsQueryString()))
+
+    for snippet in result.annotation:
+      if hasattr(snippet, 'xref_signature'):
+        signature = snippet.xref_signature.signature
+        if '%s(' % symbol in signature:
+          return signature
+
+      elif hasattr(snippet, 'internal_link'):
+        signature = snippet.internal_link.signature
+        if '::%s' % symbol in signature or 'class-%s' % symbol in signature:
+          return signature
+
+    raise Exception("Can't determine signature for %s:%s" % (filename, symbol))
+
+  def GetXrefsFor(self, signature, edge_filter):
+    refs = self.SendRequestToServer(
+        CompoundRequest(xref_search_request=[
+            XrefSearchRequest(
+                file_spec=self.GetFileSpec(),
+                query=signature,
+                edge_filter=edge_filter)
+        ]))
+    if not refs or not hasattr(refs.xref_search_response[0], 'search_result'):
+      return []
+    return refs.xref_search_response[0].search_result
+
+  def GetOverridingDefinitions(self, signature):
+    candidates = []
+    refs = self.GetXrefsFor(signature, [EdgeEnumKind.OVERRIDDEN_BY])
+    for result in refs:
+      matches = []
+      for match in result.match:
+        if hasattr(match, 'grok_modifiers') and hasattr(
+            match.grok_modifiers,
+            'definition') and match.grok_modifiers.definition:
+          matches.append(match)
+      if matches:
+        result.match = matches
+        candidates.append(result)
+    return candidates
+
+  def GetCallTargets(self, signature):
+    # First look up the declaration for the callsite.
+    refs = self.GetXrefsFor(signature, [EdgeEnumKind.HAS_DECLARATION])
+
+    candidates = []
+    for result in refs:
+      for match in result.match:
+        if hasattr(match, 'grok_modifiers') and hasattr(
+            match.grok_modifiers, 'virtual') and match.grok_modifiers.virtual:
+          candidates.extend(self.GetOverridingDefinitions(match.signature))
+    if not candidates:
+      return self.GetXrefsFor(signature, [EdgeEnumKind.HAS_DEFINITION])
+    return candidates
+
+  def IsContentStale(self, filename, buffer_lines, check_prefix=False):
+    response = self.SendRequestToServer(
+        CompoundRequest(file_info_request=[
+            FileInfoRequest(
+                file_spec=self.GetFileSpec(filename),
+                fetch_html_content=False,
+                fetch_outline=False,
+                fetch_folding=False,
+                fetch_generated_from=False)
+        ]))
+
+    response = response.file_info_response[0]
+    content_lines = response.file_info.content.text.split('\n')
+
+    if check_prefix:
+      content_lines = content_lines[:len(buffer_lines)]
+      if len(content_lines) != len(buffer_lines):
+        return True
+
+    for left, right in zip(content_lines, buffer_lines):
+      if left != right:
+        return True
+
+    return False
diff --git a/tools/codesearch/codesearch/file_cache.py b/tools/codesearch/codesearch/file_cache.py
new file mode 100644
index 0000000..4442180
--- /dev/null
+++ b/tools/codesearch/codesearch/file_cache.py
@@ -0,0 +1,121 @@
+# Copyright 2017 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.
+
+import hashlib
+import threading
+import tempfile
+import os
+import datetime
+
+# A key/value store that stores objects to disk in temporary objects
+# for 30 minutes.
+
+
+class FileCache:
+
+  def __init__(self, cache_dir=None, expiration_in_minutes=30):
+
+    # Protects |self| but individual file objects in |store| are not
+    # protected once its returned from |_file_for|.
+    self.lock = threading.Lock()
+
+    # Dictionary mapping a URL to a tuple containing a file object and a
+    # timestamp. The timestamp notes the creation time.
+    self.store = {}
+
+    # Directory containing cache files. If |cache_dir| is None, then each
+    # file is created independently using tempfile.TemporaryFile().
+    self.cache_dir = cache_dir
+
+    # Garbage collector timer.
+    self.timer = threading.Timer(15 * 60, self.gc)
+    self.timer.start()
+
+    self.expiration = datetime.timedelta(minutes=expiration_in_minutes)
+
+    if cache_dir and not os.path.exists(cache_dir):
+      if not os.path.isabs(cache_dir):
+        raise ValueError('|cache_dir| should be an absolute path')
+      os.mkdirs(cache_dir)
+
+  def _file_for(self, url, create=False):
+    with self.lock:
+      if url in self.store:
+        f, _ = self.store[url]
+        f.seek(0)
+
+      elif create and self.cache_dir is None:
+        f = tempfile.TemporaryFile()
+        f.seek(0)
+
+      elif self.cache_dir:
+        deterministic_filename = os.path.join(self.cache_dir,
+                                              hashlib.sha1(url).hexdigest())
+        if os.path.exists(deterministic_filename):
+          st = os.stat(deterministic_filename)
+          if create:
+            f = open(deterministic_filename, 'w+')
+          elif datetime.datetime.utcfromtimestamp(st.st_mtime) + \
+                  self.expiration > datetime.datetime.now():
+            f = open(deterministic_filename, 'r+')
+            self.store[url] = (f,
+                               datetime.datetime.utcfromtimestamp(st.st_mtime))
+          else:
+            # Existing file has expired.
+            os.remove(deterministic_filename)
+            return None
+        else:
+          if create:
+            f = open(deterministic_filename, 'w+')
+          else:
+            return None
+      else:
+        return None
+
+      if url not in self.store:
+        self.store[url] = (f, datetime.datetime.now())
+      return f
+
+  def put(self, url, data):
+    f = self._file_for(url, create=True)
+    f.write(data)
+    f.flush()
+
+  def get(self, url):
+    f = self._file_for(url, create=False)
+    if f is None:
+      return ''
+    f.seek(0)
+    return f.read()
+
+  def gc(self):
+    dir_to_purge = None
+    expiration = None
+    with self.lock:
+      expired = datetime.datetime.now() - self.expiration
+      remove = []
+      for url, (_, timestamp) in self.store.items():
+        if timestamp < expired:
+          remove.append(url)
+      for url in remove:
+        self.store.pop(url)
+      self.timer = threading.Timer(15 * 60, self.gc)
+      self.timer.start()
+      dir_to_purge = self.cache_dir
+      expiration = self.expiration
+
+    # This part doesn't require a lock on |self|.
+    if not dir_to_purge:
+      return
+
+    now = datetime.datetime.now()
+    for entry in os.listdir(dir_to_purge):
+      full_path = os.path.join(dir_to_purge, entry)
+      st = os.stat(full_path)
+      expires_on = datetime.datetime.utcfromtimestamp(st.st_mtime) + expiration
+      if expires_on < now:
+        os.remove(full_path)
+
+  def close(self):
+    self.timer.cancel()
diff --git a/tools/codesearch/codesearch/messages.py b/tools/codesearch/codesearch/messages.py
new file mode 100644
index 0000000..f282ca1
--- /dev/null
+++ b/tools/codesearch/codesearch/messages.py
@@ -0,0 +1,1014 @@
+# Copyright 2017 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.
+
+import json
+import sys
+
+
+class CodeSearchProtoJsonEncoder(json.JSONEncoder):
+
+  def default(self, o):
+    if isinstance(o, Message):
+      return o.__dict__
+    return o
+
+
+class CodeSearchProtoJsonSymbolizedEncoder(json.JSONEncoder):
+
+  def default(self, o):
+    if isinstance(o, Message):
+      rv = {}
+      desc = o.__class__.DESCRIPTOR
+      for k, v in o.__dict__.iteritems():
+        if k in desc and not isinstance(desc[k], list) and issubclass(
+            desc[k], Message) and desc[k].IsEnum():
+          rv[k] = desc[k].ToSymbol(v)
+        else:
+          rv[k] = v
+      return rv
+    return o
+
+
+class Message(object):
+
+  class PARENT_TYPE:
+    pass
+
+  def AsQueryString(self):
+    values = []
+    for k, v in self.__dict__.iteritems():
+      values.extend(Message.ToQueryString(k, v))
+    return values
+
+  @staticmethod
+  def ToQueryString(k, o):
+    if o is None:
+      return []
+    if isinstance(o, Message):
+      return [(k, 'b')] + o.AsQueryString() + [(k, 'e')]
+    if isinstance(o, bool):
+      return [(k, 'true' if o else 'false')]
+    if isinstance(o, list):
+      values = []
+      for v in o:
+        values.extend(Message.ToQueryString(k, v))
+      return values
+    return [(k, str(o))]
+
+  @staticmethod
+  def Coerce(source, target_type, parent_class=None):
+    if isinstance(target_type, list):
+      assert isinstance(source, list)
+      assert len(target_type) == 1
+      target_type = target_type[0]
+
+      return [Message.Coerce(x, target_type, parent_class) for x in source]
+
+    if target_type == Message.PARENT_TYPE:
+
+      assert parent_class is not None
+
+      return Message.Coerce(source, parent_class, parent_class)
+
+    if issubclass(target_type, Message):
+      if isinstance(source, target_type):
+        return source
+
+      typespec = target_type.DESCRIPTOR
+      if isinstance(typespec, dict):
+        assert isinstance(
+            source, dict), 'Source is not a dictionary: %s; Mapping to %s' % (
+                source, target_type)
+        dest = target_type()
+        for k, v in source.iteritems():
+          if k in typespec:
+            dest.__dict__[k] = Message.Coerce(v, typespec[k], target_type)
+          else:
+            dest.__dict__[k] = v
+        return dest
+      if typespec is None:
+        assert isinstance(source, dict)
+        m = Message()
+        m.__dict__ = source.copy()
+        return m
+      if sys.version_info[0] == 2:
+        if typespec != str and isinstance(source, basestring) and hasattr(
+            target_type, source):
+          return typespec(getattr(target_type, source))
+      else:
+        if typespec != str and isinstance(source, str) and hasattr(
+            target_type, source):
+          return typespec(getattr(target_type, source))
+      return typespec(source)
+    return target_type(source)
+
+  @classmethod
+  def IsEnum(cls):
+    return not isinstance(cls.DESCRIPTOR, dict)
+
+  @classmethod
+  def ToSymbol(cls, v):
+    assert cls.IsEnum()
+    for prop, value in vars(cls).iteritems():
+      if value == v:
+        return prop
+    return v
+
+  @classmethod
+  def FromSymbol(cls, s):
+    assert cls.IsEnum()
+    return vars(cls)[s]
+
+  @classmethod
+  def Make(cls, **kwargs):
+    return Message.Coerce(kwargs, cls)
+
+  @classmethod
+  def FromShallowDict(cls, d):
+    return Message.Coerce(d, cls)
+
+  @classmethod
+  def FromJsonString(cls, s):
+    d = json.loads(s, 'utf8')
+    return cls.FromShallowDict(d)
+
+  DESCRIPTOR = None
+
+
+def message(cls):
+
+  def Constructor(self, **kwargs):
+    if len(kwargs) == 0:
+      return
+    self.__dict__ = cls.Make(**kwargs).__dict__
+
+  setattr(cls, '__init__', Constructor)
+  return cls
+
+
+class AnnotationTypeValue(Message):
+  BLAME = 0x00040
+  CODE_FINDINGS = 0x40000
+  COMPILER = 0x00080
+  COVERAGE = 0x00010
+  DEPRECATED = 0x02000
+  FINDBUGS = 0x00200
+  LANG_COUNT = 0x04000
+  LINK_TO_DEFINITION = 0x00001
+  LINK_TO_URL = 0x00002
+  LINT = 0x00020
+  OFFLINE_QUERIES = 0x08000
+  OVERRIDE = 0x01000
+  TOOLS = 0x20000
+  UNKNOWN = 0x00000
+  XREF_SIGNATURE = 0x00004
+
+  DESCRIPTOR = int
+
+
+@message
+class AnnotationType(Message):
+  DESCRIPTOR = {
+      'id': AnnotationTypeValue,
+  }
+
+
+class TextRange(Message):
+  DESCRIPTOR = {
+      'start_line': int,
+      'start_column': int,
+      'end_line': int,
+      'end_column': int,
+  }
+
+  def Contains(self, line, column):
+    return not (line < self.start_line or line > self.end_line or
+                (line == self.start_line and column < self.start_column) or
+                (line == self.end_line and column > self.end_column))
+
+
+class InternalLink(Message):
+  DESCRIPTOR = {
+      'package_name': str,
+      'signature': str,
+      'signature_hash': str,
+      'path': str,
+      'range': TextRange,
+  }
+
+
+class XrefSignature(Message):
+  DESCRIPTOR = {
+      'signature': str,
+      'signature_hash': str,
+  }
+
+
+class NodeEnumKind(Message):
+  ALIAS_JOIN = 9100
+  ANNOTATION = 900
+  ARRAY = 5700
+  BIGFLOAT = 3000
+  BIGINT = 2900
+  BOOLEAN = 2000
+  CHANNEL = 6700
+  CHAR = 2100
+  CLASS = 500
+  COMMENT = 9400
+  COMMUNICATION = 3850
+  COMPLEX = 2800
+  CONSTRUCTOR = 1200
+  CONST_TYPE = 5400
+  DEF_DECL_JOIN = 9000
+  DELIMITER = 10000
+  DIAGNOSTIC = 4100
+  DIRECTORY = 4000
+  DOCUMENTATION = 9800
+  DOCUMENTATION_TAG = 9900
+  DYNAMIC_TYPE = 9300
+  ENUM = 700
+  ENUM_CONSTANT = 800
+  FIELD = 1500
+  FILE = 3900
+  FIXED_POINT = 2600
+  FLOAT = 2500
+  FORWARD_DECLARATION = 5300
+  FUNCTION = 1000
+  FUNCTION_TYPE = 10200
+  IMPORT = 8200
+  INDEX_INFO = 31337
+  INSTANCE = 4600
+  INTEGER = 2400
+  INTERFACE = 600
+  LABEL = 11600
+  LIST = 6300
+  LOCAL = 1600
+  LOST = 9600
+  MAP = 6000
+  MARKUP_ATTRIBUTE = 11300
+  MARKUP_TAG = 11200
+  MATRIX = 5800
+  METHOD = 1100
+  MODULE = 300
+  NAME = 3300
+  NAMESPACE = 100
+  NULL_TYPE = 7300
+  NUMBER = 3100
+  OBJECT = 4500
+  OPAQUE = 6500
+  OPTION_TYPE = 5500
+  PACKAGE = 200
+  PACKAGE_JOIN = 9200
+  PARAMETER = 1700
+  PARAMETRIC_TYPE = 5600
+  POINTER = 5000
+  PROPERTY = 1900
+  QUEUE = 6400
+  RATIONAL = 2700
+  REFERENCE_TYPE = 5100
+  REGEXP = 2300
+  RESTRICTION_TYPE = 10100
+  RULE = 8100
+  SEARCHABLE_IDENTIFIER = 11500
+  SEARCHABLE_NAME = 9500
+  SET = 5900
+  STRING = 2200
+  STRUCT = 400
+  SYMBOL = 3200
+  TAG_NAME = 11100
+  TARGET = 8000
+  TEMPLATE = 1400
+  TEXT = 9700
+  TEXT_MACRO = 1300
+  THREAD = 6600
+  TUPLE = 6100
+  TYPE_ALIAS = 5200
+  TYPE_DESCRIPTOR = 11400
+  TYPE_SPECIALIZATION = 7000
+  TYPE_VARIABLE = 7100
+  TYPE_VARIABLE_TYPE = 10400
+  UNION = 6200
+  UNIT_TYPE = 6900
+  UNRESOLVED_TYPE = 404
+  USAGE = 3800
+  USER_TYPE = 10300
+  VALUE = 3400
+  VARIABLE = 1800
+  VARIADIC_TYPE = 7200
+  VOID_TYPE = 6800
+
+  DESCRIPTOR = int
+
+
+class KytheNodeEnumKind(Message):
+  ABS = 100
+  ABSVAR = 200
+  ANCHOR = 300
+  CONSTANT = 500
+  DEPRECATED_CALLABLE = 400
+  DOC = 550
+  FILE = 600
+  FUNCTION = 800
+  FUNCTION_CONSTRUCTOR = 810
+  FUNCTION_DESTRUCTOR = 820
+  INTERFACE = 700
+  LOOKUP = 900
+  MACRO = 1000
+  META = 1050
+  NAME = 1100
+  PACKAGE = 1200
+  RECORD = 1300
+  RECORD_CLASS = 1310
+  RECORD_STRUCT = 1320
+  RECORD_UNION = 1330
+  SUM = 1400
+  SUM_ENUM = 1410
+  SUM_ENUM_CLASS = 1420
+  TALIAS = 1500
+  TAPP = 1600
+  TBUILTIN = 1700
+  TBUILTIN_ARRAY = 1705
+  TBUILTIN_BOOLEAN = 1710
+  TBUILTIN_BYTE = 1715
+  TBUILTIN_CHAR = 1720
+  TBUILTIN_DOUBLE = 1725
+  TBUILTIN_FLOAT = 1730
+  TBUILTIN_FN = 1735
+  TBUILTIN_INT = 1740
+  TBUILTIN_LONG = 1745
+  TBUILTIN_PTR = 1750
+  TBUILTIN_SHORT = 1755
+  TBUILTIN_VOID = 1760
+  TNOMINAL = 1800
+  TSIGMA = 1850
+  UNRESOLVED_TYPE = 0
+  VARIABLE = 1900
+  VARIABLE_FIELD = 1910
+  VARIABLE_LOCAL = 1920
+  VARIABLE_LOCAL_EXCEPTION = 1940
+  VARIABLE_LOCAL_PARAMETER = 1930
+  VARIABLE_LOCAL_RESOURCE = 1950
+  VCS = 2000
+
+  DESCRIPTOR = int
+
+
+class Annotation(Message):
+  DESCRIPTOR = {
+      'content': str,
+      'file_name': str,
+      'internal_link': InternalLink,
+      'is_implicit_target': bool,
+      'kythe_xref_kind': KytheNodeEnumKind,
+      'range': TextRange,
+      'status': int,
+      'type': AnnotationType,
+      'url': str,
+      'xref_kind': NodeEnumKind,
+      'xref_signature': XrefSignature,
+  }
+
+
+@message
+class FileSpec(Message):
+  DESCRIPTOR = {'name': str, 'package_name': str}
+
+
+class FormatType(Message):
+  CARRIAGE_RETURN = 22
+  CL_LINK = 33
+  CODESEARCH_LINK = 36
+  EXTERNAL_LINK = 31
+  GOOGLE_INTERNAL_LINK = 30
+  INCLUDE_QUERY = 35
+  LINE = 1
+  QUERY_MATCH = 40
+  SNIPPET_QUERY_MATCH = 41
+  SYNTAX_CLASS = 8
+  SYNTAX_COMMENT = 5
+  SYNTAX_CONST = 9
+  SYNTAX_DEPRECATED = 11
+  SYNTAX_DOC_NAME = 13
+  SYNTAX_DOC_TAG = 12
+  SYNTAX_ESCAPE_SEQUENCE = 10
+  SYNTAX_KEYWORD = 3
+  SYNTAX_KEYWORD_STRONG = 15
+  SYNTAX_MACRO = 7
+  SYNTAX_MARKUP_BOLD = 51
+  SYNTAX_MARKUP_CODE = 54
+  SYNTAX_MARKUP_ENTITY = 50
+  SYNTAX_MARKUP_ITALIC = 52
+  SYNTAX_MARKUP_LINK = 53
+  SYNTAX_NUMBER = 6
+  SYNTAX_PLAIN = 2
+  SYNTAX_STRING = 4
+  SYNTAX_TASK_TAG = 14
+  TABS = 21
+  TRAILING_SPACE = 20
+  UNKNOWN_TYPE = 0
+  USER_NAME_LINK = 32
+
+  DESCRIPTOR = int
+
+
+class FormatRange(Message):
+  DESCRIPTOR = {'type': FormatType, 'range': TextRange, 'target': str}
+
+
+class FileType(Message):
+  BINARY = 5
+  CODE = 1
+  DATA = 3
+  DIR = 4
+  DOC = 2
+  SYMLINK = 6
+  UNKNOWN = 0
+
+  DESCRIPTOR = int
+
+
+class AnnotatedText(Message):
+  DESCRIPTOR = {'text': str, 'range': [FormatRange]}
+
+
+class CodeBlockType(Message):
+  DESCRIPTOR = int
+
+  ALLOCATION = 49
+  ANONYMOUS_FUNCTION = 15
+  BUILD_ARGUMENT = 25
+  BUILD_BINARY = 21
+  BUILD_GENERATOR = 24
+  BUILD_LIBRARY = 23
+  BUILD_RULE = 20
+  BUILD_TEST = 22
+  BUILD_VARIABLE = 26
+  CLASS = 1
+  COMMENT = 13
+  DEFINE_CONST = 40
+  DEFINE_MACRO = 41
+  ENUM = 4
+  ENUM_CONSTANT = 14
+  ERROR = 0
+  FIELD = 7
+  FUNCTION = 8
+  INTERFACE = 2
+  JOB = 47
+  JS_ASSIGNMENT = 38
+  JS_CONST = 31
+  JS_FUNCTION_ASSIGNMENT = 39
+  JS_FUNCTION_LITERAL = 37
+  JS_GETTER = 35
+  JS_GOOG_PROVIDE = 32
+  JS_GOOG_REQUIRE = 33
+  JS_LITERAL = 36
+  JS_SETTER = 34
+  JS_VAR = 30
+  METHOD = 6
+  NAMESPACE = 11
+  PACKAGE = 17
+  PROPERTY = 12
+  RESERVED_27 = 27
+  RESERVED_28 = 28
+  RESERVED_29 = 29
+  SERVICE = 48
+  STRUCT = 3
+  TEMPLATE = 46
+  TEST = 16
+  TYPEDEF = 10
+  UNION = 5
+  VARIABLE = 9
+  XML_TAG = 45
+
+
+class Modifiers(Message):
+  DESCRIPTOR = {
+      '_global': bool,
+      '_thread_local': bool,
+      'abstract': bool,
+      'anonymous': bool,
+      'autogenerated': bool,
+      'close_delimiter': bool,
+      'constexpr_': bool,
+      'declaration': bool,
+      'definition': bool,
+      'deprecated': bool,
+      'discrete': bool,
+      'dynamically_scoped': bool,
+      'exported': bool,
+      'file_scoped': bool,
+      'foreign': bool,
+      'getter': bool,
+      'has_figment': bool,
+      'immutable': bool,
+      'implicit': bool,
+      'inferred': bool,
+      'is_figment': bool,
+      'join_node': bool,
+      'library_scoped': bool,
+      'namespace_scoped': bool,
+      'nonescaped': bool,
+      'open_delimiter': bool,
+      'operator': bool,
+      'optional': bool,
+      'package_scoped': bool,
+      'parametric': bool,
+      'predeclared': bool,
+      'private': bool,
+      'protected': bool,
+      'public': bool,
+      'receiver': bool,
+      'register': bool,
+      'renamed': bool,
+      'repeated': bool,
+      'setter': bool,
+      'shadowing': bool,
+      'signed': bool,
+      'static': bool,
+      'strict_math': bool,
+      'synchronized': bool,
+      'terminal': bool,
+      'transient': bool,
+      'unsigned': bool,
+      'virtual': bool,
+      'volatile': bool,
+      'whitelisted': bool,
+  }
+
+
+class CodeBlock(Message):
+  DESCRIPTOR = {
+      'child': [Message.PARENT_TYPE],
+      'modifiers': Modifiers,
+      'name': str,
+      'name_prefix': str,
+      'signature': str,
+      'text_range': TextRange,
+      'type': CodeBlockType,
+  }
+
+
+class FileInfo(Message):
+  DESCRIPTOR = {
+      'actual_name': str,
+      'changelist_num': str,
+      'codeblock': [CodeBlock],
+      'content': AnnotatedText,
+      'converted_content': AnnotatedText,
+      'converted_lines': int,
+      'fold_ranges': [TextRange],
+      'generated': bool,
+      'generated_from': [str],
+      'html_text': str,
+      'language': str,
+      'license_path': str,
+      'license_type': str,
+      'lines': int,
+      'md5': str,
+      'mime_type': str,
+      'name': str,
+      'package_name': str,
+      'revision_num': str,
+      'size': int,
+      'type': FileType,
+  }
+
+
+class FileInfoResponse(Message):
+  DESCRIPTOR = {
+      'announcement': str,
+      'error_message': str,
+      'file_info': FileInfo,
+      'return_code': int,
+  }
+
+
+@message
+class FileInfoRequest(Message):
+  DESCRIPTOR = {
+      'file_spec': FileSpec,
+      'fetch_html_content': bool,
+      'fetch_outline': bool,
+      'fetch_folding': bool,
+      'fetch_generated_from': bool,
+  }
+
+
+class AnnotationResponse(Message):
+  DESCRIPTOR = {
+      'annotation': [Annotation],
+      'file': str,
+      'max_findings_reached': bool,
+      'return_code': int,
+  }
+
+
+@message
+class AnnotationRequest(Message):
+  DESCRIPTOR = {
+      'file_spec': FileSpec,
+      'type': [AnnotationType],
+  }
+
+
+class MatchReason(Message):
+  DESCRIPTOR = {
+      'blame': bool,
+      'content': bool,
+      'filename': bool,
+      'filename_lineno': bool,
+      'scoped_symbol': bool,
+  }
+
+
+class Snippet(Message):
+  DESCRIPTOR = {
+      'first_line_number': int,
+      'match_reason': MatchReason,
+      'scope': str,
+      'text': AnnotatedText,
+  }
+
+
+class Node(Message):
+  DESCRIPTOR = {
+      'call_scope_range': TextRange,
+      'call_site_range': TextRange,
+      'children': [Message.PARENT_TYPE],
+      'display_name': str,
+      'edge_kind': str,
+      'file_path': str,
+      'identifier': str,
+      'node_kind': str,
+      'override': bool,
+      'package_name': str,
+      'params': [str],
+      'signature': str,
+      'snippet': Snippet,
+      'snippet_file_path': str,
+      'snippet_package_name': str,
+  }
+
+
+class CallGraphResponse(Message):
+  DESCRIPTOR = {
+      'debug_message': str,
+      'estimated_total_number_results': int,
+      'is_call_graph': bool,
+      'is_from_kythe': bool,
+      'kythe_next_page_token': str,
+      'node': Node,
+      'results_offset': int,
+      'return_code': int,
+  }
+
+
+@message
+class CallGraphRequest(Message):
+  DESCRIPTOR = {
+      'file_spec': FileSpec,
+      'max_num_results': int,
+      'signature': str,
+  }
+
+
+class EdgeEnumKind(Message):
+  DESCRIPTOR = int
+
+  ALLOWED_ACCESS_TO = 4500
+  ANNOTATED_WITH = 5000
+  ANNOTATION_OF = 5100
+  BASE_TYPE = 1300
+  BELONGS_TO_NAMESPACE = 7200
+  BELONGS_TO_PACKAGE = 6900
+  CALL = 2200
+  CALLED_AT = 2300
+  CALLGRAPH_FROM = 4700
+  CALLGRAPH_TO = 4600
+  CAPTURED_BY = 1200
+  CAPTURES = 1100
+  CATCHES = 6400
+  CAUGHT_BY = 6500
+  CHANNEL_USED_BY = 2351
+  CHILD = 5300
+  COMMENT_IN_FILE = 7400
+  COMPOSING_TYPE = 1400
+  CONSUMED_BY = 4100
+  CONTAINS_COMMENT = 7500
+  CONTAINS_DECLARATION = 5800
+  CONTAINS_USAGE = 6000
+  DECLARATION_IN_FILE = 5900
+  DECLARATION_OF = 3200
+  DECLARED_BY = 400
+  DECLARES = 300
+  DEFINITION_OF = 3400
+  DIAGNOSTIC_OF = 5400
+  DIRECTLY_INHERITED_BY = 1060
+  DIRECTLY_INHERITS = 1050
+  DIRECTLY_OVERRIDDEN_BY = 860
+  DIRECTLY_OVERRIDES = 850
+  DOCUMENTED_WITH = 7700
+  DOCUMENTS = 7600
+  ENCLOSED_USAGE = 4900
+  EXTENDED_BY = 200
+  EXTENDS = 100
+  GENERATED_BY = 3100
+  GENERATES = 3000
+  GENERATES_NAME = 3150
+  HAS_DECLARATION = 3300
+  HAS_DEFINITION = 3500
+  HAS_DIAGNOSTIC = 5500
+  HAS_FIGMENT = 9200
+  HAS_IDENTIFIER = 9400
+  HAS_INPUT = 4000
+  HAS_OUTPUT = 4200
+  HAS_PROPERTY = 2800
+  HAS_SELECTION = 10900
+  HAS_TYPE = 1800
+  IMPLEMENTED_BY = 600
+  IMPLEMENTS = 500
+  INHERITED_BY = 1000
+  INHERITS = 900
+  INITIALIZED_WITH = 9100
+  INITIALIZES = 9000
+  INJECTED_AT = 10500
+  INJECTS = 10400
+  INSTANTIATED_AT = 2500
+  INSTANTIATION = 2400
+  IS_FIGMENT_OF = 9300
+  IS_IDENTIFIER_OF = 9500
+  IS_TYPE_OF = 1900
+  KEY_METHOD = 3600
+  KEY_METHOD_OF = 3700
+  MEMBER_SELECTED_AT = 10700
+  NAMESPACE_CONTAINS = 7300
+  NAME_GENERATED_BY = 3160
+  OUTLINE_CHILD = 5700
+  OUTLINE_PARENT = 5600
+  OVERRIDDEN_BY = 800
+  OVERRIDES = 700
+  PACKAGE_CONTAINS = 6800
+  PARAMETER_TYPE = 8800
+  PARAMETER_TYPE_OF = 8900
+  PARENT = 5200
+  PRODUCED_BY = 4300
+  PROPERTY_OF = 2900
+  RECEIVES_FROM = 2353
+  REFERENCE = 2600
+  REFERENCED_AT = 2700
+  REQUIRED_BY = 3900
+  REQUIRES = 3800
+  RESTRICTED_TO = 4400
+  RETURNED_BY = 2100
+  RETURN_TYPE = 2000
+  SELECTED_FROM = 10800
+  SELECTS_MEMBER_OF = 10600
+  SENDS_TO = 2352
+  SPECIALIZATION_OF = 1600
+  SPECIALIZED_BY = 1700
+  THROWGRAPH_FROM = 6700
+  THROWGRAPH_TO = 6600
+  THROWN_BY = 6300
+  THROWS = 6200
+  TREE_CHILD = 7900
+  TREE_PARENT = 7800
+  TYPE_PARAMETER = 1500
+  TYPE_PARAMETER_OF = 1550
+  USAGE_CONTEXT = 4800
+  USAGE_IN_FILE = 6100
+  USES_CHANNEL = 2350
+  USES_VARIABLE = 7000
+  VARIABLE_USED_IN = 7100
+  XLANG_PROVIDES = 8600
+  XLANG_PROVIDES_NAME = 8400
+  XLANG_USES = 8700
+  XLANG_USES_NAME = 8500
+
+
+class XrefTypeCount(Message):
+  DESCRIPTOR = {
+      'count': int,
+      'type': str,
+      'type_id': int,
+  }
+
+
+class XrefSingleMatch(Message):
+  DESCRIPTOR = {
+      'line_number': int,
+      'line_text': str,
+      'type': str,
+      'type_id': EdgeEnumKind,
+      'grok_modifiers': Modifiers,
+      'signature': str,
+  }
+
+
+class XrefSearchResult(Message):
+  DESCRIPTOR = {
+      'file': FileSpec,
+      'match': [XrefSingleMatch],
+  }
+
+
+class XrefSearchResponse(Message):
+  DESCRIPTOR = {
+      'eliminated_type_count': [XrefTypeCount],
+      'estimated_total_type_count': [XrefTypeCount],
+      'from_kythe': bool,
+      'grok_total_number_of_results': int,
+      'search_result': [XrefSearchResult],
+      'status': int,
+      'status_message': str,
+  }
+
+
+@message
+class XrefSearchRequest(Message):
+  DESCRIPTOR = {
+      'edge_filter': [EdgeEnumKind],
+      'file_spec': FileSpec,
+      'max_num_results': int,
+      'query': str,
+  }
+
+
+class VanityGitOnBorgHostname(Message):
+  DESCRIPTOR = {
+      'name': str,
+      'hostname': str,
+  }
+
+
+class InternalPackage(Message):
+  DESCRIPTOR = {
+      'browse_path_prefix': str,
+      'cs_changelist_num': str,
+      'grok_languages': [str],
+      'grok_name': str,
+      'grok_path_prefix': [str],
+      'id': str,
+      'kythe_languages': [str],
+      'name': str,
+      'repo': str,
+      'vanity_git_on_borg_hostnames': [VanityGitOnBorgHostname],
+  }
+
+
+class StatusResponse(Message):
+  DESCRIPTOR = {
+      'announcement': str,
+      'build_label': str,
+      'internal_package': [InternalPackage],
+      'success': bool,
+  }
+
+
+class GobInfo(Message):
+  DESCRIPTOR = {
+      'commit': str,
+      'path': str,
+      'repo': str,
+  }
+
+
+class DirInfoResponseChild(Message):
+  DESCRIPTOR = {
+      'is_deleted': bool,
+      'is_directory': bool,
+      'name': str,
+      'package_id': str,
+      'path': str,
+      'revision_num': str,
+  }
+
+
+class DirInfoResponseParent(Message):
+  DESCRIPTOR = {
+      'name': str,
+      'path': str,
+      'package_id': str,
+  }
+
+
+class DirInfoResponse(Message):
+  DESCRIPTOR = {
+      'child': [DirInfoResponseChild],
+      'generated': bool,
+      'gob_info': GobInfo,
+      'name': str,
+      'package_id': str,
+      'parent': [DirInfoResponseParent],
+      'path': str,
+      'success': bool,
+  }
+
+
+@message
+class DirInfoRequest(Message):
+  DESCRIPTOR = {
+      'file_spec': FileSpec,
+  }
+
+
+class FileResult(Message):
+  DESCRIPTOR = {
+      'display_name': AnnotatedText,
+      'file': FileSpec,
+      'license': FileSpec,
+      'license_type': str,
+      'size': int,
+  }
+
+
+class SingleMatch(Message):
+  DESCRIPTOR = {
+      'line_number': int,
+      'line_text': str,
+      'match_length': int,
+      'match_offset': int,
+      'post_context_num_lines': int,
+      'post_context_text': str,
+      'pre_context_num_lines': int,
+      'pre_context_text': str,
+      'score': int,
+  }
+
+
+class SearchResult(Message):
+  DESCRIPTOR = {
+      'best_matching_line_number': int,
+      'children': [str],
+      'docid': str,
+      'duplicate': [FileResult],
+      'has_unshown_matches': bool,
+      'hit_max_matches': bool,
+      'is_augmented': bool,
+      'language': str,
+      'match': [SingleMatch],
+      'match_reason': MatchReason,
+      'num_duplicates': int,
+      'num_matches': int,
+      'snippet': [Snippet],
+      'top_file': FileResult,
+  }
+
+
+class SearchResponse(Message):
+  DESCRIPTOR = {
+      'estimated_total_number_of_results': int,
+      'hit_max_matches_per_file': bool,
+      'hit_max_results': bool,
+      'hit_max_to_score': bool,
+      'maybe_skipped_documents': bool,
+      'results_offset': int,
+      'search_result': [SearchResult],
+      'status': int,
+      'status_message': str,
+  }
+
+
+@message
+class SearchRequest(Message):
+  DESCRIPTOR = {
+      'exhaustive': bool,
+      'lines_context': int,
+      'max_num_results': int,
+      'query': str,
+      'return_all_duplicates': bool,
+      'return_all_snippets': bool,
+      'return_decorated_snippets': bool,
+      'return_directories': bool,
+      'return_line_matches': bool,
+      'return_snippets': bool,
+  }
+
+
+class StatusRequest(Message):
+  DESCRIPTOR = {}
+
+
+class CompoundResponse(Message):
+  DESCRIPTOR = {
+      'annotation_response': [AnnotationResponse],
+      'call_graph_response': [CallGraphResponse],
+      'dir_info_response': [DirInfoResponse],
+      'file_info_response': [FileInfoResponse],
+      'search_response': [SearchResponse],
+      'status_response': [StatusResponse],
+      'xref_search_response': [XrefSearchResponse],
+  }
+
+
+@message
+class CompoundRequest(Message):
+  DESCRIPTOR = {
+      'annotation_request': [AnnotationRequest],
+      'call_graph_request': [CallGraphRequest],
+      'dir_info_request': [DirInfoRequest],
+      'file_info_request': [FileInfoRequest],
+      'search_request': [SearchRequest],
+      'status_request': [StatusRequest],
+      'xref_search_request': [XrefSearchRequest],
+  }
diff --git a/tools/codesearch/codesearch/paths.py b/tools/codesearch/codesearch/paths.py
new file mode 100644
index 0000000..cfa3493
--- /dev/null
+++ b/tools/codesearch/codesearch/paths.py
@@ -0,0 +1,28 @@
+# Copyright 2017 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.
+
+import os
+
+
+def GetPackageRelativePath(filename):
+  return os.path.relpath(filename, GetSourceRoot(filename))
+
+
+def GetSourceRoot(filename):
+  # If filename is not absolute, then we are going to assume that it is
+  # relative to the current directory.
+  if not os.path.isabs(filename):
+    filename = os.path.abspath(filename)
+  if not os.path.exists(filename):
+    raise IOError('File not found: {}'.format(filename))
+  source_root = os.path.dirname(filename)
+  while True:
+    gnfile = os.path.join(source_root, 'src', '.gn')
+    if os.path.exists(gnfile):
+      return source_root
+
+    new_package_root = os.path.dirname(source_root)
+    if new_package_root == source_root:
+      raise Exception("Can't determine package root")
+    source_root = new_package_root
diff --git a/tools/codesearch/codesearch/test_file_cache.py b/tools/codesearch/codesearch/test_file_cache.py
new file mode 100644
index 0000000..fd0336d
--- /dev/null
+++ b/tools/codesearch/codesearch/test_file_cache.py
@@ -0,0 +1,48 @@
+# Copyright 2017 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.
+
+from __future__ import absolute_import
+
+import unittest
+import tempfile
+import shutil
+from .file_cache import FileCache
+
+
+class TestFileCache(unittest.TestCase):
+
+  def test_with_no_cache_dir(self):
+    try:
+      f = FileCache()
+      f.put('foo', 'hello')
+      self.assertEqual('hello', f.get('foo'))
+    finally:
+      f.close()
+
+  def test_with_cache_dir(self):
+    f = None
+    g = None
+    test_dir = None
+    try:
+      test_dir = tempfile.mkdtemp()
+      f = FileCache(cache_dir=test_dir)
+      f.put('foo', 'hello')
+      f.close()
+      f = None
+
+      g = FileCache(cache_dir=test_dir)
+      self.assertEqual('hello', g.get('foo'))
+      g.close()
+      g = None
+    finally:
+      if f:
+        f.close()
+      if g:
+        g.close()
+      if test_dir:
+        shutil.rmtree(test_dir)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tools/codesearch/codesearch/test_messages.py b/tools/codesearch/codesearch/test_messages.py
new file mode 100644
index 0000000..9e699ac8
--- /dev/null
+++ b/tools/codesearch/codesearch/test_messages.py
@@ -0,0 +1,108 @@
+# Copyright 2017 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.
+
+from __future__ import absolute_import
+
+import unittest
+
+from .messages import Message, message
+
+
+@message
+class Foo(Message):
+  DESCRIPTOR = {'x': int}
+
+
+class Bar(Message):
+  DESCRIPTOR = {'x': int, 'y': Message.PARENT_TYPE}
+
+
+class Baz(Message):
+  DESCRIPTOR = {'x': int, 'y': [Message.PARENT_TYPE]}
+
+
+class Qux(Message):
+  FOO = 1
+  BAR = 2
+
+  DESCRIPTOR = int
+
+
+class Quux(Message):
+  DESCRIPTOR = {'x': Qux}
+
+
+class TestProto(unittest.TestCase):
+
+  def test_proto_bridge(self):
+    v = Foo()
+    v.x = 3
+    self.assertEqual(v.AsQueryString(), [('x', '3')])
+
+  def test_from_json_string_1(self):
+    v = Message.FromJsonString('{"x": 3}')
+    self.assertEqual(v.x, 3)
+
+  def test_from_json_string_2(self):
+    v = Foo.FromJsonString('{"x": 3}')
+    self.assertTrue(isinstance(v, Foo))
+    self.assertTrue(isinstance(v.x, int))
+    self.assertEqual(v.x, 3)
+
+  def test_from_json_string_3(self):
+    v = Bar.FromJsonString('{"x": 3, "y": {"x": 4}}')
+    self.assertTrue(isinstance(v, Bar))
+    self.assertTrue(isinstance(v.y, Bar))
+    self.assertEqual(v.x, 3)
+    self.assertEqual(v.y.x, 4)
+
+  def test_from_json_string_4(self):
+    v = Foo.FromJsonString('{"y": 3}')
+    self.assertTrue(isinstance(v, Foo))
+
+  def test_from_json_string_5(self):
+    v = Foo.FromJsonString('{"y": 3}')
+    self.assertTrue(isinstance(v, Foo))
+    self.assertEqual(v.y, 3)
+
+  def test_from_json_string_6(self):
+    v = Quux.FromJsonString('{"x": 3}')
+    self.assertTrue(isinstance(v, Quux))
+    self.assertTrue(isinstance(v.x, int))
+    self.assertEqual(v.x, 3)
+
+  def test_from_json_string_7(self):
+    v = Quux.FromJsonString('{"x": "FOO"}')
+    self.assertTrue(isinstance(v, Quux))
+    self.assertTrue(isinstance(v.x, int))
+    self.assertEqual(v.x, 1)
+
+  def test_from_shallow_dict_1(self):
+    v = Baz.FromShallowDict({'x': 3, 'y': [{'x': 4}, {'x': 5}]})
+    self.assertTrue(isinstance(v, Baz))
+    self.assertTrue(isinstance(v.y, list))
+    self.assertTrue(isinstance(v.y[0], Baz))
+    self.assertTrue(isinstance(v.y[1], Baz))
+
+
+class TestConstructor(unittest.TestCase):
+
+  def test_empty_class(self):
+    f = Foo()
+    self.assertFalse(hasattr(f, 'x'))
+
+  def test_class_with_known_keyword(self):
+    f = Foo(x=10)
+    self.assertTrue(hasattr(f, 'x'))
+    self.assertEqual(10, f.x)
+
+  def test_class_with_unknown_keyword(self):
+    f = Foo(x=10, y=9)
+    self.assertTrue(hasattr(f, 'x'))
+    self.assertTrue(hasattr(f, 'y'))
+    self.assertEqual(9, f.y)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/ui/gl/gl_image.cc b/ui/gl/gl_image.cc
index c11006d..8525c6c 100644
--- a/ui/gl/gl_image.cc
+++ b/ui/gl/gl_image.cc
@@ -6,6 +6,11 @@
 
 namespace gl {
 
+bool GLImage::BindTexImageWithInternalformat(unsigned target,
+                                             unsigned internalformat) {
+  return false;
+}
+
 bool GLImage::EmulatingRGB() const {
   return false;
 }
diff --git a/ui/gl/gl_image.h b/ui/gl/gl_image.h
index f0d0842..be3d6f4c 100644
--- a/ui/gl/gl_image.h
+++ b/ui/gl/gl_image.h
@@ -43,6 +43,13 @@
   // It is valid for an implementation to always return false.
   virtual bool BindTexImage(unsigned target) = 0;
 
+  // Bind image to texture currently bound to |target|, forcing the texture's
+  // internal format to the specified one. This is a feature not available on
+  // all platforms. Returns true on success.  It is valid for an implementation
+  // to always return false.
+  virtual bool BindTexImageWithInternalformat(unsigned target,
+                                              unsigned internalformat);
+
   // Release image from texture currently bound to |target|.
   virtual void ReleaseTexImage(unsigned target) = 0;
 
diff --git a/ui/gl/gl_image_io_surface.h b/ui/gl/gl_image_io_surface.h
index 70962e6d..9cffcec 100644
--- a/ui/gl/gl_image_io_surface.h
+++ b/ui/gl/gl_image_io_surface.h
@@ -45,6 +45,8 @@
   gfx::Size GetSize() override;
   unsigned GetInternalFormat() override;
   bool BindTexImage(unsigned target) override;
+  bool BindTexImageWithInternalformat(unsigned target,
+                                      unsigned internalformat) override;
   void ReleaseTexImage(unsigned target) override {}
   bool CopyTexImage(unsigned target) override;
   bool CopyTexSubImage(unsigned target,
diff --git a/ui/gl/gl_image_io_surface.mm b/ui/gl/gl_image_io_surface.mm
index 8e20830..fc4df49 100644
--- a/ui/gl/gl_image_io_surface.mm
+++ b/ui/gl/gl_image_io_surface.mm
@@ -168,8 +168,9 @@
 }
 
 // When an IOSurface is bound to a texture with internalformat "GL_RGB", many
-// OpenGL operations are broken. Therefore, never allow an IOSurface to be bound
-// with GL_RGB. https://crbug.com/595948.
+// OpenGL operations are broken. Therefore, don't allow an IOSurface to be bound
+// with GL_RGB unless overridden via BindTexImageWithInternalformat.
+// crbug.com/595948, crbug.com/699566.
 GLenum ConvertRequestedInternalFormat(GLenum internalformat) {
   if (internalformat == GL_RGB)
     return GL_RGBA;
@@ -237,6 +238,11 @@
 }
 
 bool GLImageIOSurface::BindTexImage(unsigned target) {
+  return BindTexImageWithInternalformat(target, 0);
+}
+
+bool GLImageIOSurface::BindTexImageWithInternalformat(unsigned target,
+                                                      unsigned internalformat) {
   DCHECK(thread_checker_.CalledOnValidThread());
   TRACE_EVENT0("gpu", "GLImageIOSurface::BindTexImage");
   base::TimeTicks start_time = base::TimeTicks::Now();
@@ -258,10 +264,12 @@
       static_cast<CGLContextObj>(GLContext::GetCurrent()->GetHandle());
 
   DCHECK(io_surface_);
-  CGLError cgl_error =
-      CGLTexImageIOSurface2D(cgl_context, target, TextureFormat(format_),
-                             size_.width(), size_.height(), DataFormat(format_),
-                             DataType(format_), io_surface_.get(), 0);
+
+  GLenum texture_format =
+      internalformat ? internalformat : TextureFormat(format_);
+  CGLError cgl_error = CGLTexImageIOSurface2D(
+      cgl_context, target, texture_format, size_.width(), size_.height(),
+      DataFormat(format_), DataType(format_), io_surface_.get(), 0);
   if (cgl_error != kCGLNoError) {
     LOG(ERROR) << "Error in CGLTexImageIOSurface2D: "
                << CGLErrorString(cgl_error);