Import Cobalt 21.master.0.260628
diff --git a/src/components/client_update_protocol/BUILD.gn b/src/components/client_update_protocol/BUILD.gn
new file mode 100644
index 0000000..e22ee24
--- /dev/null
+++ b/src/components/client_update_protocol/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright 2016 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.
+
+static_library("client_update_protocol") {
+  sources = [
+    "ecdsa.cc",
+    "ecdsa.h",
+  ]
+
+  deps = [
+    "//base",
+    "//crypto",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "ecdsa_unittest.cc",
+  ]
+
+  deps = [
+    ":client_update_protocol",
+    "//base",
+    "//crypto",
+    "//testing/gtest",
+  ]
+}
diff --git a/src/components/client_update_protocol/client_update_protocol.gyp b/src/components/client_update_protocol/client_update_protocol.gyp
new file mode 100644
index 0000000..b49eef7
--- /dev/null
+++ b/src/components/client_update_protocol/client_update_protocol.gyp
@@ -0,0 +1,43 @@
+# Copyright 2019 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'targets': [
+    {
+      'target_name': 'client_update_protocol',
+      'type': 'static_library',
+      'sources': [
+        'ecdsa.cc',
+        'ecdsa.h',
+      ],
+      'dependencies': [
+        '<(DEPTH)/crypto/crypto.gyp:crypto',
+        '<(DEPTH)/base/base.gyp:base',
+      ],
+    },
+    {
+      'target_name': 'client_update_protocol_unittests',
+      'type': 'static_library',
+      'sources': [
+        'ecdsa_unittest.cc',
+      ],
+      'dependencies': [
+        'client_update_protocol',
+        '<(DEPTH)/crypto/crypto.gyp:crypto',
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+      ],
+    },
+  ]
+}
diff --git a/src/components/client_update_protocol/ecdsa.cc b/src/components/client_update_protocol/ecdsa.cc
new file mode 100644
index 0000000..53b7e90
--- /dev/null
+++ b/src/components/client_update_protocol/ecdsa.cc
@@ -0,0 +1,188 @@
+// Copyright 2016 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 "components/client_update_protocol/ecdsa.h"
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "crypto/random.h"
+#include "crypto/sha2.h"
+#include "crypto/signature_verifier.h"
+
+namespace client_update_protocol {
+
+namespace {
+
+std::vector<uint8_t> SHA256HashStr(const base::StringPiece& str) {
+  std::vector<uint8_t> result(crypto::kSHA256Length);
+  crypto::SHA256HashString(str, &result.front(), result.size());
+  return result;
+}
+
+std::vector<uint8_t> SHA256HashVec(const std::vector<uint8_t>& vec) {
+  if (vec.empty())
+    return SHA256HashStr(base::StringPiece());
+
+  return SHA256HashStr(base::StringPiece(
+      reinterpret_cast<const char*>(&vec.front()), vec.size()));
+}
+
+bool ParseETagHeader(const base::StringPiece& etag_header_value_in,
+                     std::vector<uint8_t>* ecdsa_signature_out,
+                     std::vector<uint8_t>* request_hash_out) {
+  DCHECK(ecdsa_signature_out);
+  DCHECK(request_hash_out);
+
+  // The ETag value is a UTF-8 string, formatted as "S:H", where:
+  // * S is the ECDSA signature in DER-encoded ASN.1 form, converted to hex.
+  // * H is the SHA-256 hash of the observed request body, standard hex format.
+  // A Weak ETag is formatted as W/"S:H". This function treats it the same as a
+  // strong ETag.
+  base::StringPiece etag_header_value(etag_header_value_in);
+
+  // Remove the weak prefix, then remove the begin and the end quotes.
+  const char kWeakETagPrefix[] = "W/";
+  if (etag_header_value.starts_with(kWeakETagPrefix))
+    etag_header_value.remove_prefix(base::size(kWeakETagPrefix) - 1);
+  if (etag_header_value.size() >= 2 && etag_header_value.starts_with("\"") &&
+      etag_header_value.ends_with("\"")) {
+    etag_header_value.remove_prefix(1);
+    etag_header_value.remove_suffix(1);
+  }
+
+  const base::StringPiece::size_type delim_pos = etag_header_value.find(':');
+  if (delim_pos == base::StringPiece::npos || delim_pos == 0 ||
+      delim_pos == etag_header_value.size() - 1)
+    return false;
+
+  const base::StringPiece sig_hex = etag_header_value.substr(0, delim_pos);
+  const base::StringPiece hash_hex = etag_header_value.substr(delim_pos + 1);
+
+  // Decode the ECDSA signature. Don't bother validating the contents of it;
+  // the SignatureValidator class will handle the actual DER decoding and
+  // ASN.1 parsing. Check for an expected size range only -- valid ECDSA
+  // signatures are between 8 and 72 bytes.
+  if (!base::HexStringToBytes(sig_hex, ecdsa_signature_out))
+    return false;
+  if (ecdsa_signature_out->size() < 8 || ecdsa_signature_out->size() > 72)
+    return false;
+
+  // Decode the SHA-256 hash; it should be exactly 32 bytes, no more or less.
+  if (!base::HexStringToBytes(hash_hex, request_hash_out))
+    return false;
+  if (request_hash_out->size() != crypto::kSHA256Length)
+    return false;
+
+  return true;
+}
+
+}  // namespace
+
+Ecdsa::Ecdsa(int key_version, const base::StringPiece& public_key)
+    : pub_key_version_(key_version),
+      public_key_(public_key.begin(), public_key.end()) {}
+
+Ecdsa::~Ecdsa() {}
+
+std::unique_ptr<Ecdsa> Ecdsa::Create(int key_version,
+                                     const base::StringPiece& public_key) {
+  DCHECK_GT(key_version, 0);
+  DCHECK(!public_key.empty());
+
+  return base::WrapUnique(new Ecdsa(key_version, public_key));
+}
+
+void Ecdsa::OverrideNonceForTesting(int key_version, uint32_t nonce) {
+  DCHECK(!request_query_cup2key_.empty());
+  request_query_cup2key_ = base::StringPrintf("%d:%u", pub_key_version_, nonce);
+}
+
+void Ecdsa::SignRequest(const base::StringPiece& request_body,
+                        std::string* query_params) {
+  DCHECK(query_params);
+
+  // Generate a random nonce to use for freshness, build the cup2key query
+  // string, and compute the SHA-256 hash of the request body. Set these
+  // two pieces of data aside to use during ValidateResponse().
+  uint32_t nonce = 0;
+  crypto::RandBytes(&nonce, sizeof(nonce));
+  request_query_cup2key_ = base::StringPrintf("%d:%u", pub_key_version_, nonce);
+  request_hash_ = SHA256HashStr(request_body);
+
+  // Return the query string for the user to send with the request.
+  std::string request_hash_hex =
+      base::HexEncode(&request_hash_.front(), request_hash_.size());
+  request_hash_hex = base::ToLowerASCII(request_hash_hex);
+
+  *query_params = base::StringPrintf("cup2key=%s&cup2hreq=%s",
+                                     request_query_cup2key_.c_str(),
+                                     request_hash_hex.c_str());
+}
+
+bool Ecdsa::ValidateResponse(const base::StringPiece& response_body,
+                             const base::StringPiece& server_etag) {
+  DCHECK(!request_hash_.empty());
+  DCHECK(!request_query_cup2key_.empty());
+
+  if (response_body.empty() || server_etag.empty())
+    return false;
+
+  // Break the ETag into its two components (the ECDSA signature, and the
+  // hash of the request that the server observed) and decode to byte buffers.
+  std::vector<uint8_t> signature;
+  std::vector<uint8_t> observed_request_hash;
+  if (!ParseETagHeader(server_etag, &signature, &observed_request_hash))
+    return false;
+
+  // Check that the server's observed request hash is equal to the original
+  // request hash. (This is a quick rejection test; the signature test is
+  // authoritative, but slower.)
+  DCHECK_EQ(request_hash_.size(), crypto::kSHA256Length);
+  if (observed_request_hash.size() != crypto::kSHA256Length)
+    return false;
+  if (!std::equal(observed_request_hash.begin(), observed_request_hash.end(),
+                  request_hash_.begin()))
+    return false;
+
+  // Next, build the buffer that the server will have signed on its end:
+  //   hash( hash(request) | hash(response) | cup2key_query_string )
+  // When building the client's version of the buffer, it's important to use
+  // the original request hash that it attempted to send, and not the observed
+  // request hash that the server sent back to us.
+  const std::vector<uint8_t> response_hash = SHA256HashStr(response_body);
+
+  std::vector<uint8_t> signed_message;
+  signed_message.insert(signed_message.end(), request_hash_.begin(),
+                        request_hash_.end());
+  signed_message.insert(signed_message.end(), response_hash.begin(),
+                        response_hash.end());
+  signed_message.insert(signed_message.end(), request_query_cup2key_.begin(),
+                        request_query_cup2key_.end());
+
+  const std::vector<uint8_t> signed_message_hash =
+      SHA256HashVec(signed_message);
+
+  // Initialize the signature verifier.
+  crypto::SignatureVerifier verifier;
+  if (!verifier.VerifyInit(crypto::SignatureVerifier::ECDSA_SHA256, signature,
+                           public_key_)) {
+    DVLOG(1) << "Couldn't init SignatureVerifier.";
+    return false;
+  }
+
+  // If the verification fails, that implies one of two outcomes:
+  // * The signature was modified
+  // * The buffer that the server signed does not match the buffer that the
+  //   client assembled -- implying that either request body or response body
+  //   was modified, or a different nonce value was used.
+  verifier.VerifyUpdate(signed_message_hash);
+  return verifier.VerifyFinal();
+}
+
+}  // namespace client_update_protocol
diff --git a/src/components/client_update_protocol/ecdsa.h b/src/components/client_update_protocol/ecdsa.h
new file mode 100644
index 0000000..24e4171
--- /dev/null
+++ b/src/components/client_update_protocol/ecdsa.h
@@ -0,0 +1,88 @@
+// Copyright 2016 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 COMPONENTS_CLIENT_UPDATE_PROTOCOL_ECDSA_H_
+#define COMPONENTS_CLIENT_UPDATE_PROTOCOL_ECDSA_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+
+namespace client_update_protocol {
+
+// Client Update Protocol v2, or CUP-ECDSA, is used by Google Update (Omaha)
+// servers to ensure freshness and authenticity of server responses over HTTP,
+// without the overhead of HTTPS -- namely, no PKI, no guarantee of privacy, and
+// no request replay protection.
+//
+// CUP-ECDSA relies on a single signing operation using ECDSA with SHA-256,
+// instead of the original CUP which used HMAC-SHA1 with a random signing key
+// encrypted using RSA.
+//
+// Each |Ecdsa| object represents a single network ping in flight -- a call to
+// SignRequest() generates internal state that will be used by
+// ValidateResponse().
+class Ecdsa {
+ public:
+  ~Ecdsa();
+
+  // Initializes this instance of CUP-ECDSA with a versioned public key.
+  // |key_version| must be non-negative. |public_key| is expected to be a
+  // DER-encoded ASN.1 SubjectPublicKeyInfo containing an ECDSA public key.
+  // Returns a NULL pointer on failure.
+  static std::unique_ptr<Ecdsa> Create(int key_version,
+                                       const base::StringPiece& public_key);
+
+  // Generates freshness/authentication data for an outgoing ping.
+  // |request_body| contains the body of the ping in UTF-8.  On return,
+  // |query_params| contains a set of query parameters (in UTF-8) to be appended
+  // to the URL.
+  //
+  // This method will store internal state in this instance used by calls to
+  // ValidateResponse(); if you need to have multiple pings in flight,
+  // initialize a separate CUP-ECDSA instance for each one.
+  void SignRequest(const base::StringPiece& request_body,
+                   std::string* query_params);
+
+  // Validates a response given to a ping previously signed with
+  // SignRequest(). |response_body| contains the body of the response in
+  // UTF-8. |signature| contains the ECDSA signature and observed request
+  // hash. Returns true if the response is valid and the observed request hash
+  // matches the sent hash.  This method uses internal state that is set by a
+  // prior SignRequest() call.
+  bool ValidateResponse(const base::StringPiece& response_body,
+                        const base::StringPiece& signature);
+
+  // Sets the key and nonce that were used to generate a signature that is baked
+  // into a unit test.
+  void OverrideNonceForTesting(int key_version, uint32_t nonce);
+
+ private:
+  Ecdsa(int key_version, const base::StringPiece& public_key);
+
+  // The server keeps multiple signing keys; a version must be sent so that
+  // the correct signing key is used to sign the assembled message.
+  const int pub_key_version_;
+
+  // The ECDSA public key to use for verifying response signatures.
+  const std::vector<uint8_t> public_key_;
+
+  // The SHA-256 hash of the XML request.  This is modified on each call to
+  // SignRequest(), and checked by ValidateResponse().
+  std::vector<uint8_t> request_hash_;
+
+  // The query string containing key version and nonce in UTF-8 form.  This is
+  // modified on each call to SignRequest(), and checked by ValidateResponse().
+  std::string request_query_cup2key_;
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(Ecdsa);
+};
+
+}  // namespace client_update_protocol
+
+#endif  // COMPONENTS_CLIENT_UPDATE_PROTOCOL_ECDSA_H_
diff --git a/src/components/client_update_protocol/ecdsa_unittest.cc b/src/components/client_update_protocol/ecdsa_unittest.cc
new file mode 100644
index 0000000..71c7441
--- /dev/null
+++ b/src/components/client_update_protocol/ecdsa_unittest.cc
@@ -0,0 +1,294 @@
+// Copyright 2016 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 "components/client_update_protocol/ecdsa.h"
+
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "crypto/random.h"
+#include "crypto/secure_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace client_update_protocol {
+
+namespace {
+
+std::string GetPublicKeyForTesting() {
+  // How to generate this key:
+  //   openssl ecparam -genkey -name prime256v1 -out ecpriv.pem
+  //   openssl ec -in ecpriv.pem -pubout -out ecpub.pem
+
+  static const char kCupEcdsaTestKey_Base64[] =
+      "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJNOjKyN6UHyUGkGow+xCmQthQXUo"
+      "9sd7RIXSpVIM768UlbGb/5JrnISjSYejCc/pxQooI6mJTzWL3pZb5TA1DA==";
+
+  std::string result;
+  if (!base::Base64Decode(std::string(kCupEcdsaTestKey_Base64), &result))
+    return std::string();
+
+  return result;
+}
+
+}  // end namespace
+
+class CupEcdsaTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    cup_ = Ecdsa::Create(8, GetPublicKeyForTesting());
+    ASSERT_TRUE(cup_.get());
+  }
+
+  Ecdsa& CUP() { return *cup_; }
+
+ private:
+  std::unique_ptr<Ecdsa> cup_;
+};
+
+TEST_F(CupEcdsaTest, SignRequest) {
+  static const char kRequest[] = "TestSequenceForCupEcdsaUnitTest";
+  static const char kRequestHash[] =
+      "&cup2hreq="
+      "cde1f7dc1311ed96813057ca321c2f5a17ea2c9c776ee0eb31965f7985a3074a";
+  static const char kKeyId[] = "cup2key=8:";
+
+  std::string query;
+  CUP().SignRequest(kRequest, &query);
+  std::string query2;
+  CUP().SignRequest(kRequest, &query2);
+
+  EXPECT_FALSE(query.empty());
+  EXPECT_FALSE(query2.empty());
+  EXPECT_EQ(0UL, query.find(kKeyId));
+  EXPECT_EQ(0UL, query2.find(kKeyId));
+  EXPECT_NE(std::string::npos, query.find(kRequestHash));
+  EXPECT_NE(std::string::npos, query2.find(kRequestHash));
+
+  // In theory, this is a flaky test, as there's nothing preventing the RNG
+  // from returning the same nonce twice in a row. In practice, this should
+  // be fine.
+  EXPECT_NE(query, query2);
+}
+
+TEST_F(CupEcdsaTest, ValidateResponse_TestETagParsing) {
+  // Invalid ETags must be gracefully rejected without a crash.
+  std::string query_discard;
+  CUP().SignRequest("Request_A", &query_discard);
+  CUP().OverrideNonceForTesting(8, 12345);
+
+  // Expect a pass for a well-formed etag.
+  EXPECT_TRUE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
+      "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
+
+  // Reject empty etags.
+  EXPECT_FALSE(CUP().ValidateResponse("Response_A", ""));
+
+  // Reject etags with zero-length hashes or signatures, even if the other
+  // component is wellformed.
+  EXPECT_FALSE(CUP().ValidateResponse("Response_A", ":"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
+      "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
+      ":"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
+
+  // Reject etags with non-hex content in either component.
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "02207fb15d24e66c168ac150458__ae51f843c4858e27d41be3f9396d4919bbd5656"
+      "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
+      "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
+      ":2727bc2b3c33feb6800a830f4055901d__7d65a84184c5fbeb3f816db0a243f5"));
+
+  // Reject etags where either/both component has a length that's not a
+  // multiple of 2 (i.e. not a valid hex encoding).
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
+      "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
+      "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
+      "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f"));
+
+  // Reject etags where the hash is even, but not 256 bits.
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
+      "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
+      "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5ff"));
+
+  // Reject etags where the signature field is too small to be valid. (Note that
+  // the case isn't even a signature -- it's a validly encoded ASN.1 NULL.)
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "0500"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
+
+  // Reject etags where the signature field is too big to be a valid signature.
+  // (This is a validly formed structure, but both ints are over 256 bits.)
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3048"
+      "202207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+      "202207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5ff"));
+
+  // Reject etags where the signature is valid DER-encoded ASN.1, but is not
+  // an ECDSA signature. (This is actually stressing crypto's SignatureValidator
+  // library, and not CUP's use of it, but it's worth testing here.)  Cases:
+  // * Something that's not a sequence
+  // * Sequences that contain things other than ints (i.e. octet strings)
+  // * Sequences that contain a negative int.
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "0406020100020100"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "06200123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+      "06200123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3046"
+      "02047fffffff"
+      "0220ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
+
+  // Reject etags where the signature is not a valid DER encoding. (Again, this
+  // is stressing SignatureValidator.)  Test cases are:
+  // * No length field
+  // * Zero length field
+  // * One of the ints has truncated content
+  // * One of the ints has content longer than its length field
+  // * A positive int is improperly zero-padded
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "30"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3000"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "02207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+      "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "02207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00"
+      "02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3044"
+      "022000007f24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
+      "02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
+}
+
+TEST_F(CupEcdsaTest, ValidateResponse_TestSigning) {
+  std::string query_discard;
+  CUP().SignRequest("Request_A", &query_discard);
+  CUP().OverrideNonceForTesting(8, 12345);
+
+  // How to generate an ECDSA signature:
+  //   echo -n Request_A | sha256sum | cut -d " " -f 1 > h
+  //   echo -n Response_A | sha256sum | cut -d " " -f 1 >> h
+  //   cat h | xxd -r -p > hbin
+  //   echo -n 8:12345 >> hbin
+  //   sha256sum hbin | cut -d " " -f 1 | xxd -r -p > hbin2
+  //   openssl dgst -hex -sha256 -sign ecpriv.pem hbin2 | cut -d " " -f 2 > sig
+  //   echo -n :Request_A | sha256sum | cut -d " " -f 1 >> sig
+  //   cat sig
+  // It's useful to throw this in a bash script and parameterize it if you're
+  // updating this unit test.
+
+  // Valid case:
+  //  * Send "Request_A" with key 8 / nonce 12345 to server.
+  //  * Receive "Response_A", signature, and observed request hash from server.
+  //  * Signature signs HASH(Request_A) | HASH(Response_A) | 8:12345.
+  //  * Observed hash matches HASH(Request_A).
+  EXPECT_TRUE(CUP().ValidateResponse(
+      "Response_A",
+      "3045022077a2d004f1643a92af5d356877c3434c46519ce32882d6e30ef6d154ee9775e3"
+      "022100aca63c77d34152bdc0918ae0629e82b59314e5459f607cdc5ac95f1a4b7c31a2"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
+
+  // Failure case: "Request_A" made it to the server intact, but the response
+  // body is modified to "Response_B" on return.  The signature is now invalid.
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_B",
+      "3045022077a2d004f1643a92af5d356877c3434c46519ce32882d6e30ef6d154ee9775e3"
+      "022100aca63c77d34152bdc0918ae0629e82b59314e5459f607cdc5ac95f1a4b7c31a2"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
+
+  // Failure case: Request body was modified to "Request_B" before it reached
+  // the server.  Test a fast reject based on the observed_hash parameter.
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_B",
+      "304402206289a7765f0371c7c48796779747f1166707d5937a99af518845f44af95876"
+      "8c0220139fe935fde3e6b416ee742f91c6a480113762d78d889a2661de37576866d21c"
+      ":80e3ef1b373efe5f2a8383a0cf9c89fb2e0cbb8e85db4813655ff5dc05009e7e"));
+
+  // Failure case: Request body was modified to "Request_B" before it reached
+  // the server.  Test a slow reject based on a signature mismatch.
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_B",
+      "304402206289a7765f0371c7c48796779747f1166707d5937a99af518845f44af95876"
+      "8c0220139fe935fde3e6b416ee742f91c6a480113762d78d889a2661de37576866d21c"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
+
+  // Failure case: Request/response are intact, but the signature is invalid
+  // because it was signed against a different nonce (67890).
+  EXPECT_FALSE(CUP().ValidateResponse(
+      "Response_A",
+      "3046022100d3bbb1fb4451c8e04a07fe95404cc39121ed0e0bc084f87de19d52eee50a97"
+      "bf022100dd7d41d467be2af98d9116b0c7ba09740d54578c02a02f74da5f089834be3403"
+      ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
+}
+
+}  // namespace client_update_protocol
diff --git a/src/components/courgette/courgette.h b/src/components/courgette/courgette.h
new file mode 100644
index 0000000..b80c3ca
--- /dev/null
+++ b/src/components/courgette/courgette.h
@@ -0,0 +1,118 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_COURGETTE_COURGETTE_H_
+#define COMPONENTS_COURGETTE_COURGETTE_H_
+
+#include <stddef.h>  // Required to define size_t on GCC
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+
+namespace courgette {
+
+// Status codes for Courgette APIs.
+//
+// Client code should only rely on the distintion between C_OK and the other
+// status codes.
+//
+enum Status {
+  C_OK = 1,  // Successful operation.
+
+  C_GENERAL_ERROR = 2,  // Error other than listed below.
+
+  C_READ_OPEN_ERROR = 3,  // Could not open input file for reading.
+  C_READ_ERROR = 4,       // Could not read from opened input file.
+
+  C_WRITE_OPEN_ERROR = 3,  // Could not open output file for writing.
+  C_WRITE_ERROR = 4,       // Could not write to opened output file.
+
+  C_BAD_ENSEMBLE_MAGIC = 5,    // Ensemble patch has bad magic.
+  C_BAD_ENSEMBLE_VERSION = 6,  // Ensemble patch has wrong version.
+  C_BAD_ENSEMBLE_HEADER = 7,   // Ensemble patch has corrupt header.
+  C_BAD_ENSEMBLE_CRC = 8,      // Ensemble patch has corrupt data.
+
+  C_BAD_TRANSFORM = 12,  // Transform mis-specified.
+  C_BAD_BASE = 13,       // Base for transform malformed.
+
+  C_BINARY_DIFF_CRC_ERROR = 14,  // Internal diff input doesn't have expected
+                                 // CRC.
+
+  // Internal errors.
+  C_STREAM_ERROR = 20,            // Unexpected error from streams.h.
+  C_STREAM_NOT_CONSUMED = 21,     // Stream has extra data, is expected to be
+                                  // used up.
+  C_SERIALIZATION_FAILED = 22,    //
+  C_DESERIALIZATION_FAILED = 23,  //
+  C_INPUT_NOT_RECOGNIZED = 24,    // Unrecognized input (not an executable).
+  C_DISASSEMBLY_FAILED = 25,      //
+  C_ASSEMBLY_FAILED = 26,         //
+  C_ADJUSTMENT_FAILED = 27,       //
+};
+
+// What type of executable is something
+// This is part of the patch format. Never reuse an id number.
+enum ExecutableType {
+  EXE_UNKNOWN = 0,
+  EXE_WIN_32_X86 = 1,
+  EXE_ELF_32_X86 = 2,
+  EXE_ELF_32_ARM = 3,
+  EXE_WIN_32_X64 = 4,
+};
+
+class SinkStream;
+class SinkStreamSet;
+class SourceStream;
+
+class AssemblyProgram;
+class EncodedProgram;
+
+// Applies the patch to the bytes in |old| and writes the transformed ensemble
+// to |output|.
+// Returns C_OK unless something went wrong.
+Status ApplyEnsemblePatch(SourceStream* old,
+                          SourceStream* patch,
+                          SinkStream* output);
+
+// Applies the patch in |patch_file| to the bytes in |old_file| and writes the
+// transformed ensemble to |new_file|.
+// Returns C_OK unless something went wrong.
+// This function first validates that the patch file has a proper header, so the
+// function can be used to 'try' a patch.
+Status ApplyEnsemblePatch(base::File old_file,
+                          base::File patch_file,
+                          base::File new_file);
+
+// Applies the patch in |patch_file_name| to the bytes in |old_file_name| and
+// writes the transformed ensemble to |new_file_name|.
+// Returns C_OK unless something went wrong.
+// This function first validates that the patch file has a proper header, so the
+// function can be used to 'try' a patch.
+Status ApplyEnsemblePatch(const base::FilePath::CharType* old_file_name,
+                          const base::FilePath::CharType* patch_file_name,
+                          const base::FilePath::CharType* new_file_name);
+
+// Generates a patch that will transform the bytes in |old| into the bytes in
+// |target|.
+// Returns C_OK unless something when wrong (unexpected).
+Status GenerateEnsemblePatch(SourceStream* old,
+                             SourceStream* target,
+                             SinkStream* patch);
+
+// Serializes |encoded| into the stream set.
+// Returns C_OK if succeeded, otherwise returns an error status.
+Status WriteEncodedProgram(EncodedProgram* encoded, SinkStreamSet* sink);
+
+// Assembles |encoded|, emitting the bytes into |buffer|.
+// Returns C_OK if succeeded, otherwise returns an error status and leaves
+// |buffer| in an undefined state.
+Status Assemble(EncodedProgram* encoded, SinkStream* buffer);
+
+// Adjusts |program| to look more like |model|.
+//
+Status Adjust(const AssemblyProgram& model, AssemblyProgram* program);
+
+}  // namespace courgette
+
+#endif  // COMPONENTS_COURGETTE_COURGETTE_H_
diff --git a/src/components/courgette/third_party/bsdiff/bsdiff.h b/src/components/courgette/third_party/bsdiff/bsdiff.h
new file mode 100644
index 0000000..4abb860
--- /dev/null
+++ b/src/components/courgette/third_party/bsdiff/bsdiff.h
@@ -0,0 +1,107 @@
+// Copyright 2003, 2004 Colin Percival
+// All rights reserved
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted providing that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+//    notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+//    notice, this list of conditions and the following disclaimer in the
+//    documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// For the terms under which this work may be distributed, please see
+// the adjoining file "LICENSE".
+//
+// Changelog:
+// 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to
+//              the header, and make all the types 32-bit.
+//                --Benjamin Smedberg <benjamin@smedbergs.us>
+// 2009-03-31 - Change to use Streams.  Move CRC code to crc.{h,cc}
+//              Changed status to an enum, removed unused status codes.
+//                --Stephen Adams <sra@chromium.org>
+// 2013-04-10 - Added wrapper to apply a patch directly to files.
+//                --Joshua Pawlicki <waffles@chromium.org>
+
+// Copyright 2016 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 COMPONENTS_COURGETTE_THIRD_PARTY_BSDIFF_BSDIFF_H_
+#define COMPONENTS_COURGETTE_THIRD_PARTY_BSDIFF_BSDIFF_H_
+
+#include <stdint.h>
+
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+
+namespace courgette {
+class SourceStream;
+class SinkStream;
+}  // namespace courgette
+
+namespace bsdiff {
+
+enum BSDiffStatus {
+  OK = 0,
+  MEM_ERROR = 1,
+  CRC_ERROR = 2,
+  READ_ERROR = 3,
+  UNEXPECTED_ERROR = 4,
+  WRITE_ERROR = 5
+};
+
+// Creates a binary patch.
+//
+BSDiffStatus CreateBinaryPatch(courgette::SourceStream* old_stream,
+                               courgette::SourceStream* new_stream,
+                               courgette::SinkStream* patch_stream);
+
+// Applies the given patch file to a given source file. This method validates
+// the CRC of the original file stored in the patch file, before applying the
+// patch to it.
+//
+BSDiffStatus ApplyBinaryPatch(courgette::SourceStream* old_stream,
+                              courgette::SourceStream* patch_stream,
+                              courgette::SinkStream* new_stream);
+
+// As above, but simply takes base::Files.
+BSDiffStatus ApplyBinaryPatch(base::File old_stream,
+                              base::File patch_stream,
+                              base::File new_stream);
+
+// As above, but simply takes the file paths.
+BSDiffStatus ApplyBinaryPatch(const base::FilePath& old_stream,
+                              const base::FilePath& patch_stream,
+                              const base::FilePath& new_stream);
+
+// The following declarations are common to the patch-creation and
+// patch-application code.
+
+// The patch stream starts with a MBSPatchHeader.
+typedef struct MBSPatchHeader_ {
+  char tag[8];      // Contains MBS_PATCH_HEADER_TAG.
+  uint32_t slen;    // Length of the file to be patched.
+  uint32_t scrc32;  // CRC32 of the file to be patched.
+  uint32_t dlen;    // Length of the result file.
+} MBSPatchHeader;
+
+// This is the value for the tag field.  Must match length exactly, not counting
+// null at end of string.
+#define MBS_PATCH_HEADER_TAG "GBSDIF42"
+
+}  // namespace bsdiff
+
+#endif  // COMPONENTS_COURGETTE_THIRD_PARTY_BSDIFF_BSDIFF_H_
diff --git a/src/components/crx_file/BUILD.gn b/src/components/crx_file/BUILD.gn
new file mode 100644
index 0000000..57667db
--- /dev/null
+++ b/src/components/crx_file/BUILD.gn
@@ -0,0 +1,81 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+static_library("crx_file") {
+  sources = [
+    "crx_file.h",
+    "crx_verifier.cc",
+    "crx_verifier.h",
+    "id_util.cc",
+    "id_util.h",
+  ]
+
+  deps = [
+    "//base",
+    "//crypto",
+  ]
+
+  public_deps = [
+    ":crx3_proto",
+  ]
+}
+
+static_library("crx_creator") {
+  sources = [
+    "crx_creator.cc",
+    "crx_creator.h",
+  ]
+
+  deps = [
+    ":crx_file",
+    "//base",
+    "//crypto",
+  ]
+
+  public_deps = [
+    ":crx3_proto",
+  ]
+}
+
+bundle_data("unit_tests_bundle_data") {
+  visibility = [ ":unit_tests" ]
+  testonly = true
+  sources = [
+    "//components/test/data/crx_file/sample.zip",
+    "//components/test/data/crx_file/unsigned.crx3",
+    "//components/test/data/crx_file/valid.crx2",
+    "//components/test/data/crx_file/valid_no_publisher.crx3",
+    "//components/test/data/crx_file/valid_publisher.crx3",
+  ]
+  outputs = [
+    "{{bundle_resources_dir}}/" +
+        "{{source_root_relative_dir}}/{{source_file_part}}",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "crx_creator_unittest.cc",
+    "crx_verifier_unittest.cc",
+    "id_util_unittest.cc",
+  ]
+
+  deps = [
+    ":crx_creator",
+    ":crx_file",
+    ":unit_tests_bundle_data",
+    "//base",
+    "//crypto",
+    "//testing/gtest",
+  ]
+}
+
+proto_library("crx3_proto") {
+  sources = [
+    "crx3.proto",
+  ]
+}
diff --git a/src/components/crx_file/crx3.pb.cc b/src/components/crx_file/crx3.pb.cc
new file mode 100644
index 0000000..d7e7940
--- /dev/null
+++ b/src/components/crx_file/crx3.pb.cc
@@ -0,0 +1,1212 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: crx3.proto
+
+#define INTERNAL_SUPPRESS_PROTOBUF_FIELD_DEPRECATION
+#include "crx3.pb.h"
+
+#include <algorithm>
+
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <google/protobuf/stubs/common.h>
+#include <google/protobuf/stubs/once.h>
+#include <google/protobuf/stubs/port.h>
+#include <google/protobuf/stubs/starboard_poem.h>
+#include <google/protobuf/wire_format_lite_inl.h>
+// @@protoc_insertion_point(includes)
+
+namespace crx_file {
+
+void protobuf_ShutdownFile_crx3_2eproto() {
+  delete CrxFileHeader::default_instance_;
+  delete AsymmetricKeyProof::default_instance_;
+  delete SignedData::default_instance_;
+}
+
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+void protobuf_AddDesc_crx3_2eproto_impl() {
+  GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+#else
+void protobuf_AddDesc_crx3_2eproto() {
+  static bool already_here = false;
+  if (already_here)
+    return;
+  already_here = true;
+  GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+#endif
+  CrxFileHeader::default_instance_ = new CrxFileHeader();
+  AsymmetricKeyProof::default_instance_ = new AsymmetricKeyProof();
+  SignedData::default_instance_ = new SignedData();
+  CrxFileHeader::default_instance_->InitAsDefaultInstance();
+  AsymmetricKeyProof::default_instance_->InitAsDefaultInstance();
+  SignedData::default_instance_->InitAsDefaultInstance();
+  ::google::protobuf::internal::OnShutdown(&protobuf_ShutdownFile_crx3_2eproto);
+}
+
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+GOOGLE_PROTOBUF_DECLARE_ONCE(protobuf_AddDesc_crx3_2eproto_once_);
+void protobuf_AddDesc_crx3_2eproto() {
+  ::google::protobuf::GoogleOnceInit(&protobuf_AddDesc_crx3_2eproto_once_,
+                                     &protobuf_AddDesc_crx3_2eproto_impl);
+}
+#else
+// Force AddDescriptors() to be called at static initialization time.
+struct StaticDescriptorInitializer_crx3_2eproto {
+  StaticDescriptorInitializer_crx3_2eproto() {
+    protobuf_AddDesc_crx3_2eproto();
+  }
+} static_descriptor_initializer_crx3_2eproto_;
+#endif
+
+namespace {
+
+static void MergeFromFail(int line) GOOGLE_ATTRIBUTE_COLD;
+GOOGLE_ATTRIBUTE_NOINLINE static void MergeFromFail(int line) {
+  GOOGLE_CHECK(false) << __FILE__ << ":" << line;
+}
+
+}  // namespace
+
+// ===================================================================
+
+static ::std::string* MutableUnknownFieldsForCrxFileHeader(CrxFileHeader* ptr) {
+  return ptr->mutable_unknown_fields();
+}
+
+#if !defined(_MSC_VER) || _MSC_VER >= 1900
+const int CrxFileHeader::kSha256WithRsaFieldNumber;
+const int CrxFileHeader::kSha256WithEcdsaFieldNumber;
+const int CrxFileHeader::kSignedHeaderDataFieldNumber;
+#endif  // !defined(_MSC_VER) || _MSC_VER >= 1900
+
+CrxFileHeader::CrxFileHeader()
+    : ::google::protobuf::MessageLite(), _arena_ptr_(NULL) {
+  SharedCtor();
+  // @@protoc_insertion_point(constructor:crx_file.CrxFileHeader)
+}
+
+void CrxFileHeader::InitAsDefaultInstance() {}
+
+CrxFileHeader::CrxFileHeader(const CrxFileHeader& from)
+    : ::google::protobuf::MessageLite(), _arena_ptr_(NULL) {
+  SharedCtor();
+  MergeFrom(from);
+  // @@protoc_insertion_point(copy_constructor:crx_file.CrxFileHeader)
+}
+
+void CrxFileHeader::SharedCtor() {
+  ::google::protobuf::internal::GetEmptyString();
+  _cached_size_ = 0;
+  _unknown_fields_.UnsafeSetDefault(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  signed_header_data_.UnsafeSetDefault(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+CrxFileHeader::~CrxFileHeader() {
+  // @@protoc_insertion_point(destructor:crx_file.CrxFileHeader)
+  SharedDtor();
+}
+
+void CrxFileHeader::SharedDtor() {
+  _unknown_fields_.DestroyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  signed_header_data_.DestroyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  if (this != &default_instance()) {
+#else
+  if (this != default_instance_) {
+#endif
+  }
+}
+
+void CrxFileHeader::SetCachedSize(int size) const {
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const CrxFileHeader& CrxFileHeader::default_instance() {
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  protobuf_AddDesc_crx3_2eproto();
+#else
+  if (default_instance_ == NULL)
+    protobuf_AddDesc_crx3_2eproto();
+#endif
+  return *default_instance_;
+}
+
+CrxFileHeader* CrxFileHeader::default_instance_ = NULL;
+
+CrxFileHeader* CrxFileHeader::New(::google::protobuf::Arena* arena) const {
+  CrxFileHeader* n = new CrxFileHeader;
+  if (arena != NULL) {
+    arena->Own(n);
+  }
+  return n;
+}
+
+void CrxFileHeader::Clear() {
+  // @@protoc_insertion_point(message_clear_start:crx_file.CrxFileHeader)
+  if (has_signed_header_data()) {
+    signed_header_data_.ClearToEmptyNoArena(
+        &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  sha256_with_rsa_.Clear();
+  sha256_with_ecdsa_.Clear();
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+  _unknown_fields_.ClearToEmptyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+
+bool CrxFileHeader::MergePartialFromCodedStream(
+    ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION)                 \
+  if (!GOOGLE_PREDICT_TRUE(EXPRESSION)) \
+  goto failure
+  ::google::protobuf::uint32 tag;
+  ::google::protobuf::io::LazyStringOutputStream unknown_fields_string(
+      ::google::protobuf::internal::NewPermanentCallback(
+          &MutableUnknownFieldsForCrxFileHeader, this));
+  ::google::protobuf::io::CodedOutputStream unknown_fields_stream(
+      &unknown_fields_string, false);
+  // @@protoc_insertion_point(parse_start:crx_file.CrxFileHeader)
+  for (;;) {
+    ::std::pair< ::google::protobuf::uint32, bool> p =
+        input->ReadTagWithCutoff(80002);
+    tag = p.first;
+    if (!p.second)
+      goto handle_unusual;
+    switch (
+        ::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+      // repeated .crx_file.AsymmetricKeyProof sha256_with_rsa = 2;
+      case 2: {
+        if (tag == 18) {
+          DO_(input->IncrementRecursionDepth());
+        parse_loop_sha256_with_rsa:
+          DO_(::google::protobuf::internal::WireFormatLite::
+                  ReadMessageNoVirtualNoRecursionDepth(input,
+                                                       add_sha256_with_rsa()));
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectTag(18))
+          goto parse_loop_sha256_with_rsa;
+        if (input->ExpectTag(26))
+          goto parse_loop_sha256_with_ecdsa;
+        input->UnsafeDecrementRecursionDepth();
+        break;
+      }
+
+      // repeated .crx_file.AsymmetricKeyProof sha256_with_ecdsa = 3;
+      case 3: {
+        if (tag == 26) {
+          DO_(input->IncrementRecursionDepth());
+        parse_loop_sha256_with_ecdsa:
+          DO_(::google::protobuf::internal::WireFormatLite::
+                  ReadMessageNoVirtualNoRecursionDepth(
+                      input, add_sha256_with_ecdsa()));
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectTag(26))
+          goto parse_loop_sha256_with_ecdsa;
+        input->UnsafeDecrementRecursionDepth();
+        if (input->ExpectTag(80002))
+          goto parse_signed_header_data;
+        break;
+      }
+
+      // optional bytes signed_header_data = 10000;
+      case 10000: {
+        if (tag == 80002) {
+        parse_signed_header_data:
+          DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+              input, this->mutable_signed_header_data()));
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectAtEnd())
+          goto success;
+        break;
+      }
+
+      default: {
+      handle_unusual:
+        if (tag == 0 ||
+            ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+                ::google::protobuf::internal::WireFormatLite::
+                    WIRETYPE_END_GROUP) {
+          goto success;
+        }
+        DO_(::google::protobuf::internal::WireFormatLite::SkipField(
+            input, tag, &unknown_fields_stream));
+        break;
+      }
+    }
+  }
+success:
+  // @@protoc_insertion_point(parse_success:crx_file.CrxFileHeader)
+  return true;
+failure:
+  // @@protoc_insertion_point(parse_failure:crx_file.CrxFileHeader)
+  return false;
+#undef DO_
+}
+
+void CrxFileHeader::SerializeWithCachedSizes(
+    ::google::protobuf::io::CodedOutputStream* output) const {
+  // @@protoc_insertion_point(serialize_start:crx_file.CrxFileHeader)
+  // repeated .crx_file.AsymmetricKeyProof sha256_with_rsa = 2;
+  for (unsigned int i = 0, n = this->sha256_with_rsa_size(); i < n; i++) {
+    ::google::protobuf::internal::WireFormatLite::WriteMessage(
+        2, this->sha256_with_rsa(i), output);
+  }
+
+  // repeated .crx_file.AsymmetricKeyProof sha256_with_ecdsa = 3;
+  for (unsigned int i = 0, n = this->sha256_with_ecdsa_size(); i < n; i++) {
+    ::google::protobuf::internal::WireFormatLite::WriteMessage(
+        3, this->sha256_with_ecdsa(i), output);
+  }
+
+  // optional bytes signed_header_data = 10000;
+  if (has_signed_header_data()) {
+    ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+        10000, this->signed_header_data(), output);
+  }
+
+  output->WriteRaw(unknown_fields().data(),
+                   static_cast<int>(unknown_fields().size()));
+  // @@protoc_insertion_point(serialize_end:crx_file.CrxFileHeader)
+}
+
+int CrxFileHeader::ByteSize() const {
+  // @@protoc_insertion_point(message_byte_size_start:crx_file.CrxFileHeader)
+  int total_size = 0;
+
+  // optional bytes signed_header_data = 10000;
+  if (has_signed_header_data()) {
+    total_size += 3 + ::google::protobuf::internal::WireFormatLite::BytesSize(
+                          this->signed_header_data());
+  }
+
+  // repeated .crx_file.AsymmetricKeyProof sha256_with_rsa = 2;
+  total_size += 1 * this->sha256_with_rsa_size();
+  for (int i = 0; i < this->sha256_with_rsa_size(); i++) {
+    total_size +=
+        ::google::protobuf::internal::WireFormatLite::MessageSizeNoVirtual(
+            this->sha256_with_rsa(i));
+  }
+
+  // repeated .crx_file.AsymmetricKeyProof sha256_with_ecdsa = 3;
+  total_size += 1 * this->sha256_with_ecdsa_size();
+  for (int i = 0; i < this->sha256_with_ecdsa_size(); i++) {
+    total_size +=
+        ::google::protobuf::internal::WireFormatLite::MessageSizeNoVirtual(
+            this->sha256_with_ecdsa(i));
+  }
+
+  total_size += unknown_fields().size();
+
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = total_size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+  return total_size;
+}
+
+void CrxFileHeader::CheckTypeAndMergeFrom(
+    const ::google::protobuf::MessageLite& from) {
+  MergeFrom(*::google::protobuf::down_cast<const CrxFileHeader*>(&from));
+}
+
+void CrxFileHeader::MergeFrom(const CrxFileHeader& from) {
+  // @@protoc_insertion_point(class_specific_merge_from_start:crx_file.CrxFileHeader)
+  if (GOOGLE_PREDICT_FALSE(&from == this))
+    MergeFromFail(__LINE__);
+  sha256_with_rsa_.MergeFrom(from.sha256_with_rsa_);
+  sha256_with_ecdsa_.MergeFrom(from.sha256_with_ecdsa_);
+  if (from._has_bits_[2 / 32] & (0xffu << (2 % 32))) {
+    if (from.has_signed_header_data()) {
+      set_has_signed_header_data();
+      signed_header_data_.AssignWithDefault(
+          &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+          from.signed_header_data_);
+    }
+  }
+  if (!from.unknown_fields().empty()) {
+    mutable_unknown_fields()->append(from.unknown_fields());
+  }
+}
+
+void CrxFileHeader::CopyFrom(const CrxFileHeader& from) {
+  // @@protoc_insertion_point(class_specific_copy_from_start:crx_file.CrxFileHeader)
+  if (&from == this)
+    return;
+  Clear();
+  MergeFrom(from);
+}
+
+bool CrxFileHeader::IsInitialized() const {
+  return true;
+}
+
+void CrxFileHeader::Swap(CrxFileHeader* other) {
+  if (other == this)
+    return;
+  InternalSwap(other);
+}
+void CrxFileHeader::InternalSwap(CrxFileHeader* other) {
+  sha256_with_rsa_.UnsafeArenaSwap(&other->sha256_with_rsa_);
+  sha256_with_ecdsa_.UnsafeArenaSwap(&other->sha256_with_ecdsa_);
+  signed_header_data_.Swap(&other->signed_header_data_);
+  std::swap(_has_bits_[0], other->_has_bits_[0]);
+  _unknown_fields_.Swap(&other->_unknown_fields_);
+  std::swap(_cached_size_, other->_cached_size_);
+}
+
+::std::string CrxFileHeader::GetTypeName() const {
+  return "crx_file.CrxFileHeader";
+}
+
+#if PROTOBUF_INLINE_NOT_IN_HEADERS
+// CrxFileHeader
+
+// repeated .crx_file.AsymmetricKeyProof sha256_with_rsa = 2;
+int CrxFileHeader::sha256_with_rsa_size() const {
+  return sha256_with_rsa_.size();
+}
+void CrxFileHeader::clear_sha256_with_rsa() {
+  sha256_with_rsa_.Clear();
+}
+const ::crx_file::AsymmetricKeyProof& CrxFileHeader::sha256_with_rsa(
+    int index) const {
+  // @@protoc_insertion_point(field_get:crx_file.CrxFileHeader.sha256_with_rsa)
+  return sha256_with_rsa_.Get(index);
+}
+::crx_file::AsymmetricKeyProof* CrxFileHeader::mutable_sha256_with_rsa(
+    int index) {
+  // @@protoc_insertion_point(field_mutable:crx_file.CrxFileHeader.sha256_with_rsa)
+  return sha256_with_rsa_.Mutable(index);
+}
+::crx_file::AsymmetricKeyProof* CrxFileHeader::add_sha256_with_rsa() {
+  // @@protoc_insertion_point(field_add:crx_file.CrxFileHeader.sha256_with_rsa)
+  return sha256_with_rsa_.Add();
+}
+::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>*
+CrxFileHeader::mutable_sha256_with_rsa() {
+  // @@protoc_insertion_point(field_mutable_list:crx_file.CrxFileHeader.sha256_with_rsa)
+  return &sha256_with_rsa_;
+}
+const ::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>&
+CrxFileHeader::sha256_with_rsa() const {
+  // @@protoc_insertion_point(field_list:crx_file.CrxFileHeader.sha256_with_rsa)
+  return sha256_with_rsa_;
+}
+
+// repeated .crx_file.AsymmetricKeyProof sha256_with_ecdsa = 3;
+int CrxFileHeader::sha256_with_ecdsa_size() const {
+  return sha256_with_ecdsa_.size();
+}
+void CrxFileHeader::clear_sha256_with_ecdsa() {
+  sha256_with_ecdsa_.Clear();
+}
+const ::crx_file::AsymmetricKeyProof& CrxFileHeader::sha256_with_ecdsa(
+    int index) const {
+  // @@protoc_insertion_point(field_get:crx_file.CrxFileHeader.sha256_with_ecdsa)
+  return sha256_with_ecdsa_.Get(index);
+}
+::crx_file::AsymmetricKeyProof* CrxFileHeader::mutable_sha256_with_ecdsa(
+    int index) {
+  // @@protoc_insertion_point(field_mutable:crx_file.CrxFileHeader.sha256_with_ecdsa)
+  return sha256_with_ecdsa_.Mutable(index);
+}
+::crx_file::AsymmetricKeyProof* CrxFileHeader::add_sha256_with_ecdsa() {
+  // @@protoc_insertion_point(field_add:crx_file.CrxFileHeader.sha256_with_ecdsa)
+  return sha256_with_ecdsa_.Add();
+}
+::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>*
+CrxFileHeader::mutable_sha256_with_ecdsa() {
+  // @@protoc_insertion_point(field_mutable_list:crx_file.CrxFileHeader.sha256_with_ecdsa)
+  return &sha256_with_ecdsa_;
+}
+const ::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>&
+CrxFileHeader::sha256_with_ecdsa() const {
+  // @@protoc_insertion_point(field_list:crx_file.CrxFileHeader.sha256_with_ecdsa)
+  return sha256_with_ecdsa_;
+}
+
+// optional bytes signed_header_data = 10000;
+bool CrxFileHeader::has_signed_header_data() const {
+  return (_has_bits_[0] & 0x00000004u) != 0;
+}
+void CrxFileHeader::set_has_signed_header_data() {
+  _has_bits_[0] |= 0x00000004u;
+}
+void CrxFileHeader::clear_has_signed_header_data() {
+  _has_bits_[0] &= ~0x00000004u;
+}
+void CrxFileHeader::clear_signed_header_data() {
+  signed_header_data_.ClearToEmptyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  clear_has_signed_header_data();
+}
+const ::std::string& CrxFileHeader::signed_header_data() const {
+  // @@protoc_insertion_point(field_get:crx_file.CrxFileHeader.signed_header_data)
+  return signed_header_data_.GetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+void CrxFileHeader::set_signed_header_data(const ::std::string& value) {
+  set_has_signed_header_data();
+  signed_header_data_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
+  // @@protoc_insertion_point(field_set:crx_file.CrxFileHeader.signed_header_data)
+}
+void CrxFileHeader::set_signed_header_data(const char* value) {
+  set_has_signed_header_data();
+  signed_header_data_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(value));
+  // @@protoc_insertion_point(field_set_char:crx_file.CrxFileHeader.signed_header_data)
+}
+void CrxFileHeader::set_signed_header_data(const void* value, size_t size) {
+  set_has_signed_header_data();
+  signed_header_data_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(reinterpret_cast<const char*>(value), size));
+  // @@protoc_insertion_point(field_set_pointer:crx_file.CrxFileHeader.signed_header_data)
+}
+::std::string* CrxFileHeader::mutable_signed_header_data() {
+  set_has_signed_header_data();
+  // @@protoc_insertion_point(field_mutable:crx_file.CrxFileHeader.signed_header_data)
+  return signed_header_data_.MutableNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+::std::string* CrxFileHeader::release_signed_header_data() {
+  // @@protoc_insertion_point(field_release:crx_file.CrxFileHeader.signed_header_data)
+  clear_has_signed_header_data();
+  return signed_header_data_.ReleaseNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+void CrxFileHeader::set_allocated_signed_header_data(
+    ::std::string* signed_header_data) {
+  if (signed_header_data != NULL) {
+    set_has_signed_header_data();
+  } else {
+    clear_has_signed_header_data();
+  }
+  signed_header_data_.SetAllocatedNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      signed_header_data);
+  // @@protoc_insertion_point(field_set_allocated:crx_file.CrxFileHeader.signed_header_data)
+}
+
+#endif  // PROTOBUF_INLINE_NOT_IN_HEADERS
+
+// ===================================================================
+
+static ::std::string* MutableUnknownFieldsForAsymmetricKeyProof(
+    AsymmetricKeyProof* ptr) {
+  return ptr->mutable_unknown_fields();
+}
+
+#if !defined(_MSC_VER) || _MSC_VER >= 1900
+const int AsymmetricKeyProof::kPublicKeyFieldNumber;
+const int AsymmetricKeyProof::kSignatureFieldNumber;
+#endif  // !defined(_MSC_VER) || _MSC_VER >= 1900
+
+AsymmetricKeyProof::AsymmetricKeyProof()
+    : ::google::protobuf::MessageLite(), _arena_ptr_(NULL) {
+  SharedCtor();
+  // @@protoc_insertion_point(constructor:crx_file.AsymmetricKeyProof)
+}
+
+void AsymmetricKeyProof::InitAsDefaultInstance() {}
+
+AsymmetricKeyProof::AsymmetricKeyProof(const AsymmetricKeyProof& from)
+    : ::google::protobuf::MessageLite(), _arena_ptr_(NULL) {
+  SharedCtor();
+  MergeFrom(from);
+  // @@protoc_insertion_point(copy_constructor:crx_file.AsymmetricKeyProof)
+}
+
+void AsymmetricKeyProof::SharedCtor() {
+  ::google::protobuf::internal::GetEmptyString();
+  _cached_size_ = 0;
+  _unknown_fields_.UnsafeSetDefault(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  public_key_.UnsafeSetDefault(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  signature_.UnsafeSetDefault(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+AsymmetricKeyProof::~AsymmetricKeyProof() {
+  // @@protoc_insertion_point(destructor:crx_file.AsymmetricKeyProof)
+  SharedDtor();
+}
+
+void AsymmetricKeyProof::SharedDtor() {
+  _unknown_fields_.DestroyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  public_key_.DestroyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  signature_.DestroyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  if (this != &default_instance()) {
+#else
+  if (this != default_instance_) {
+#endif
+  }
+}
+
+void AsymmetricKeyProof::SetCachedSize(int size) const {
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const AsymmetricKeyProof& AsymmetricKeyProof::default_instance() {
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  protobuf_AddDesc_crx3_2eproto();
+#else
+  if (default_instance_ == NULL)
+    protobuf_AddDesc_crx3_2eproto();
+#endif
+  return *default_instance_;
+}
+
+AsymmetricKeyProof* AsymmetricKeyProof::default_instance_ = NULL;
+
+AsymmetricKeyProof* AsymmetricKeyProof::New(
+    ::google::protobuf::Arena* arena) const {
+  AsymmetricKeyProof* n = new AsymmetricKeyProof;
+  if (arena != NULL) {
+    arena->Own(n);
+  }
+  return n;
+}
+
+void AsymmetricKeyProof::Clear() {
+  // @@protoc_insertion_point(message_clear_start:crx_file.AsymmetricKeyProof)
+  if (_has_bits_[0 / 32] & 3u) {
+    if (has_public_key()) {
+      public_key_.ClearToEmptyNoArena(
+          &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+    }
+    if (has_signature()) {
+      signature_.ClearToEmptyNoArena(
+          &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+    }
+  }
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+  _unknown_fields_.ClearToEmptyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+
+bool AsymmetricKeyProof::MergePartialFromCodedStream(
+    ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION)                 \
+  if (!GOOGLE_PREDICT_TRUE(EXPRESSION)) \
+  goto failure
+  ::google::protobuf::uint32 tag;
+  ::google::protobuf::io::LazyStringOutputStream unknown_fields_string(
+      ::google::protobuf::internal::NewPermanentCallback(
+          &MutableUnknownFieldsForAsymmetricKeyProof, this));
+  ::google::protobuf::io::CodedOutputStream unknown_fields_stream(
+      &unknown_fields_string, false);
+  // @@protoc_insertion_point(parse_start:crx_file.AsymmetricKeyProof)
+  for (;;) {
+    ::std::pair< ::google::protobuf::uint32, bool> p =
+        input->ReadTagWithCutoff(127);
+    tag = p.first;
+    if (!p.second)
+      goto handle_unusual;
+    switch (
+        ::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+      // optional bytes public_key = 1;
+      case 1: {
+        if (tag == 10) {
+          DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+              input, this->mutable_public_key()));
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectTag(18))
+          goto parse_signature;
+        break;
+      }
+
+      // optional bytes signature = 2;
+      case 2: {
+        if (tag == 18) {
+        parse_signature:
+          DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+              input, this->mutable_signature()));
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectAtEnd())
+          goto success;
+        break;
+      }
+
+      default: {
+      handle_unusual:
+        if (tag == 0 ||
+            ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+                ::google::protobuf::internal::WireFormatLite::
+                    WIRETYPE_END_GROUP) {
+          goto success;
+        }
+        DO_(::google::protobuf::internal::WireFormatLite::SkipField(
+            input, tag, &unknown_fields_stream));
+        break;
+      }
+    }
+  }
+success:
+  // @@protoc_insertion_point(parse_success:crx_file.AsymmetricKeyProof)
+  return true;
+failure:
+  // @@protoc_insertion_point(parse_failure:crx_file.AsymmetricKeyProof)
+  return false;
+#undef DO_
+}
+
+void AsymmetricKeyProof::SerializeWithCachedSizes(
+    ::google::protobuf::io::CodedOutputStream* output) const {
+  // @@protoc_insertion_point(serialize_start:crx_file.AsymmetricKeyProof)
+  // optional bytes public_key = 1;
+  if (has_public_key()) {
+    ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+        1, this->public_key(), output);
+  }
+
+  // optional bytes signature = 2;
+  if (has_signature()) {
+    ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+        2, this->signature(), output);
+  }
+
+  output->WriteRaw(unknown_fields().data(),
+                   static_cast<int>(unknown_fields().size()));
+  // @@protoc_insertion_point(serialize_end:crx_file.AsymmetricKeyProof)
+}
+
+int AsymmetricKeyProof::ByteSize() const {
+  // @@protoc_insertion_point(message_byte_size_start:crx_file.AsymmetricKeyProof)
+  int total_size = 0;
+
+  if (_has_bits_[0 / 32] & 3u) {
+    // optional bytes public_key = 1;
+    if (has_public_key()) {
+      total_size += 1 + ::google::protobuf::internal::WireFormatLite::BytesSize(
+                            this->public_key());
+    }
+
+    // optional bytes signature = 2;
+    if (has_signature()) {
+      total_size += 1 + ::google::protobuf::internal::WireFormatLite::BytesSize(
+                            this->signature());
+    }
+  }
+  total_size += unknown_fields().size();
+
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = total_size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+  return total_size;
+}
+
+void AsymmetricKeyProof::CheckTypeAndMergeFrom(
+    const ::google::protobuf::MessageLite& from) {
+  MergeFrom(*::google::protobuf::down_cast<const AsymmetricKeyProof*>(&from));
+}
+
+void AsymmetricKeyProof::MergeFrom(const AsymmetricKeyProof& from) {
+  // @@protoc_insertion_point(class_specific_merge_from_start:crx_file.AsymmetricKeyProof)
+  if (GOOGLE_PREDICT_FALSE(&from == this))
+    MergeFromFail(__LINE__);
+  if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    if (from.has_public_key()) {
+      set_has_public_key();
+      public_key_.AssignWithDefault(
+          &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+          from.public_key_);
+    }
+    if (from.has_signature()) {
+      set_has_signature();
+      signature_.AssignWithDefault(
+          &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+          from.signature_);
+    }
+  }
+  if (!from.unknown_fields().empty()) {
+    mutable_unknown_fields()->append(from.unknown_fields());
+  }
+}
+
+void AsymmetricKeyProof::CopyFrom(const AsymmetricKeyProof& from) {
+  // @@protoc_insertion_point(class_specific_copy_from_start:crx_file.AsymmetricKeyProof)
+  if (&from == this)
+    return;
+  Clear();
+  MergeFrom(from);
+}
+
+bool AsymmetricKeyProof::IsInitialized() const {
+  return true;
+}
+
+void AsymmetricKeyProof::Swap(AsymmetricKeyProof* other) {
+  if (other == this)
+    return;
+  InternalSwap(other);
+}
+void AsymmetricKeyProof::InternalSwap(AsymmetricKeyProof* other) {
+  public_key_.Swap(&other->public_key_);
+  signature_.Swap(&other->signature_);
+  std::swap(_has_bits_[0], other->_has_bits_[0]);
+  _unknown_fields_.Swap(&other->_unknown_fields_);
+  std::swap(_cached_size_, other->_cached_size_);
+}
+
+::std::string AsymmetricKeyProof::GetTypeName() const {
+  return "crx_file.AsymmetricKeyProof";
+}
+
+#if PROTOBUF_INLINE_NOT_IN_HEADERS
+// AsymmetricKeyProof
+
+// optional bytes public_key = 1;
+bool AsymmetricKeyProof::has_public_key() const {
+  return (_has_bits_[0] & 0x00000001u) != 0;
+}
+void AsymmetricKeyProof::set_has_public_key() {
+  _has_bits_[0] |= 0x00000001u;
+}
+void AsymmetricKeyProof::clear_has_public_key() {
+  _has_bits_[0] &= ~0x00000001u;
+}
+void AsymmetricKeyProof::clear_public_key() {
+  public_key_.ClearToEmptyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  clear_has_public_key();
+}
+const ::std::string& AsymmetricKeyProof::public_key() const {
+  // @@protoc_insertion_point(field_get:crx_file.AsymmetricKeyProof.public_key)
+  return public_key_.GetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+void AsymmetricKeyProof::set_public_key(const ::std::string& value) {
+  set_has_public_key();
+  public_key_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
+  // @@protoc_insertion_point(field_set:crx_file.AsymmetricKeyProof.public_key)
+}
+void AsymmetricKeyProof::set_public_key(const char* value) {
+  set_has_public_key();
+  public_key_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(value));
+  // @@protoc_insertion_point(field_set_char:crx_file.AsymmetricKeyProof.public_key)
+}
+void AsymmetricKeyProof::set_public_key(const void* value, size_t size) {
+  set_has_public_key();
+  public_key_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(reinterpret_cast<const char*>(value), size));
+  // @@protoc_insertion_point(field_set_pointer:crx_file.AsymmetricKeyProof.public_key)
+}
+::std::string* AsymmetricKeyProof::mutable_public_key() {
+  set_has_public_key();
+  // @@protoc_insertion_point(field_mutable:crx_file.AsymmetricKeyProof.public_key)
+  return public_key_.MutableNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+::std::string* AsymmetricKeyProof::release_public_key() {
+  // @@protoc_insertion_point(field_release:crx_file.AsymmetricKeyProof.public_key)
+  clear_has_public_key();
+  return public_key_.ReleaseNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+void AsymmetricKeyProof::set_allocated_public_key(::std::string* public_key) {
+  if (public_key != NULL) {
+    set_has_public_key();
+  } else {
+    clear_has_public_key();
+  }
+  public_key_.SetAllocatedNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), public_key);
+  // @@protoc_insertion_point(field_set_allocated:crx_file.AsymmetricKeyProof.public_key)
+}
+
+// optional bytes signature = 2;
+bool AsymmetricKeyProof::has_signature() const {
+  return (_has_bits_[0] & 0x00000002u) != 0;
+}
+void AsymmetricKeyProof::set_has_signature() {
+  _has_bits_[0] |= 0x00000002u;
+}
+void AsymmetricKeyProof::clear_has_signature() {
+  _has_bits_[0] &= ~0x00000002u;
+}
+void AsymmetricKeyProof::clear_signature() {
+  signature_.ClearToEmptyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  clear_has_signature();
+}
+const ::std::string& AsymmetricKeyProof::signature() const {
+  // @@protoc_insertion_point(field_get:crx_file.AsymmetricKeyProof.signature)
+  return signature_.GetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+void AsymmetricKeyProof::set_signature(const ::std::string& value) {
+  set_has_signature();
+  signature_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
+  // @@protoc_insertion_point(field_set:crx_file.AsymmetricKeyProof.signature)
+}
+void AsymmetricKeyProof::set_signature(const char* value) {
+  set_has_signature();
+  signature_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(value));
+  // @@protoc_insertion_point(field_set_char:crx_file.AsymmetricKeyProof.signature)
+}
+void AsymmetricKeyProof::set_signature(const void* value, size_t size) {
+  set_has_signature();
+  signature_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(reinterpret_cast<const char*>(value), size));
+  // @@protoc_insertion_point(field_set_pointer:crx_file.AsymmetricKeyProof.signature)
+}
+::std::string* AsymmetricKeyProof::mutable_signature() {
+  set_has_signature();
+  // @@protoc_insertion_point(field_mutable:crx_file.AsymmetricKeyProof.signature)
+  return signature_.MutableNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+::std::string* AsymmetricKeyProof::release_signature() {
+  // @@protoc_insertion_point(field_release:crx_file.AsymmetricKeyProof.signature)
+  clear_has_signature();
+  return signature_.ReleaseNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+void AsymmetricKeyProof::set_allocated_signature(::std::string* signature) {
+  if (signature != NULL) {
+    set_has_signature();
+  } else {
+    clear_has_signature();
+  }
+  signature_.SetAllocatedNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), signature);
+  // @@protoc_insertion_point(field_set_allocated:crx_file.AsymmetricKeyProof.signature)
+}
+
+#endif  // PROTOBUF_INLINE_NOT_IN_HEADERS
+
+// ===================================================================
+
+static ::std::string* MutableUnknownFieldsForSignedData(SignedData* ptr) {
+  return ptr->mutable_unknown_fields();
+}
+
+#if !defined(_MSC_VER) || _MSC_VER >= 1900
+const int SignedData::kCrxIdFieldNumber;
+#endif  // !defined(_MSC_VER) || _MSC_VER >= 1900
+
+SignedData::SignedData()
+    : ::google::protobuf::MessageLite(), _arena_ptr_(NULL) {
+  SharedCtor();
+  // @@protoc_insertion_point(constructor:crx_file.SignedData)
+}
+
+void SignedData::InitAsDefaultInstance() {}
+
+SignedData::SignedData(const SignedData& from)
+    : ::google::protobuf::MessageLite(), _arena_ptr_(NULL) {
+  SharedCtor();
+  MergeFrom(from);
+  // @@protoc_insertion_point(copy_constructor:crx_file.SignedData)
+}
+
+void SignedData::SharedCtor() {
+  ::google::protobuf::internal::GetEmptyString();
+  _cached_size_ = 0;
+  _unknown_fields_.UnsafeSetDefault(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  crx_id_.UnsafeSetDefault(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+SignedData::~SignedData() {
+  // @@protoc_insertion_point(destructor:crx_file.SignedData)
+  SharedDtor();
+}
+
+void SignedData::SharedDtor() {
+  _unknown_fields_.DestroyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  crx_id_.DestroyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  if (this != &default_instance()) {
+#else
+  if (this != default_instance_) {
+#endif
+  }
+}
+
+void SignedData::SetCachedSize(int size) const {
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const SignedData& SignedData::default_instance() {
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  protobuf_AddDesc_crx3_2eproto();
+#else
+  if (default_instance_ == NULL)
+    protobuf_AddDesc_crx3_2eproto();
+#endif
+  return *default_instance_;
+}
+
+SignedData* SignedData::default_instance_ = NULL;
+
+SignedData* SignedData::New(::google::protobuf::Arena* arena) const {
+  SignedData* n = new SignedData;
+  if (arena != NULL) {
+    arena->Own(n);
+  }
+  return n;
+}
+
+void SignedData::Clear() {
+  // @@protoc_insertion_point(message_clear_start:crx_file.SignedData)
+  if (has_crx_id()) {
+    crx_id_.ClearToEmptyNoArena(
+        &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+  _unknown_fields_.ClearToEmptyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+
+bool SignedData::MergePartialFromCodedStream(
+    ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION)                 \
+  if (!GOOGLE_PREDICT_TRUE(EXPRESSION)) \
+  goto failure
+  ::google::protobuf::uint32 tag;
+  ::google::protobuf::io::LazyStringOutputStream unknown_fields_string(
+      ::google::protobuf::internal::NewPermanentCallback(
+          &MutableUnknownFieldsForSignedData, this));
+  ::google::protobuf::io::CodedOutputStream unknown_fields_stream(
+      &unknown_fields_string, false);
+  // @@protoc_insertion_point(parse_start:crx_file.SignedData)
+  for (;;) {
+    ::std::pair< ::google::protobuf::uint32, bool> p =
+        input->ReadTagWithCutoff(127);
+    tag = p.first;
+    if (!p.second)
+      goto handle_unusual;
+    switch (
+        ::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+      // optional bytes crx_id = 1;
+      case 1: {
+        if (tag == 10) {
+          DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+              input, this->mutable_crx_id()));
+        } else {
+          goto handle_unusual;
+        }
+        if (input->ExpectAtEnd())
+          goto success;
+        break;
+      }
+
+      default: {
+      handle_unusual:
+        if (tag == 0 ||
+            ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+                ::google::protobuf::internal::WireFormatLite::
+                    WIRETYPE_END_GROUP) {
+          goto success;
+        }
+        DO_(::google::protobuf::internal::WireFormatLite::SkipField(
+            input, tag, &unknown_fields_stream));
+        break;
+      }
+    }
+  }
+success:
+  // @@protoc_insertion_point(parse_success:crx_file.SignedData)
+  return true;
+failure:
+  // @@protoc_insertion_point(parse_failure:crx_file.SignedData)
+  return false;
+#undef DO_
+}
+
+void SignedData::SerializeWithCachedSizes(
+    ::google::protobuf::io::CodedOutputStream* output) const {
+  // @@protoc_insertion_point(serialize_start:crx_file.SignedData)
+  // optional bytes crx_id = 1;
+  if (has_crx_id()) {
+    ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+        1, this->crx_id(), output);
+  }
+
+  output->WriteRaw(unknown_fields().data(),
+                   static_cast<int>(unknown_fields().size()));
+  // @@protoc_insertion_point(serialize_end:crx_file.SignedData)
+}
+
+int SignedData::ByteSize() const {
+  // @@protoc_insertion_point(message_byte_size_start:crx_file.SignedData)
+  int total_size = 0;
+
+  // optional bytes crx_id = 1;
+  if (has_crx_id()) {
+    total_size += 1 + ::google::protobuf::internal::WireFormatLite::BytesSize(
+                          this->crx_id());
+  }
+
+  total_size += unknown_fields().size();
+
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = total_size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+  return total_size;
+}
+
+void SignedData::CheckTypeAndMergeFrom(
+    const ::google::protobuf::MessageLite& from) {
+  MergeFrom(*::google::protobuf::down_cast<const SignedData*>(&from));
+}
+
+void SignedData::MergeFrom(const SignedData& from) {
+  // @@protoc_insertion_point(class_specific_merge_from_start:crx_file.SignedData)
+  if (GOOGLE_PREDICT_FALSE(&from == this))
+    MergeFromFail(__LINE__);
+  if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    if (from.has_crx_id()) {
+      set_has_crx_id();
+      crx_id_.AssignWithDefault(
+          &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+          from.crx_id_);
+    }
+  }
+  if (!from.unknown_fields().empty()) {
+    mutable_unknown_fields()->append(from.unknown_fields());
+  }
+}
+
+void SignedData::CopyFrom(const SignedData& from) {
+  // @@protoc_insertion_point(class_specific_copy_from_start:crx_file.SignedData)
+  if (&from == this)
+    return;
+  Clear();
+  MergeFrom(from);
+}
+
+bool SignedData::IsInitialized() const {
+  return true;
+}
+
+void SignedData::Swap(SignedData* other) {
+  if (other == this)
+    return;
+  InternalSwap(other);
+}
+void SignedData::InternalSwap(SignedData* other) {
+  crx_id_.Swap(&other->crx_id_);
+  std::swap(_has_bits_[0], other->_has_bits_[0]);
+  _unknown_fields_.Swap(&other->_unknown_fields_);
+  std::swap(_cached_size_, other->_cached_size_);
+}
+
+::std::string SignedData::GetTypeName() const {
+  return "crx_file.SignedData";
+}
+
+#if PROTOBUF_INLINE_NOT_IN_HEADERS
+// SignedData
+
+// optional bytes crx_id = 1;
+bool SignedData::has_crx_id() const {
+  return (_has_bits_[0] & 0x00000001u) != 0;
+}
+void SignedData::set_has_crx_id() {
+  _has_bits_[0] |= 0x00000001u;
+}
+void SignedData::clear_has_crx_id() {
+  _has_bits_[0] &= ~0x00000001u;
+}
+void SignedData::clear_crx_id() {
+  crx_id_.ClearToEmptyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  clear_has_crx_id();
+}
+const ::std::string& SignedData::crx_id() const {
+  // @@protoc_insertion_point(field_get:crx_file.SignedData.crx_id)
+  return crx_id_.GetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+void SignedData::set_crx_id(const ::std::string& value) {
+  set_has_crx_id();
+  crx_id_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
+  // @@protoc_insertion_point(field_set:crx_file.SignedData.crx_id)
+}
+void SignedData::set_crx_id(const char* value) {
+  set_has_crx_id();
+  crx_id_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(value));
+  // @@protoc_insertion_point(field_set_char:crx_file.SignedData.crx_id)
+}
+void SignedData::set_crx_id(const void* value, size_t size) {
+  set_has_crx_id();
+  crx_id_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(reinterpret_cast<const char*>(value), size));
+  // @@protoc_insertion_point(field_set_pointer:crx_file.SignedData.crx_id)
+}
+::std::string* SignedData::mutable_crx_id() {
+  set_has_crx_id();
+  // @@protoc_insertion_point(field_mutable:crx_file.SignedData.crx_id)
+  return crx_id_.MutableNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+::std::string* SignedData::release_crx_id() {
+  // @@protoc_insertion_point(field_release:crx_file.SignedData.crx_id)
+  clear_has_crx_id();
+  return crx_id_.ReleaseNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+void SignedData::set_allocated_crx_id(::std::string* crx_id) {
+  if (crx_id != NULL) {
+    set_has_crx_id();
+  } else {
+    clear_has_crx_id();
+  }
+  crx_id_.SetAllocatedNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), crx_id);
+  // @@protoc_insertion_point(field_set_allocated:crx_file.SignedData.crx_id)
+}
+
+#endif  // PROTOBUF_INLINE_NOT_IN_HEADERS
+
+// @@protoc_insertion_point(namespace_scope)
+
+}  // namespace crx_file
+
+// @@protoc_insertion_point(global_scope)
diff --git a/src/components/crx_file/crx3.pb.h b/src/components/crx_file/crx3.pb.h
new file mode 100644
index 0000000..82fc855
--- /dev/null
+++ b/src/components/crx_file/crx3.pb.h
@@ -0,0 +1,769 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: crx3.proto
+
+#ifndef PROTOBUF_crx3_2eproto__INCLUDED
+#define PROTOBUF_crx3_2eproto__INCLUDED
+
+#include <string>
+
+#include <google/protobuf/stubs/common.h>
+
+#if GOOGLE_PROTOBUF_VERSION < 3000000
+#error This file was generated by a newer version of protoc which is
+#error incompatible with your Protocol Buffer headers.  Please update
+#error your headers.
+#endif
+#if 3000000 < GOOGLE_PROTOBUF_MIN_PROTOC_VERSION
+#error This file was generated by an older version of protoc which is
+#error incompatible with your Protocol Buffer headers.  Please
+#error regenerate this file with a newer version of protoc.
+#endif
+
+#include <google/protobuf/arena.h>
+#include <google/protobuf/arenastring.h>
+#include <google/protobuf/extension_set.h>
+#include <google/protobuf/generated_message_util.h>
+#include <google/protobuf/message_lite.h>
+#include <google/protobuf/repeated_field.h>
+// @@protoc_insertion_point(includes)
+
+namespace crx_file {
+
+// Internal implementation detail -- do not call these.
+void protobuf_AddDesc_crx3_2eproto();
+void protobuf_AssignDesc_crx3_2eproto();
+void protobuf_ShutdownFile_crx3_2eproto();
+
+class AsymmetricKeyProof;
+class CrxFileHeader;
+class SignedData;
+
+// ===================================================================
+
+class CrxFileHeader : public ::google::protobuf::MessageLite {
+ public:
+  CrxFileHeader();
+  virtual ~CrxFileHeader();
+
+  CrxFileHeader(const CrxFileHeader& from);
+
+  inline CrxFileHeader& operator=(const CrxFileHeader& from) {
+    CopyFrom(from);
+    return *this;
+  }
+
+  inline const ::std::string& unknown_fields() const {
+    return _unknown_fields_.GetNoArena(
+        &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+
+  inline ::std::string* mutable_unknown_fields() {
+    return _unknown_fields_.MutableNoArena(
+        &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+
+  static const CrxFileHeader& default_instance();
+
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  // Returns the internal default instance pointer. This function can
+  // return NULL thus should not be used by the user. This is intended
+  // for Protobuf internal code. Please use default_instance() declared
+  // above instead.
+  static inline const CrxFileHeader* internal_default_instance() {
+    return default_instance_;
+  }
+#endif
+
+  GOOGLE_ATTRIBUTE_NOINLINE void Swap(CrxFileHeader* other);
+
+  // implements Message ----------------------------------------------
+
+  inline CrxFileHeader* New() const { return New(NULL); }
+
+  CrxFileHeader* New(::google::protobuf::Arena* arena) const;
+  void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from);
+  void CopyFrom(const CrxFileHeader& from);
+  void MergeFrom(const CrxFileHeader& from);
+  void Clear();
+  bool IsInitialized() const;
+
+  int ByteSize() const;
+  bool MergePartialFromCodedStream(
+      ::google::protobuf::io::CodedInputStream* input);
+  void SerializeWithCachedSizes(
+      ::google::protobuf::io::CodedOutputStream* output) const;
+  void DiscardUnknownFields();
+  int GetCachedSize() const { return _cached_size_; }
+
+ private:
+  void SharedCtor();
+  void SharedDtor();
+  void SetCachedSize(int size) const;
+  void InternalSwap(CrxFileHeader* other);
+
+ private:
+  inline ::google::protobuf::Arena* GetArenaNoVirtual() const {
+    return _arena_ptr_;
+  }
+  inline ::google::protobuf::Arena* MaybeArenaPtr() const {
+    return _arena_ptr_;
+  }
+
+ public:
+  ::std::string GetTypeName() const;
+
+  // nested types ----------------------------------------------------
+
+  // accessors -------------------------------------------------------
+
+  // repeated .crx_file.AsymmetricKeyProof sha256_with_rsa = 2;
+  int sha256_with_rsa_size() const;
+  void clear_sha256_with_rsa();
+  static const int kSha256WithRsaFieldNumber = 2;
+  const ::crx_file::AsymmetricKeyProof& sha256_with_rsa(int index) const;
+  ::crx_file::AsymmetricKeyProof* mutable_sha256_with_rsa(int index);
+  ::crx_file::AsymmetricKeyProof* add_sha256_with_rsa();
+  ::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>*
+  mutable_sha256_with_rsa();
+  const ::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>&
+  sha256_with_rsa() const;
+
+  // repeated .crx_file.AsymmetricKeyProof sha256_with_ecdsa = 3;
+  int sha256_with_ecdsa_size() const;
+  void clear_sha256_with_ecdsa();
+  static const int kSha256WithEcdsaFieldNumber = 3;
+  const ::crx_file::AsymmetricKeyProof& sha256_with_ecdsa(int index) const;
+  ::crx_file::AsymmetricKeyProof* mutable_sha256_with_ecdsa(int index);
+  ::crx_file::AsymmetricKeyProof* add_sha256_with_ecdsa();
+  ::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>*
+  mutable_sha256_with_ecdsa();
+  const ::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>&
+  sha256_with_ecdsa() const;
+
+  // optional bytes signed_header_data = 10000;
+  bool has_signed_header_data() const;
+  void clear_signed_header_data();
+  static const int kSignedHeaderDataFieldNumber = 10000;
+  const ::std::string& signed_header_data() const;
+  void set_signed_header_data(const ::std::string& value);
+  void set_signed_header_data(const char* value);
+  void set_signed_header_data(const void* value, size_t size);
+  ::std::string* mutable_signed_header_data();
+  ::std::string* release_signed_header_data();
+  void set_allocated_signed_header_data(::std::string* signed_header_data);
+
+  // @@protoc_insertion_point(class_scope:crx_file.CrxFileHeader)
+ private:
+  inline void set_has_signed_header_data();
+  inline void clear_has_signed_header_data();
+
+  ::google::protobuf::internal::ArenaStringPtr _unknown_fields_;
+  ::google::protobuf::Arena* _arena_ptr_;
+
+  ::google::protobuf::uint32 _has_bits_[1];
+  mutable int _cached_size_;
+  ::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>
+      sha256_with_rsa_;
+  ::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>
+      sha256_with_ecdsa_;
+  ::google::protobuf::internal::ArenaStringPtr signed_header_data_;
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  friend void protobuf_AddDesc_crx3_2eproto_impl();
+#else
+  friend void protobuf_AddDesc_crx3_2eproto();
+#endif
+  friend void protobuf_AssignDesc_crx3_2eproto();
+  friend void protobuf_ShutdownFile_crx3_2eproto();
+
+  void InitAsDefaultInstance();
+  static CrxFileHeader* default_instance_;
+};
+// -------------------------------------------------------------------
+
+class AsymmetricKeyProof : public ::google::protobuf::MessageLite {
+ public:
+  AsymmetricKeyProof();
+  virtual ~AsymmetricKeyProof();
+
+  AsymmetricKeyProof(const AsymmetricKeyProof& from);
+
+  inline AsymmetricKeyProof& operator=(const AsymmetricKeyProof& from) {
+    CopyFrom(from);
+    return *this;
+  }
+
+  inline const ::std::string& unknown_fields() const {
+    return _unknown_fields_.GetNoArena(
+        &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+
+  inline ::std::string* mutable_unknown_fields() {
+    return _unknown_fields_.MutableNoArena(
+        &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+
+  static const AsymmetricKeyProof& default_instance();
+
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  // Returns the internal default instance pointer. This function can
+  // return NULL thus should not be used by the user. This is intended
+  // for Protobuf internal code. Please use default_instance() declared
+  // above instead.
+  static inline const AsymmetricKeyProof* internal_default_instance() {
+    return default_instance_;
+  }
+#endif
+
+  GOOGLE_ATTRIBUTE_NOINLINE void Swap(AsymmetricKeyProof* other);
+
+  // implements Message ----------------------------------------------
+
+  inline AsymmetricKeyProof* New() const { return New(NULL); }
+
+  AsymmetricKeyProof* New(::google::protobuf::Arena* arena) const;
+  void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from);
+  void CopyFrom(const AsymmetricKeyProof& from);
+  void MergeFrom(const AsymmetricKeyProof& from);
+  void Clear();
+  bool IsInitialized() const;
+
+  int ByteSize() const;
+  bool MergePartialFromCodedStream(
+      ::google::protobuf::io::CodedInputStream* input);
+  void SerializeWithCachedSizes(
+      ::google::protobuf::io::CodedOutputStream* output) const;
+  void DiscardUnknownFields();
+  int GetCachedSize() const { return _cached_size_; }
+
+ private:
+  void SharedCtor();
+  void SharedDtor();
+  void SetCachedSize(int size) const;
+  void InternalSwap(AsymmetricKeyProof* other);
+
+ private:
+  inline ::google::protobuf::Arena* GetArenaNoVirtual() const {
+    return _arena_ptr_;
+  }
+  inline ::google::protobuf::Arena* MaybeArenaPtr() const {
+    return _arena_ptr_;
+  }
+
+ public:
+  ::std::string GetTypeName() const;
+
+  // nested types ----------------------------------------------------
+
+  // accessors -------------------------------------------------------
+
+  // optional bytes public_key = 1;
+  bool has_public_key() const;
+  void clear_public_key();
+  static const int kPublicKeyFieldNumber = 1;
+  const ::std::string& public_key() const;
+  void set_public_key(const ::std::string& value);
+  void set_public_key(const char* value);
+  void set_public_key(const void* value, size_t size);
+  ::std::string* mutable_public_key();
+  ::std::string* release_public_key();
+  void set_allocated_public_key(::std::string* public_key);
+
+  // optional bytes signature = 2;
+  bool has_signature() const;
+  void clear_signature();
+  static const int kSignatureFieldNumber = 2;
+  const ::std::string& signature() const;
+  void set_signature(const ::std::string& value);
+  void set_signature(const char* value);
+  void set_signature(const void* value, size_t size);
+  ::std::string* mutable_signature();
+  ::std::string* release_signature();
+  void set_allocated_signature(::std::string* signature);
+
+  // @@protoc_insertion_point(class_scope:crx_file.AsymmetricKeyProof)
+ private:
+  inline void set_has_public_key();
+  inline void clear_has_public_key();
+  inline void set_has_signature();
+  inline void clear_has_signature();
+
+  ::google::protobuf::internal::ArenaStringPtr _unknown_fields_;
+  ::google::protobuf::Arena* _arena_ptr_;
+
+  ::google::protobuf::uint32 _has_bits_[1];
+  mutable int _cached_size_;
+  ::google::protobuf::internal::ArenaStringPtr public_key_;
+  ::google::protobuf::internal::ArenaStringPtr signature_;
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  friend void protobuf_AddDesc_crx3_2eproto_impl();
+#else
+  friend void protobuf_AddDesc_crx3_2eproto();
+#endif
+  friend void protobuf_AssignDesc_crx3_2eproto();
+  friend void protobuf_ShutdownFile_crx3_2eproto();
+
+  void InitAsDefaultInstance();
+  static AsymmetricKeyProof* default_instance_;
+};
+// -------------------------------------------------------------------
+
+class SignedData : public ::google::protobuf::MessageLite {
+ public:
+  SignedData();
+  virtual ~SignedData();
+
+  SignedData(const SignedData& from);
+
+  inline SignedData& operator=(const SignedData& from) {
+    CopyFrom(from);
+    return *this;
+  }
+
+  inline const ::std::string& unknown_fields() const {
+    return _unknown_fields_.GetNoArena(
+        &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+
+  inline ::std::string* mutable_unknown_fields() {
+    return _unknown_fields_.MutableNoArena(
+        &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+
+  static const SignedData& default_instance();
+
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  // Returns the internal default instance pointer. This function can
+  // return NULL thus should not be used by the user. This is intended
+  // for Protobuf internal code. Please use default_instance() declared
+  // above instead.
+  static inline const SignedData* internal_default_instance() {
+    return default_instance_;
+  }
+#endif
+
+  GOOGLE_ATTRIBUTE_NOINLINE void Swap(SignedData* other);
+
+  // implements Message ----------------------------------------------
+
+  inline SignedData* New() const { return New(NULL); }
+
+  SignedData* New(::google::protobuf::Arena* arena) const;
+  void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from);
+  void CopyFrom(const SignedData& from);
+  void MergeFrom(const SignedData& from);
+  void Clear();
+  bool IsInitialized() const;
+
+  int ByteSize() const;
+  bool MergePartialFromCodedStream(
+      ::google::protobuf::io::CodedInputStream* input);
+  void SerializeWithCachedSizes(
+      ::google::protobuf::io::CodedOutputStream* output) const;
+  void DiscardUnknownFields();
+  int GetCachedSize() const { return _cached_size_; }
+
+ private:
+  void SharedCtor();
+  void SharedDtor();
+  void SetCachedSize(int size) const;
+  void InternalSwap(SignedData* other);
+
+ private:
+  inline ::google::protobuf::Arena* GetArenaNoVirtual() const {
+    return _arena_ptr_;
+  }
+  inline ::google::protobuf::Arena* MaybeArenaPtr() const {
+    return _arena_ptr_;
+  }
+
+ public:
+  ::std::string GetTypeName() const;
+
+  // nested types ----------------------------------------------------
+
+  // accessors -------------------------------------------------------
+
+  // optional bytes crx_id = 1;
+  bool has_crx_id() const;
+  void clear_crx_id();
+  static const int kCrxIdFieldNumber = 1;
+  const ::std::string& crx_id() const;
+  void set_crx_id(const ::std::string& value);
+  void set_crx_id(const char* value);
+  void set_crx_id(const void* value, size_t size);
+  ::std::string* mutable_crx_id();
+  ::std::string* release_crx_id();
+  void set_allocated_crx_id(::std::string* crx_id);
+
+  // @@protoc_insertion_point(class_scope:crx_file.SignedData)
+ private:
+  inline void set_has_crx_id();
+  inline void clear_has_crx_id();
+
+  ::google::protobuf::internal::ArenaStringPtr _unknown_fields_;
+  ::google::protobuf::Arena* _arena_ptr_;
+
+  ::google::protobuf::uint32 _has_bits_[1];
+  mutable int _cached_size_;
+  ::google::protobuf::internal::ArenaStringPtr crx_id_;
+#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
+  friend void protobuf_AddDesc_crx3_2eproto_impl();
+#else
+  friend void protobuf_AddDesc_crx3_2eproto();
+#endif
+  friend void protobuf_AssignDesc_crx3_2eproto();
+  friend void protobuf_ShutdownFile_crx3_2eproto();
+
+  void InitAsDefaultInstance();
+  static SignedData* default_instance_;
+};
+// ===================================================================
+
+// ===================================================================
+
+#if !PROTOBUF_INLINE_NOT_IN_HEADERS
+// CrxFileHeader
+
+// repeated .crx_file.AsymmetricKeyProof sha256_with_rsa = 2;
+inline int CrxFileHeader::sha256_with_rsa_size() const {
+  return sha256_with_rsa_.size();
+}
+inline void CrxFileHeader::clear_sha256_with_rsa() {
+  sha256_with_rsa_.Clear();
+}
+inline const ::crx_file::AsymmetricKeyProof& CrxFileHeader::sha256_with_rsa(
+    int index) const {
+  // @@protoc_insertion_point(field_get:crx_file.CrxFileHeader.sha256_with_rsa)
+  return sha256_with_rsa_.Get(index);
+}
+inline ::crx_file::AsymmetricKeyProof* CrxFileHeader::mutable_sha256_with_rsa(
+    int index) {
+  // @@protoc_insertion_point(field_mutable:crx_file.CrxFileHeader.sha256_with_rsa)
+  return sha256_with_rsa_.Mutable(index);
+}
+inline ::crx_file::AsymmetricKeyProof* CrxFileHeader::add_sha256_with_rsa() {
+  // @@protoc_insertion_point(field_add:crx_file.CrxFileHeader.sha256_with_rsa)
+  return sha256_with_rsa_.Add();
+}
+inline ::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>*
+CrxFileHeader::mutable_sha256_with_rsa() {
+  // @@protoc_insertion_point(field_mutable_list:crx_file.CrxFileHeader.sha256_with_rsa)
+  return &sha256_with_rsa_;
+}
+inline const ::google::protobuf::RepeatedPtrField<
+    ::crx_file::AsymmetricKeyProof>&
+CrxFileHeader::sha256_with_rsa() const {
+  // @@protoc_insertion_point(field_list:crx_file.CrxFileHeader.sha256_with_rsa)
+  return sha256_with_rsa_;
+}
+
+// repeated .crx_file.AsymmetricKeyProof sha256_with_ecdsa = 3;
+inline int CrxFileHeader::sha256_with_ecdsa_size() const {
+  return sha256_with_ecdsa_.size();
+}
+inline void CrxFileHeader::clear_sha256_with_ecdsa() {
+  sha256_with_ecdsa_.Clear();
+}
+inline const ::crx_file::AsymmetricKeyProof& CrxFileHeader::sha256_with_ecdsa(
+    int index) const {
+  // @@protoc_insertion_point(field_get:crx_file.CrxFileHeader.sha256_with_ecdsa)
+  return sha256_with_ecdsa_.Get(index);
+}
+inline ::crx_file::AsymmetricKeyProof* CrxFileHeader::mutable_sha256_with_ecdsa(
+    int index) {
+  // @@protoc_insertion_point(field_mutable:crx_file.CrxFileHeader.sha256_with_ecdsa)
+  return sha256_with_ecdsa_.Mutable(index);
+}
+inline ::crx_file::AsymmetricKeyProof* CrxFileHeader::add_sha256_with_ecdsa() {
+  // @@protoc_insertion_point(field_add:crx_file.CrxFileHeader.sha256_with_ecdsa)
+  return sha256_with_ecdsa_.Add();
+}
+inline ::google::protobuf::RepeatedPtrField< ::crx_file::AsymmetricKeyProof>*
+CrxFileHeader::mutable_sha256_with_ecdsa() {
+  // @@protoc_insertion_point(field_mutable_list:crx_file.CrxFileHeader.sha256_with_ecdsa)
+  return &sha256_with_ecdsa_;
+}
+inline const ::google::protobuf::RepeatedPtrField<
+    ::crx_file::AsymmetricKeyProof>&
+CrxFileHeader::sha256_with_ecdsa() const {
+  // @@protoc_insertion_point(field_list:crx_file.CrxFileHeader.sha256_with_ecdsa)
+  return sha256_with_ecdsa_;
+}
+
+// optional bytes signed_header_data = 10000;
+inline bool CrxFileHeader::has_signed_header_data() const {
+  return (_has_bits_[0] & 0x00000004u) != 0;
+}
+inline void CrxFileHeader::set_has_signed_header_data() {
+  _has_bits_[0] |= 0x00000004u;
+}
+inline void CrxFileHeader::clear_has_signed_header_data() {
+  _has_bits_[0] &= ~0x00000004u;
+}
+inline void CrxFileHeader::clear_signed_header_data() {
+  signed_header_data_.ClearToEmptyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  clear_has_signed_header_data();
+}
+inline const ::std::string& CrxFileHeader::signed_header_data() const {
+  // @@protoc_insertion_point(field_get:crx_file.CrxFileHeader.signed_header_data)
+  return signed_header_data_.GetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline void CrxFileHeader::set_signed_header_data(const ::std::string& value) {
+  set_has_signed_header_data();
+  signed_header_data_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
+  // @@protoc_insertion_point(field_set:crx_file.CrxFileHeader.signed_header_data)
+}
+inline void CrxFileHeader::set_signed_header_data(const char* value) {
+  set_has_signed_header_data();
+  signed_header_data_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(value));
+  // @@protoc_insertion_point(field_set_char:crx_file.CrxFileHeader.signed_header_data)
+}
+inline void CrxFileHeader::set_signed_header_data(const void* value,
+                                                  size_t size) {
+  set_has_signed_header_data();
+  signed_header_data_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(reinterpret_cast<const char*>(value), size));
+  // @@protoc_insertion_point(field_set_pointer:crx_file.CrxFileHeader.signed_header_data)
+}
+inline ::std::string* CrxFileHeader::mutable_signed_header_data() {
+  set_has_signed_header_data();
+  // @@protoc_insertion_point(field_mutable:crx_file.CrxFileHeader.signed_header_data)
+  return signed_header_data_.MutableNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline ::std::string* CrxFileHeader::release_signed_header_data() {
+  // @@protoc_insertion_point(field_release:crx_file.CrxFileHeader.signed_header_data)
+  clear_has_signed_header_data();
+  return signed_header_data_.ReleaseNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline void CrxFileHeader::set_allocated_signed_header_data(
+    ::std::string* signed_header_data) {
+  if (signed_header_data != NULL) {
+    set_has_signed_header_data();
+  } else {
+    clear_has_signed_header_data();
+  }
+  signed_header_data_.SetAllocatedNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      signed_header_data);
+  // @@protoc_insertion_point(field_set_allocated:crx_file.CrxFileHeader.signed_header_data)
+}
+
+// -------------------------------------------------------------------
+
+// AsymmetricKeyProof
+
+// optional bytes public_key = 1;
+inline bool AsymmetricKeyProof::has_public_key() const {
+  return (_has_bits_[0] & 0x00000001u) != 0;
+}
+inline void AsymmetricKeyProof::set_has_public_key() {
+  _has_bits_[0] |= 0x00000001u;
+}
+inline void AsymmetricKeyProof::clear_has_public_key() {
+  _has_bits_[0] &= ~0x00000001u;
+}
+inline void AsymmetricKeyProof::clear_public_key() {
+  public_key_.ClearToEmptyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  clear_has_public_key();
+}
+inline const ::std::string& AsymmetricKeyProof::public_key() const {
+  // @@protoc_insertion_point(field_get:crx_file.AsymmetricKeyProof.public_key)
+  return public_key_.GetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline void AsymmetricKeyProof::set_public_key(const ::std::string& value) {
+  set_has_public_key();
+  public_key_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
+  // @@protoc_insertion_point(field_set:crx_file.AsymmetricKeyProof.public_key)
+}
+inline void AsymmetricKeyProof::set_public_key(const char* value) {
+  set_has_public_key();
+  public_key_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(value));
+  // @@protoc_insertion_point(field_set_char:crx_file.AsymmetricKeyProof.public_key)
+}
+inline void AsymmetricKeyProof::set_public_key(const void* value, size_t size) {
+  set_has_public_key();
+  public_key_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(reinterpret_cast<const char*>(value), size));
+  // @@protoc_insertion_point(field_set_pointer:crx_file.AsymmetricKeyProof.public_key)
+}
+inline ::std::string* AsymmetricKeyProof::mutable_public_key() {
+  set_has_public_key();
+  // @@protoc_insertion_point(field_mutable:crx_file.AsymmetricKeyProof.public_key)
+  return public_key_.MutableNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline ::std::string* AsymmetricKeyProof::release_public_key() {
+  // @@protoc_insertion_point(field_release:crx_file.AsymmetricKeyProof.public_key)
+  clear_has_public_key();
+  return public_key_.ReleaseNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline void AsymmetricKeyProof::set_allocated_public_key(
+    ::std::string* public_key) {
+  if (public_key != NULL) {
+    set_has_public_key();
+  } else {
+    clear_has_public_key();
+  }
+  public_key_.SetAllocatedNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), public_key);
+  // @@protoc_insertion_point(field_set_allocated:crx_file.AsymmetricKeyProof.public_key)
+}
+
+// optional bytes signature = 2;
+inline bool AsymmetricKeyProof::has_signature() const {
+  return (_has_bits_[0] & 0x00000002u) != 0;
+}
+inline void AsymmetricKeyProof::set_has_signature() {
+  _has_bits_[0] |= 0x00000002u;
+}
+inline void AsymmetricKeyProof::clear_has_signature() {
+  _has_bits_[0] &= ~0x00000002u;
+}
+inline void AsymmetricKeyProof::clear_signature() {
+  signature_.ClearToEmptyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  clear_has_signature();
+}
+inline const ::std::string& AsymmetricKeyProof::signature() const {
+  // @@protoc_insertion_point(field_get:crx_file.AsymmetricKeyProof.signature)
+  return signature_.GetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline void AsymmetricKeyProof::set_signature(const ::std::string& value) {
+  set_has_signature();
+  signature_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
+  // @@protoc_insertion_point(field_set:crx_file.AsymmetricKeyProof.signature)
+}
+inline void AsymmetricKeyProof::set_signature(const char* value) {
+  set_has_signature();
+  signature_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(value));
+  // @@protoc_insertion_point(field_set_char:crx_file.AsymmetricKeyProof.signature)
+}
+inline void AsymmetricKeyProof::set_signature(const void* value, size_t size) {
+  set_has_signature();
+  signature_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(reinterpret_cast<const char*>(value), size));
+  // @@protoc_insertion_point(field_set_pointer:crx_file.AsymmetricKeyProof.signature)
+}
+inline ::std::string* AsymmetricKeyProof::mutable_signature() {
+  set_has_signature();
+  // @@protoc_insertion_point(field_mutable:crx_file.AsymmetricKeyProof.signature)
+  return signature_.MutableNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline ::std::string* AsymmetricKeyProof::release_signature() {
+  // @@protoc_insertion_point(field_release:crx_file.AsymmetricKeyProof.signature)
+  clear_has_signature();
+  return signature_.ReleaseNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline void AsymmetricKeyProof::set_allocated_signature(
+    ::std::string* signature) {
+  if (signature != NULL) {
+    set_has_signature();
+  } else {
+    clear_has_signature();
+  }
+  signature_.SetAllocatedNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), signature);
+  // @@protoc_insertion_point(field_set_allocated:crx_file.AsymmetricKeyProof.signature)
+}
+
+// -------------------------------------------------------------------
+
+// SignedData
+
+// optional bytes crx_id = 1;
+inline bool SignedData::has_crx_id() const {
+  return (_has_bits_[0] & 0x00000001u) != 0;
+}
+inline void SignedData::set_has_crx_id() {
+  _has_bits_[0] |= 0x00000001u;
+}
+inline void SignedData::clear_has_crx_id() {
+  _has_bits_[0] &= ~0x00000001u;
+}
+inline void SignedData::clear_crx_id() {
+  crx_id_.ClearToEmptyNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  clear_has_crx_id();
+}
+inline const ::std::string& SignedData::crx_id() const {
+  // @@protoc_insertion_point(field_get:crx_file.SignedData.crx_id)
+  return crx_id_.GetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline void SignedData::set_crx_id(const ::std::string& value) {
+  set_has_crx_id();
+  crx_id_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
+  // @@protoc_insertion_point(field_set:crx_file.SignedData.crx_id)
+}
+inline void SignedData::set_crx_id(const char* value) {
+  set_has_crx_id();
+  crx_id_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(value));
+  // @@protoc_insertion_point(field_set_char:crx_file.SignedData.crx_id)
+}
+inline void SignedData::set_crx_id(const void* value, size_t size) {
+  set_has_crx_id();
+  crx_id_.SetNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(reinterpret_cast<const char*>(value), size));
+  // @@protoc_insertion_point(field_set_pointer:crx_file.SignedData.crx_id)
+}
+inline ::std::string* SignedData::mutable_crx_id() {
+  set_has_crx_id();
+  // @@protoc_insertion_point(field_mutable:crx_file.SignedData.crx_id)
+  return crx_id_.MutableNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline ::std::string* SignedData::release_crx_id() {
+  // @@protoc_insertion_point(field_release:crx_file.SignedData.crx_id)
+  clear_has_crx_id();
+  return crx_id_.ReleaseNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline void SignedData::set_allocated_crx_id(::std::string* crx_id) {
+  if (crx_id != NULL) {
+    set_has_crx_id();
+  } else {
+    clear_has_crx_id();
+  }
+  crx_id_.SetAllocatedNoArena(
+      &::google::protobuf::internal::GetEmptyStringAlreadyInited(), crx_id);
+  // @@protoc_insertion_point(field_set_allocated:crx_file.SignedData.crx_id)
+}
+
+#endif  // !PROTOBUF_INLINE_NOT_IN_HEADERS
+// -------------------------------------------------------------------
+
+// -------------------------------------------------------------------
+
+// @@protoc_insertion_point(namespace_scope)
+
+}  // namespace crx_file
+
+// @@protoc_insertion_point(global_scope)
+
+#endif  // PROTOBUF_crx3_2eproto__INCLUDED
diff --git a/src/components/crx_file/crx3.proto b/src/components/crx_file/crx3.proto
new file mode 100644
index 0000000..2ea91f1
--- /dev/null
+++ b/src/components/crx_file/crx3.proto
@@ -0,0 +1,55 @@
+// 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
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package crx_file;
+
+// A CRX₃ file is a binary file of the following format:
+// [4 octets]: "Cr24", a magic number.
+// [4 octets]: The version of the *.crx file format used (currently 3).
+// [4 octets]: N, little-endian, the length of the header section.
+// [N octets]: The header (the binary encoding of a CrxFileHeader).
+// [M octets]: The ZIP archive.
+// Clients should reject CRX₃ files that contain an N that is too large for the
+// client to safely handle in memory.
+
+message CrxFileHeader {
+  // PSS signature with RSA public key. The public key is formatted as a
+  // X.509 SubjectPublicKeyInfo block, as in CRXâ‚‚. In the common case of a
+  // developer key proof, the first 128 bits of the SHA-256 hash of the
+  // public key must equal the crx_id.
+  repeated AsymmetricKeyProof sha256_with_rsa = 2;
+
+  // ECDSA signature, using the NIST P-256 curve. Public key appears in
+  // named-curve format.
+  // The pinned algorithm will be this, at least on 2017-01-01.
+  repeated AsymmetricKeyProof sha256_with_ecdsa = 3;
+
+  // The binary form of a SignedData message. We do not use a nested
+  // SignedData message, as handlers of this message must verify the proofs
+  // on exactly these bytes, so it is convenient to parse in two steps.
+  //
+  // All proofs in this CrxFile message are on the value
+  // "CRX3 SignedData\x00" + signed_header_size + signed_header_data +
+  // archive, where "\x00" indicates an octet with value 0, "CRX3 SignedData"
+  // is encoded using UTF-8, signed_header_size is the size in octets of the
+  // contents of this field and is encoded using 4 octets in little-endian
+  // order, signed_header_data is exactly the content of this field, and
+  // archive is the remaining contents of the file following the header.
+  optional bytes signed_header_data = 10000;
+}
+
+message AsymmetricKeyProof {
+  optional bytes public_key = 1;
+  optional bytes signature = 2;
+}
+
+message SignedData {
+  // This is simple binary, not UTF-8 encoded mpdecimal; i.e. it is exactly
+  // 16 bytes long.
+  optional bytes crx_id = 1;
+}
diff --git a/src/components/crx_file/crx_creator.cc b/src/components/crx_file/crx_creator.cc
new file mode 100644
index 0000000..8ec6bb3
--- /dev/null
+++ b/src/components/crx_file/crx_creator.cc
@@ -0,0 +1,129 @@
+// 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 "components/crx_file/crx_creator.h"
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/stl_util.h"
+#include "components/crx_file/crx3.pb.h"
+#include "components/crx_file/crx_file.h"
+#include "crypto/rsa_private_key.h"
+#include "crypto/sha2.h"
+#include "crypto/signature_creator.h"
+
+namespace crx_file {
+
+namespace {
+
+std::string GetCrxId(const std::string& key) {
+  uint8_t hash[16] = {};  // CRX IDs are 16 bytes long.
+  crypto::SHA256HashString(key, hash, sizeof(hash));
+  static_assert(sizeof(char) == sizeof(uint8_t), "Unsupported char size.");
+  return std::string(reinterpret_cast<char*>(hash), sizeof(hash));
+}
+
+// Read to the end of the file, updating the signer.
+CreatorResult ReadAndSignArchive(base::File* file,
+                                 crypto::SignatureCreator* signer,
+                                 std::vector<uint8_t>* signature) {
+  uint8_t buffer[1 << 12] = {};
+  int read = 0;
+  static_assert(sizeof(char) == sizeof(uint8_t), "Unsupported char size.");
+  while ((read = file->ReadAtCurrentPos(reinterpret_cast<char*>(buffer),
+                                        base::size(buffer))) > 0) {
+    if (!signer->Update(buffer, read))
+      return CreatorResult::ERROR_SIGNING_FAILURE;
+  }
+  if (read < 0)
+    return CreatorResult::ERROR_SIGNING_FAILURE;
+  return signer->Final(signature) ? CreatorResult::OK
+                                  : CreatorResult::ERROR_SIGNING_FAILURE;
+}
+
+bool WriteBuffer(base::File* file, const char buffer[], int len) {
+  return file->WriteAtCurrentPos(buffer, len) == len;
+}
+
+bool WriteArchive(base::File* out, base::File* in) {
+  char buffer[1 << 12] = {};
+  int read = 0;
+  in->Seek(base::File::Whence::FROM_BEGIN, 0);
+  while ((read = in->ReadAtCurrentPos(buffer, base::size(buffer))) > 0) {
+    if (out->WriteAtCurrentPos(buffer, read) != read)
+      return false;
+  }
+  return read == 0;
+}
+
+}  // namespace
+
+CreatorResult Create(const base::FilePath& output_path,
+                     const base::FilePath& zip_path,
+                     crypto::RSAPrivateKey* signing_key) {
+  // Get the public key.
+  std::vector<uint8_t> public_key;
+  signing_key->ExportPublicKey(&public_key);
+  const std::string public_key_str(public_key.begin(), public_key.end());
+
+  // Assemble SignedData section.
+  SignedData signed_header_data;
+  signed_header_data.set_crx_id(GetCrxId(public_key_str));
+  const std::string signed_header_data_str =
+      signed_header_data.SerializeAsString();
+  const int signed_header_size = signed_header_data_str.size();
+  const uint8_t signed_header_size_octets[] = {
+      signed_header_size, signed_header_size >> 8, signed_header_size >> 16,
+      signed_header_size >> 24};
+
+  // Create a signer, init with purpose, SignedData length, run SignedData
+  // through, run ZIP through.
+  auto signer = crypto::SignatureCreator::Create(
+      signing_key, crypto::SignatureCreator::HashAlgorithm::SHA256);
+  signer->Update(kSignatureContext, base::size(kSignatureContext));
+  signer->Update(signed_header_size_octets,
+                 base::size(signed_header_size_octets));
+  signer->Update(
+      reinterpret_cast<const uint8_t*>(signed_header_data_str.data()),
+      signed_header_data_str.size());
+  base::File file(zip_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+  if (!file.IsValid())
+    return CreatorResult::ERROR_FILE_NOT_READABLE;
+  std::vector<uint8_t> signature;
+  const CreatorResult signing_result =
+      ReadAndSignArchive(&file, signer.get(), &signature);
+  if (signing_result != CreatorResult::OK)
+    return signing_result;
+
+  // Create CRXFileHeader.
+  CrxFileHeader header;
+  AsymmetricKeyProof* proof = header.add_sha256_with_rsa();
+  proof->set_public_key(public_key_str);
+  proof->set_signature(std::string(signature.begin(), signature.end()));
+  header.set_signed_header_data(signed_header_data_str);
+  const std::string header_str = header.SerializeAsString();
+  const int header_size = header_str.size();
+  const uint8_t header_size_octets[] = {header_size, header_size >> 8,
+                                        header_size >> 16, header_size >> 24};
+
+  // Write CRX.
+  const uint8_t format_version_octets[] = {3, 0, 0, 0};
+  base::File crx(output_path,
+                 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+  if (!crx.IsValid())
+    return CreatorResult::ERROR_FILE_NOT_WRITABLE;
+  static_assert(sizeof(char) == sizeof(uint8_t), "Unsupported char size.");
+  if (!WriteBuffer(&crx, kCrxFileHeaderMagic, kCrxFileHeaderMagicSize) ||
+      !WriteBuffer(&crx, reinterpret_cast<const char*>(format_version_octets),
+                   base::size(format_version_octets)) ||
+      !WriteBuffer(&crx, reinterpret_cast<const char*>(header_size_octets),
+                   base::size(header_size_octets)) ||
+      !WriteBuffer(&crx, header_str.c_str(), header_str.length()) ||
+      !WriteArchive(&crx, &file)) {
+    return CreatorResult::ERROR_FILE_WRITE_FAILURE;
+  }
+  return CreatorResult::OK;
+}
+
+}  // namespace crx_file
diff --git a/src/components/crx_file/crx_creator.h b/src/components/crx_file/crx_creator.h
new file mode 100644
index 0000000..a8c2094
--- /dev/null
+++ b/src/components/crx_file/crx_creator.h
@@ -0,0 +1,35 @@
+// 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 COMPONENTS_CRX_FILE_CRX_CREATOR_H_
+#define COMPONENTS_CRX_FILE_CRX_CREATOR_H_
+
+namespace base {
+class FilePath;
+}  // namespace base
+
+namespace crypto {
+class RSAPrivateKey;
+}  // namespace crypto
+
+namespace crx_file {
+
+enum class CreatorResult {
+  OK,  // The CRX file was successfully created.
+  ERROR_SIGNING_FAILURE,
+  ERROR_FILE_NOT_READABLE,
+  ERROR_FILE_NOT_WRITABLE,
+  ERROR_FILE_WRITE_FAILURE,
+};
+
+// Create a CRX3 file at |output_path|, using the contents of the ZIP archive
+// located at |zip_path| and signing with (and deriving the CRX ID from)
+// |signing_key|.
+CreatorResult Create(const base::FilePath& output_path,
+                     const base::FilePath& zip_path,
+                     crypto::RSAPrivateKey* signing_key);
+
+}  // namespace crx_file
+
+#endif  // COMPONENTS_CRX_FILE_CRX_CREATOR_H_
diff --git a/src/components/crx_file/crx_creator_unittest.cc b/src/components/crx_file/crx_creator_unittest.cc
new file mode 100644
index 0000000..086a5a5
--- /dev/null
+++ b/src/components/crx_file/crx_creator_unittest.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 "components/crx_file/crx_creator.h"
+#include "base/base64.h"
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "components/crx_file/crx_verifier.h"
+#include "crypto/rsa_private_key.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+base::FilePath TestFile(const std::string& file) {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  return path.AppendASCII("components")
+      .AppendASCII("test")
+      .AppendASCII("data")
+      .AppendASCII("crx_file")
+      .AppendASCII(file);
+}
+
+}  // namespace
+
+namespace crx_file {
+
+using CrxCreatorTest = testing::Test;
+
+TEST_F(CrxCreatorTest, Create) {
+  // Set up a signing key.
+  auto signing_key = crypto::RSAPrivateKey::Create(4096);
+  std::vector<uint8_t> public_key;
+  signing_key->ExportPublicKey(&public_key);
+  std::string expected_public_key;
+  base::Base64Encode(std::string(public_key.begin(), public_key.end()),
+                     &expected_public_key);
+
+  // Create a CRX File.
+  base::FilePath tempFile;
+  EXPECT_TRUE(base::CreateTemporaryFile(&tempFile));
+  EXPECT_EQ(CreatorResult::OK,
+            Create(tempFile, TestFile("sample.zip"), signing_key.get()));
+
+  // Test that the created file can be verified.
+  const std::vector<std::vector<uint8_t>> keys;
+  const std::vector<uint8_t> hash;
+  std::string public_key_in_crx;
+  EXPECT_EQ(VerifierResult::OK_FULL,
+            Verify(tempFile, VerifierFormat::CRX3, keys, hash,
+                   &public_key_in_crx, nullptr));
+  EXPECT_EQ(expected_public_key, public_key_in_crx);
+
+  // Delete the file.
+  EXPECT_TRUE(base::DeleteFile(tempFile, false));
+}
+
+}  // namespace crx_file
diff --git a/src/components/crx_file/crx_file.gyp b/src/components/crx_file/crx_file.gyp
new file mode 100644
index 0000000..bff0031
--- /dev/null
+++ b/src/components/crx_file/crx_file.gyp
@@ -0,0 +1,51 @@
+# Copyright 2019 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'targets': [
+    {
+      'target_name': 'crx_file',
+      'type': 'static_library',
+      'sources': [
+        'crx_file.h',
+        'crx_verifier.cc',
+        'crx_verifier.h',
+        'crx3.pb.cc',
+        'crx3.pb.h',
+        'crx3.proto',
+        'id_util.cc',
+        'id_util.h',
+      ],
+      'dependencies': [
+        '<(DEPTH)/third_party/protobuf/protobuf.gyp:protobuf_lite',
+        '<(DEPTH)/crypto/crypto.gyp:crypto',
+        '<(DEPTH)/base/base.gyp:base',
+      ],
+    },
+    {
+      'target_name': 'crx_creator',
+      'type': 'static_library',
+      'sources': [
+        'crx_creator.cc',
+        'crx_creator.h',
+      ],
+      'dependencies': [
+        'crx_file',
+        '<(DEPTH)/third_party/protobuf/protobuf.gyp:protobuf_lite',
+        '<(DEPTH)/crypto/crypto.gyp:crypto',
+        '<(DEPTH)/base/base.gyp:base',
+      ],
+    },
+  ]
+}
diff --git a/src/components/crx_file/crx_file.h b/src/components/crx_file/crx_file.h
new file mode 100644
index 0000000..4e258e1
--- /dev/null
+++ b/src/components/crx_file/crx_file.h
@@ -0,0 +1,18 @@
+// 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 COMPONENTS_CRX_FILE_CRX_FILE_H_
+#define COMPONENTS_CRX_FILE_CRX_FILE_H_
+
+namespace crx_file {
+
+// The magic string embedded in the header.
+constexpr char kCrxFileHeaderMagic[] = "Cr24";
+constexpr char kCrxDiffFileHeaderMagic[] = "CrOD";
+constexpr int kCrxFileHeaderMagicSize = 4;
+constexpr unsigned char kSignatureContext[] = u8"CRX3 SignedData";
+
+}  // namespace crx_file
+
+#endif  // COMPONENTS_CRX_FILE_CRX_FILE_H_
diff --git a/src/components/crx_file/crx_verifier.cc b/src/components/crx_file/crx_verifier.cc
new file mode 100644
index 0000000..b8a875e
--- /dev/null
+++ b/src/components/crx_file/crx_verifier.cc
@@ -0,0 +1,333 @@
+// 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 "components/crx_file/crx_verifier.h"
+
+#include <cstring>
+#include <iterator>
+#include <memory>
+#include <set>
+#include <utility>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/crx_file/crx3.pb.h"
+#include "components/crx_file/crx_file.h"
+#include "components/crx_file/id_util.h"
+#include "crypto/secure_hash.h"
+#include "crypto/secure_util.h"
+#include "crypto/sha2.h"
+#include "crypto/signature_verifier.h"
+
+namespace crx_file {
+
+namespace {
+
+// The maximum size the Crx2 parser will tolerate for a public key.
+constexpr uint32_t kMaxPublicKeySize = 1 << 16;
+
+// The maximum size the Crx2 parser will tolerate for a signature.
+constexpr uint32_t kMaxSignatureSize = 1 << 16;
+
+// The maximum size the Crx3 parser will tolerate for a header.
+constexpr uint32_t kMaxHeaderSize = 1 << 18;
+
+// The SHA256 hash of the DER SPKI "ecdsa_2017_public" Crx3 key.
+constexpr uint8_t kPublisherKeyHash[] = {
+    0x61, 0xf7, 0xf2, 0xa6, 0xbf, 0xcf, 0x74, 0xcd, 0x0b, 0xc1, 0xfe,
+    0x24, 0x97, 0xcc, 0x9b, 0x04, 0x25, 0x4c, 0x65, 0x8f, 0x79, 0xf2,
+    0x14, 0x53, 0x92, 0x86, 0x7e, 0xa8, 0x36, 0x63, 0x67, 0xcf};
+
+// The SHA256 hash of the DER SPKI "ecdsa_2017_public" Crx3 test key.
+constexpr uint8_t kPublisherTestKeyHash[] = {
+    0x6c, 0x46, 0x41, 0x3b, 0x00, 0xd0, 0xfa, 0x0e, 0x72, 0xc8, 0xd2,
+    0x5f, 0x64, 0xf3, 0xa6, 0x17, 0x03, 0x0d, 0xde, 0x21, 0x61, 0xbe,
+    0xb7, 0x95, 0x91, 0x95, 0x83, 0x68, 0x12, 0xe9, 0x78, 0x1e};
+
+using VerifierCollection =
+    std::vector<std::unique_ptr<crypto::SignatureVerifier>>;
+using RepeatedProof = google::protobuf::RepeatedPtrField<AsymmetricKeyProof>;
+
+int ReadAndHashBuffer(uint8_t* buffer,
+                      int length,
+                      base::File* file,
+                      crypto::SecureHash* hash) {
+  static_assert(sizeof(char) == sizeof(uint8_t), "Unsupported char size.");
+  int read = file->ReadAtCurrentPos(reinterpret_cast<char*>(buffer), length);
+  if (read > 0)
+    hash->Update(buffer, read);
+  return read;
+}
+
+// Returns UINT32_MAX in the case of an unexpected EOF or read error, else
+// returns the read uint32.
+uint32_t ReadAndHashLittleEndianUInt32(base::File* file,
+                                       crypto::SecureHash* hash) {
+  uint8_t buffer[4] = {};
+  if (ReadAndHashBuffer(buffer, 4, file, hash) != 4)
+    return UINT32_MAX;
+  return buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0];
+}
+
+// Read to the end of the file, updating the hash and all verifiers.
+bool ReadHashAndVerifyArchive(base::File* file,
+                              crypto::SecureHash* hash,
+                              const VerifierCollection& verifiers) {
+  uint8_t buffer[1 << 12] = {};
+  size_t len = 0;
+  while ((len = ReadAndHashBuffer(buffer, base::size(buffer), file, hash)) >
+         0) {
+    for (auto& verifier : verifiers)
+      verifier->VerifyUpdate(base::make_span(buffer, len));
+  }
+  for (auto& verifier : verifiers) {
+    if (!verifier->VerifyFinal())
+      return false;
+  }
+  return len == 0;
+}
+
+// The remaining contents of a Crx3 file are [header-size][header][archive].
+// [header] is an encoded protocol buffer and contains both a signed and
+// unsigned section. The unsigned section contains a set of key/signature pairs,
+// and the signed section is the encoding of another protocol buffer. All
+// signatures cover [prefix][signed-header-size][signed-header][archive].
+VerifierResult VerifyCrx3(
+    base::File* file,
+    crypto::SecureHash* hash,
+    const std::vector<std::vector<uint8_t>>& required_key_hashes,
+    std::string* public_key,
+    std::string* crx_id,
+    bool require_publisher_key,
+    bool accept_publisher_test_key) {
+  // Parse [header-size] and [header].
+  const uint32_t header_size = ReadAndHashLittleEndianUInt32(file, hash);
+  if (header_size > kMaxHeaderSize)
+    return VerifierResult::ERROR_HEADER_INVALID;
+  std::vector<uint8_t> header_bytes(header_size);
+  // Assuming kMaxHeaderSize can fit in an int, the following cast is safe.
+  if (ReadAndHashBuffer(header_bytes.data(), header_size, file, hash) !=
+      static_cast<int>(header_size))
+    return VerifierResult::ERROR_HEADER_INVALID;
+  CrxFileHeader header;
+  if (!header.ParseFromArray(header_bytes.data(), header_size))
+    return VerifierResult::ERROR_HEADER_INVALID;
+
+  // Parse [signed-header].
+  const std::string& signed_header_data_str = header.signed_header_data();
+  SignedData signed_header_data;
+  if (!signed_header_data.ParseFromString(signed_header_data_str))
+    return VerifierResult::ERROR_HEADER_INVALID;
+  const std::string& crx_id_encoded = signed_header_data.crx_id();
+  const std::string declared_crx_id = id_util::GenerateIdFromHex(
+      base::HexEncode(crx_id_encoded.data(), crx_id_encoded.size()));
+
+  // Create a little-endian representation of [signed-header-size].
+  const int signed_header_size = signed_header_data_str.size();
+  const uint8_t header_size_octets[] = {
+      static_cast<uint8_t>(signed_header_size),
+      static_cast<uint8_t>(signed_header_size >> 8),
+      static_cast<uint8_t>(signed_header_size >> 16),
+      static_cast<uint8_t>(signed_header_size >> 24)};
+
+  // Create a set of all required key hashes.
+  std::set<std::vector<uint8_t>> required_key_set(required_key_hashes.begin(),
+                                                  required_key_hashes.end());
+
+  using ProofFetcher = const RepeatedProof& (CrxFileHeader::*)() const;
+  ProofFetcher rsa = &CrxFileHeader::sha256_with_rsa;
+  ProofFetcher ecdsa = &CrxFileHeader::sha256_with_ecdsa;
+
+  std::string public_key_bytes;
+  VerifierCollection verifiers;
+  verifiers.reserve(header.sha256_with_rsa_size() +
+                    header.sha256_with_ecdsa_size());
+  const std::vector<
+      std::pair<ProofFetcher, crypto::SignatureVerifier::SignatureAlgorithm>>
+      proof_types = {
+          std::make_pair(rsa, crypto::SignatureVerifier::RSA_PKCS1_SHA256),
+          std::make_pair(ecdsa, crypto::SignatureVerifier::ECDSA_SHA256)};
+
+  std::vector<uint8_t> publisher_key(std::begin(kPublisherKeyHash),
+                                     std::end(kPublisherKeyHash));
+  base::Optional<std::vector<uint8_t>> publisher_test_key;
+  if (accept_publisher_test_key) {
+    publisher_test_key.emplace(std::begin(kPublisherTestKeyHash),
+                               std::end(kPublisherTestKeyHash));
+  }
+  bool found_publisher_key = false;
+
+  // Initialize all verifiers and update them with
+  // [prefix][signed-header-size][signed-header].
+  // Clear any elements of required_key_set that are encountered, and watch for
+  // the developer key.
+  for (const auto& proof_type : proof_types) {
+    for (const auto& proof : (header.*proof_type.first)()) {
+      const std::string& key = proof.public_key();
+      const std::string& sig = proof.signature();
+      if (id_util::GenerateId(key) == declared_crx_id)
+        public_key_bytes = key;
+      std::vector<uint8_t> key_hash(crypto::kSHA256Length);
+      crypto::SHA256HashString(key, key_hash.data(), key_hash.size());
+      required_key_set.erase(key_hash);
+      DCHECK_EQ(accept_publisher_test_key, publisher_test_key.has_value());
+      found_publisher_key =
+          found_publisher_key || key_hash == publisher_key ||
+          (accept_publisher_test_key && key_hash == *publisher_test_key);
+      auto v = std::make_unique<crypto::SignatureVerifier>();
+      static_assert(sizeof(unsigned char) == sizeof(uint8_t),
+                    "Unsupported char size.");
+      if (!v->VerifyInit(proof_type.second,
+                         base::as_bytes(base::make_span(sig)),
+                         base::as_bytes(base::make_span(key))))
+        return VerifierResult::ERROR_SIGNATURE_INITIALIZATION_FAILED;
+      v->VerifyUpdate(kSignatureContext);
+      v->VerifyUpdate(header_size_octets);
+      v->VerifyUpdate(base::as_bytes(base::make_span(signed_header_data_str)));
+      verifiers.push_back(std::move(v));
+    }
+  }
+  if (public_key_bytes.empty() || !required_key_set.empty())
+    return VerifierResult::ERROR_REQUIRED_PROOF_MISSING;
+
+  if (require_publisher_key && !found_publisher_key)
+    return VerifierResult::ERROR_REQUIRED_PROOF_MISSING;
+
+  // Update and finalize the verifiers with [archive].
+  if (!ReadHashAndVerifyArchive(file, hash, verifiers))
+    return VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED;
+
+  base::Base64Encode(public_key_bytes, public_key);
+  *crx_id = declared_crx_id;
+  return VerifierResult::OK_FULL;
+}
+
+VerifierResult VerifyCrx2(
+    base::File* file,
+    crypto::SecureHash* hash,
+    const std::vector<std::vector<uint8_t>>& required_key_hashes,
+    std::string* public_key,
+    std::string* crx_id) {
+  const uint32_t key_size = ReadAndHashLittleEndianUInt32(file, hash);
+  if (key_size > kMaxPublicKeySize)
+    return VerifierResult::ERROR_HEADER_INVALID;
+  const uint32_t sig_size = ReadAndHashLittleEndianUInt32(file, hash);
+  if (sig_size > kMaxSignatureSize)
+    return VerifierResult::ERROR_HEADER_INVALID;
+  std::vector<uint8_t> key(key_size);
+  if (ReadAndHashBuffer(key.data(), key_size, file, hash) !=
+      static_cast<int>(key_size))
+    return VerifierResult::ERROR_HEADER_INVALID;
+  for (const auto& expected_hash : required_key_hashes) {
+    // In practice we expect zero or one key_hashes_ for Crx2 files.
+    std::vector<uint8_t> hash(crypto::kSHA256Length);
+    std::unique_ptr<crypto::SecureHash> sha256 =
+        crypto::SecureHash::Create(crypto::SecureHash::SHA256);
+    sha256->Update(key.data(), key.size());
+    sha256->Finish(hash.data(), hash.size());
+    if (hash != expected_hash)
+      return VerifierResult::ERROR_REQUIRED_PROOF_MISSING;
+  }
+
+  std::vector<uint8_t> sig(sig_size);
+  if (ReadAndHashBuffer(sig.data(), sig_size, file, hash) !=
+      static_cast<int>(sig_size))
+    return VerifierResult::ERROR_HEADER_INVALID;
+  std::vector<std::unique_ptr<crypto::SignatureVerifier>> verifiers;
+  verifiers.push_back(std::make_unique<crypto::SignatureVerifier>());
+  if (!verifiers[0]->VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, sig,
+                                key)) {
+    return VerifierResult::ERROR_SIGNATURE_INITIALIZATION_FAILED;
+  }
+
+  if (!ReadHashAndVerifyArchive(file, hash, verifiers))
+    return VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED;
+
+  const std::string public_key_bytes(key.begin(), key.end());
+  base::Base64Encode(public_key_bytes, public_key);
+  *crx_id = id_util::GenerateId(public_key_bytes);
+  return VerifierResult::OK_FULL;
+}
+
+}  // namespace
+
+VerifierResult Verify(
+    const base::FilePath& crx_path,
+    const VerifierFormat& format,
+    const std::vector<std::vector<uint8_t>>& required_key_hashes,
+    const std::vector<uint8_t>& required_file_hash,
+    std::string* public_key,
+    std::string* crx_id) {
+  std::string public_key_local;
+  std::string crx_id_local;
+  base::File file(crx_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+  if (!file.IsValid())
+    return VerifierResult::ERROR_FILE_NOT_READABLE;
+
+  std::unique_ptr<crypto::SecureHash> file_hash =
+      crypto::SecureHash::Create(crypto::SecureHash::SHA256);
+
+  // Magic number.
+  bool diff = false;
+  char buffer[kCrxFileHeaderMagicSize] = {};
+  if (file.ReadAtCurrentPos(buffer, kCrxFileHeaderMagicSize) !=
+      kCrxFileHeaderMagicSize)
+    return VerifierResult::ERROR_HEADER_INVALID;
+  if (!strncmp(buffer, kCrxDiffFileHeaderMagic, kCrxFileHeaderMagicSize))
+    diff = true;
+  else if (strncmp(buffer, kCrxFileHeaderMagic, kCrxFileHeaderMagicSize))
+    return VerifierResult::ERROR_HEADER_INVALID;
+  file_hash->Update(buffer, sizeof(buffer));
+
+  // Version number.
+  const uint32_t version =
+      ReadAndHashLittleEndianUInt32(&file, file_hash.get());
+  VerifierResult result;
+  if (version == 2)
+    LOG(WARNING) << "File '" << crx_path
+                 << "' is in CRX2 format, which is deprecated and will not be "
+                    "supported in M78+";
+  if (format == VerifierFormat::CRX2_OR_CRX3 &&
+      (version == 2 || (diff && version == 0))) {
+    result = VerifyCrx2(&file, file_hash.get(), required_key_hashes,
+                        &public_key_local, &crx_id_local);
+  } else if (version == 3) {
+    bool require_publisher_key =
+        format == VerifierFormat::CRX3_WITH_PUBLISHER_PROOF ||
+        format == VerifierFormat::CRX3_WITH_TEST_PUBLISHER_PROOF;
+    result =
+        VerifyCrx3(&file, file_hash.get(), required_key_hashes,
+                   &public_key_local, &crx_id_local, require_publisher_key,
+                   format == VerifierFormat::CRX3_WITH_TEST_PUBLISHER_PROOF);
+  } else {
+    result = VerifierResult::ERROR_HEADER_INVALID;
+  }
+  if (result != VerifierResult::OK_FULL)
+    return result;
+
+  // Finalize file hash.
+  uint8_t final_hash[crypto::kSHA256Length] = {};
+  file_hash->Finish(final_hash, sizeof(final_hash));
+  if (!required_file_hash.empty()) {
+    if (required_file_hash.size() != crypto::kSHA256Length)
+      return VerifierResult::ERROR_EXPECTED_HASH_INVALID;
+    if (!crypto::SecureMemEqual(final_hash, required_file_hash.data(),
+                                crypto::kSHA256Length))
+      return VerifierResult::ERROR_FILE_HASH_FAILED;
+  }
+
+  // All is well. Set the out-params and return.
+  if (public_key)
+    *public_key = public_key_local;
+  if (crx_id)
+    *crx_id = crx_id_local;
+  return diff ? VerifierResult::OK_DELTA : VerifierResult::OK_FULL;
+}
+
+}  // namespace crx_file
diff --git a/src/components/crx_file/crx_verifier.h b/src/components/crx_file/crx_verifier.h
new file mode 100644
index 0000000..b770626
--- /dev/null
+++ b/src/components/crx_file/crx_verifier.h
@@ -0,0 +1,56 @@
+// 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 COMPONENTS_CRX_FILE_CRX_VERIFIER_H_
+#define COMPONENTS_CRX_FILE_CRX_VERIFIER_H_
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace base {
+class FilePath;
+}  // namespace base
+
+namespace crx_file {
+
+enum class VerifierFormat {
+  CRX2_OR_CRX3,                    // Accept Crx2 or Crx3.
+  CRX3,                            // Accept only Crx3.
+  CRX3_WITH_TEST_PUBLISHER_PROOF,  // Accept only Crx3 with a test or production
+                                   // publisher proof.
+  CRX3_WITH_PUBLISHER_PROOF,       // Accept only Crx3 with a production
+                                   // publisher proof.
+};
+
+enum class VerifierResult {
+  OK_FULL,   // The file verifies as a correct full CRX file.
+  OK_DELTA,  // The file verifies as a correct differential CRX file.
+  ERROR_FILE_NOT_READABLE,      // Cannot open the CRX file.
+  ERROR_HEADER_INVALID,         // Failed to parse or understand CRX header.
+  ERROR_EXPECTED_HASH_INVALID,  // Expected hash is not well-formed.
+  ERROR_FILE_HASH_FAILED,       // The file's actual hash != the expected hash.
+  ERROR_SIGNATURE_INITIALIZATION_FAILED,  // A signature or key is malformed.
+  ERROR_SIGNATURE_VERIFICATION_FAILED,    // A signature doesn't match.
+  ERROR_REQUIRED_PROOF_MISSING,           // RequireKeyProof was unsatisfied.
+};
+
+// Verify the file at |crx_path| as a valid Crx of |format|. The Crx must be
+// well-formed, contain no invalid proofs, match the |required_file_hash| (if
+// non-empty), and contain a proof with each of the |required_key_hashes|.
+// If and only if this function returns OK_FULL or OK_DELTA, and only if
+// |public_key| / |crx_id| are non-null, they will be updated to contain the
+// public key (PEM format, without the header/footer) and crx id (encoded in
+// base16 using the characters [a-p]).
+VerifierResult Verify(
+    const base::FilePath& crx_path,
+    const VerifierFormat& format,
+    const std::vector<std::vector<uint8_t>>& required_key_hashes,
+    const std::vector<uint8_t>& required_file_hash,
+    std::string* public_key,
+    std::string* crx_id);
+
+}  // namespace crx_file
+
+#endif  // COMPONENTS_CRX_FILE_CRX_VERIFIER_H_
diff --git a/src/components/crx_file/crx_verifier_unittest.cc b/src/components/crx_file/crx_verifier_unittest.cc
new file mode 100644
index 0000000..8028b7c
--- /dev/null
+++ b/src/components/crx_file/crx_verifier_unittest.cc
@@ -0,0 +1,247 @@
+// 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 "components/crx_file/crx_verifier.h"
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+base::FilePath TestFile(const std::string& file) {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  return path.AppendASCII("components")
+      .AppendASCII("test")
+      .AppendASCII("data")
+      .AppendASCII("crx_file")
+      .AppendASCII(file);
+}
+
+constexpr char kOjjHash[] = "ojjgnpkioondelmggbekfhllhdaimnho";
+constexpr char kOjjKey[] =
+    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA230uN7vYDEhdDlb4/"
+    "+pg2pfL8p0FFzCF/O146NB3D5dPKuLbnNphn0OUzOrDzR/Z1XLVDlDyiA6xnb+qeRp7H8n7Wk/"
+    "/gvVDNArZyForlVqWdaHLhl4dyZoNJwPKsggf30p/"
+    "MxCbNfy2rzFujzn2nguOrJKzWvNt0BFqssrBpzOQl69blBezE2ZYGOnYW8mPgQV29ekIgOfJk2"
+    "GgXoJBQQRRsjoPmUY7GDuEKudEB/"
+    "CmWh3+"
+    "mCsHBHFWbqtGhSN4YCAw3DYQzwdTcIVaIA8f2Uo4AZ4INKkrEPRL8o9mZDYtO2YHIQg8pMSRMa"
+    "6AawBNYi9tZScnmgl5L1qE6z5oIwIDAQAB";
+
+constexpr char kJlnHash[] = "jlnmailbicnpnbggmhfebbomaddckncf";
+constexpr char kJlnKey[] =
+    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtYd4M8wBjlPsc/wxS1/uXKMD6GtI7/"
+    "0GLxRe6UXTYtDk91u/"
+    "FdJRK9jHws+"
+    "FO6Yi3XcJGtMvuojiB1j4bdiYBfvRgfTD4b7krWtWM2udKPBtHI9ikAT5aom5Bda8rCPNyaqXC"
+    "6Ax+KTgQpeeJglYu7TTd/"
+    "AePyvlRHtCKNkcvRQLY0b6hccALqoTzyTueDX12c8Htg76syEPbz7hSIPPfq6KEGvuVSxWAejy"
+    "/y6EhwAdXRLpegul9KmL94OY1G6dpycUKwyKeXOcB6Qj5iKNcOqJAaSLxoOZby4G3cI1BcQpp/"
+    "3vYccJ4qouDMfaanLe8CvFlLp4VOn833aJ8PYpLQIDAQAB";
+
+}  // namespace
+
+namespace crx_file {
+
+using CrxVerifierTest = testing::Test;
+
+TEST_F(CrxVerifierTest, ValidFullCrx2) {
+  const std::vector<std::vector<uint8_t>> keys;
+  const std::vector<uint8_t> hash;
+  std::string public_key;
+  std::string crx_id;
+
+  EXPECT_EQ(VerifierResult::OK_FULL,
+            Verify(TestFile("valid.crx2"), VerifierFormat::CRX2_OR_CRX3, keys,
+                   hash, &public_key, &crx_id));
+  EXPECT_EQ(std::string(kOjjHash), crx_id);
+  EXPECT_EQ(std::string(kOjjKey), public_key);
+}
+
+TEST_F(CrxVerifierTest, ValidFullCrx3) {
+  const std::vector<std::vector<uint8_t>> keys;
+  const std::vector<uint8_t> hash;
+  std::string public_key = "UNSET";
+  std::string crx_id = "UNSET";
+
+  EXPECT_EQ(VerifierResult::OK_FULL, Verify(TestFile("valid_no_publisher.crx3"),
+                                            VerifierFormat::CRX2_OR_CRX3, keys,
+                                            hash, &public_key, &crx_id));
+  EXPECT_EQ(std::string(kOjjHash), crx_id);
+  EXPECT_EQ(std::string(kOjjKey), public_key);
+
+  public_key = "UNSET";
+  crx_id = "UNSET";
+  EXPECT_EQ(VerifierResult::OK_FULL,
+            Verify(TestFile("valid_no_publisher.crx3"), VerifierFormat::CRX3,
+                   keys, hash, &public_key, &crx_id));
+  EXPECT_EQ(std::string(kOjjHash), crx_id);
+  EXPECT_EQ(std::string(kOjjKey), public_key);
+}
+
+TEST_F(CrxVerifierTest, Crx3RejectsCrx2) {
+  const std::vector<std::vector<uint8_t>> keys;
+  const std::vector<uint8_t> hash;
+  std::string public_key = "UNSET";
+  std::string crx_id = "UNSET";
+
+  EXPECT_EQ(VerifierResult::ERROR_HEADER_INVALID,
+            Verify(TestFile("valid.crx2"), VerifierFormat::CRX3, keys, hash,
+                   &public_key, &crx_id));
+  EXPECT_EQ("UNSET", crx_id);
+  EXPECT_EQ("UNSET", public_key);
+}
+
+TEST_F(CrxVerifierTest, VerifiesFileHash) {
+  const std::vector<std::vector<uint8_t>> keys;
+  std::vector<uint8_t> hash;
+  EXPECT_TRUE(base::HexStringToBytes(
+      "d033c510f9e4ee081ccb60ea2bf530dc2e5cb0e71085b55503c8b13b74515fe4",
+      &hash));
+  std::string public_key = "UNSET";
+  std::string crx_id = "UNSET";
+
+  EXPECT_EQ(VerifierResult::OK_FULL, Verify(TestFile("valid_no_publisher.crx3"),
+                                            VerifierFormat::CRX2_OR_CRX3, keys,
+                                            hash, &public_key, &crx_id));
+  EXPECT_EQ(std::string(kOjjHash), crx_id);
+  EXPECT_EQ(std::string(kOjjKey), public_key);
+
+  hash.clear();
+  EXPECT_TRUE(base::HexStringToBytes(std::string(32, '0'), &hash));
+  public_key = "UNSET";
+  crx_id = "UNSET";
+  EXPECT_EQ(VerifierResult::ERROR_EXPECTED_HASH_INVALID,
+            Verify(TestFile("valid_no_publisher.crx3"), VerifierFormat::CRX3,
+                   keys, hash, &public_key, &crx_id));
+  EXPECT_EQ("UNSET", crx_id);
+  EXPECT_EQ("UNSET", public_key);
+
+  hash.clear();
+  EXPECT_TRUE(base::HexStringToBytes(std::string(64, '0'), &hash));
+  public_key = "UNSET";
+  crx_id = "UNSET";
+  EXPECT_EQ(VerifierResult::ERROR_FILE_HASH_FAILED,
+            Verify(TestFile("valid_no_publisher.crx3"), VerifierFormat::CRX3,
+                   keys, hash, &public_key, &crx_id));
+  EXPECT_EQ("UNSET", crx_id);
+  EXPECT_EQ("UNSET", public_key);
+}
+
+TEST_F(CrxVerifierTest, ChecksRequiredKeyHashes) {
+  const std::vector<uint8_t> hash;
+
+  std::vector<uint8_t> good_key;
+  EXPECT_TRUE(base::HexStringToBytes(
+      "e996dfa8eed34bc6614a57bb7308cd7e519bcc690841e1969f7cb173ef16800a",
+      &good_key));
+  const std::vector<std::vector<uint8_t>> good_keys = {good_key};
+  std::string public_key = "UNSET";
+  std::string crx_id = "UNSET";
+  EXPECT_EQ(
+      VerifierResult::OK_FULL,
+      Verify(TestFile("valid_no_publisher.crx3"), VerifierFormat::CRX2_OR_CRX3,
+             good_keys, hash, &public_key, &crx_id));
+  EXPECT_EQ(std::string(kOjjHash), crx_id);
+  EXPECT_EQ(std::string(kOjjKey), public_key);
+
+  std::vector<uint8_t> bad_key;
+  EXPECT_TRUE(base::HexStringToBytes(std::string(64, '0'), &bad_key));
+  const std::vector<std::vector<uint8_t>> bad_keys = {bad_key};
+  public_key = "UNSET";
+  crx_id = "UNSET";
+  EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING,
+            Verify(TestFile("valid_no_publisher.crx3"), VerifierFormat::CRX3,
+                   bad_keys, hash, &public_key, &crx_id));
+  EXPECT_EQ("UNSET", crx_id);
+  EXPECT_EQ("UNSET", public_key);
+}
+
+TEST_F(CrxVerifierTest, ChecksPinnedKey) {
+  const std::vector<uint8_t> hash;
+  const std::vector<std::vector<uint8_t>> keys;
+  std::string public_key = "UNSET";
+  std::string crx_id = "UNSET";
+  EXPECT_EQ(VerifierResult::OK_FULL,
+            Verify(TestFile("valid_publisher.crx3"),
+                   VerifierFormat::CRX3_WITH_PUBLISHER_PROOF, keys, hash,
+                   &public_key, &crx_id));
+  EXPECT_EQ(std::string(kOjjHash), crx_id);
+  EXPECT_EQ(std::string(kOjjKey), public_key);
+
+  public_key = "UNSET";
+  crx_id = "UNSET";
+  EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING,
+            Verify(TestFile("valid_test_publisher.crx3"),
+                   VerifierFormat::CRX3_WITH_PUBLISHER_PROOF, keys, hash,
+                   &public_key, &crx_id));
+  EXPECT_EQ("UNSET", crx_id);
+  EXPECT_EQ("UNSET", public_key);
+
+  public_key = "UNSET";
+  crx_id = "UNSET";
+  EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING,
+            Verify(TestFile("valid_no_publisher.crx3"),
+                   VerifierFormat::CRX3_WITH_PUBLISHER_PROOF, keys, hash,
+                   &public_key, &crx_id));
+  EXPECT_EQ("UNSET", crx_id);
+  EXPECT_EQ("UNSET", public_key);
+}
+
+TEST_F(CrxVerifierTest, ChecksPinnedKeyAcceptsTest) {
+  const std::vector<uint8_t> hash;
+  const std::vector<std::vector<uint8_t>> keys;
+  std::string public_key = "UNSET";
+  std::string crx_id = "UNSET";
+  EXPECT_EQ(VerifierResult::OK_FULL,
+            Verify(TestFile("valid_publisher.crx3"),
+                   VerifierFormat::CRX3_WITH_TEST_PUBLISHER_PROOF, keys, hash,
+                   &public_key, &crx_id));
+  EXPECT_EQ(std::string(kOjjHash), crx_id);
+  EXPECT_EQ(std::string(kOjjKey), public_key);
+
+  public_key = "UNSET";
+  crx_id = "UNSET";
+  EXPECT_EQ(VerifierResult::OK_FULL,
+            Verify(TestFile("valid_test_publisher.crx3"),
+                   VerifierFormat::CRX3_WITH_TEST_PUBLISHER_PROOF, keys, hash,
+                   &public_key, &crx_id));
+  EXPECT_EQ(std::string(kJlnHash), crx_id);
+  EXPECT_EQ(std::string(kJlnKey), public_key);
+
+  public_key = "UNSET";
+  crx_id = "UNSET";
+  EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING,
+            Verify(TestFile("valid_no_publisher.crx3"),
+                   VerifierFormat::CRX3_WITH_TEST_PUBLISHER_PROOF, keys, hash,
+                   &public_key, &crx_id));
+  EXPECT_EQ("UNSET", crx_id);
+  EXPECT_EQ("UNSET", public_key);
+}
+
+TEST_F(CrxVerifierTest, NullptrSafe) {
+  const std::vector<uint8_t> hash;
+  const std::vector<std::vector<uint8_t>> keys;
+  EXPECT_EQ(VerifierResult::OK_FULL,
+            Verify(TestFile("valid_publisher.crx3"),
+                   VerifierFormat::CRX3_WITH_PUBLISHER_PROOF, keys, hash,
+                   nullptr, nullptr));
+}
+
+TEST_F(CrxVerifierTest, RequiresDeveloperKey) {
+  const std::vector<uint8_t> hash;
+  const std::vector<std::vector<uint8_t>> keys;
+  std::string public_key = "UNSET";
+  std::string crx_id = "UNSET";
+  EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING,
+            Verify(TestFile("unsigned.crx3"), VerifierFormat::CRX2_OR_CRX3,
+                   keys, hash, &public_key, &crx_id));
+  EXPECT_EQ("UNSET", crx_id);
+  EXPECT_EQ("UNSET", public_key);
+}
+
+}  // namespace crx_file
diff --git a/src/components/crx_file/id_util.cc b/src/components/crx_file/id_util.cc
new file mode 100644
index 0000000..d896ab4
--- /dev/null
+++ b/src/components/crx_file/id_util.cc
@@ -0,0 +1,105 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/crx_file/id_util.h"
+
+#include <stdint.h>
+
+#include "base/files/file_path.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "crypto/sha2.h"
+
+namespace {
+
+// Converts a normal hexadecimal string into the alphabet used by extensions.
+// We use the characters 'a'-'p' instead of '0'-'f' to avoid ever having a
+// completely numeric host, since some software interprets that as an IP
+// address.
+static void ConvertHexadecimalToIDAlphabet(std::string* id) {
+  for (auto& ch : *id) {
+    int val;
+    if (base::HexStringToInt(base::StringPiece(&ch, 1), &val)) {
+      ch = 'a' + val;
+    } else {
+      ch = 'a';
+    }
+  }
+}
+
+}  // namespace
+
+namespace crx_file {
+namespace id_util {
+
+// First 16 bytes of SHA256 hashed public key.
+const size_t kIdSize = 16;
+
+std::string GenerateId(base::StringPiece input) {
+  uint8_t hash[kIdSize];
+  crypto::SHA256HashString(input, hash, sizeof(hash));
+  return GenerateIdFromHash(hash, sizeof(hash));
+}
+
+std::string GenerateIdFromHash(const uint8_t* hash, size_t hash_size) {
+  CHECK_GE(hash_size, kIdSize);
+  std::string result = base::HexEncode(hash, kIdSize);
+  ConvertHexadecimalToIDAlphabet(&result);
+  return result;
+}
+
+std::string GenerateIdFromHex(const std::string& input) {
+  std::string output = input;
+  ConvertHexadecimalToIDAlphabet(&output);
+  return output;
+}
+
+std::string GenerateIdForPath(const base::FilePath& path) {
+  base::FilePath new_path = MaybeNormalizePath(path);
+  const base::StringPiece path_bytes(
+      reinterpret_cast<const char*>(new_path.value().data()),
+      new_path.value().size() * sizeof(base::FilePath::CharType));
+  return GenerateId(path_bytes);
+}
+
+std::string HashedIdInHex(const std::string& id) {
+  const std::string id_hash = base::SHA1HashString(id);
+  DCHECK_EQ(base::kSHA1Length, id_hash.length());
+  return base::HexEncode(id_hash.c_str(), id_hash.length());
+}
+
+base::FilePath MaybeNormalizePath(const base::FilePath& path) {
+#if defined(OS_WIN)
+  // Normalize any drive letter to upper-case. We do this for consistency with
+  // net_utils::FilePathToFileURL(), which does the same thing, to make string
+  // comparisons simpler.
+  base::FilePath::StringType path_str = path.value();
+  if (path_str.size() >= 2 && path_str[0] >= L'a' && path_str[0] <= L'z' &&
+      path_str[1] == L':')
+    path_str[0] = towupper(path_str[0]);
+
+  return base::FilePath(path_str);
+#else
+  return path;
+#endif
+}
+
+bool IdIsValid(const std::string& id) {
+  // Verify that the id is legal.
+  if (id.size() != (crx_file::id_util::kIdSize * 2))
+    return false;
+
+  for (size_t i = 0; i < id.size(); i++) {
+    const char ch = base::ToLowerASCII(id[i]);
+    if (ch < 'a' || ch > 'p')
+      return false;
+  }
+
+  return true;
+}
+
+}  // namespace id_util
+}  // namespace crx_file
diff --git a/src/components/crx_file/id_util.h b/src/components/crx_file/id_util.h
new file mode 100644
index 0000000..6370560
--- /dev/null
+++ b/src/components/crx_file/id_util.h
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CRX_FILE_ID_UTIL_H_
+#define COMPONENTS_CRX_FILE_ID_UTIL_H_
+
+#include "base/strings/string_piece.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+namespace base {
+class FilePath;
+}
+
+namespace crx_file {
+namespace id_util {
+
+// The number of bytes in a legal id.
+extern const size_t kIdSize;
+
+// Generates an extension ID from arbitrary input. The same input string will
+// always generate the same output ID.
+std::string GenerateId(base::StringPiece input);
+
+// Generates an ID from a HEX string. The same input string will always generate
+// the same output ID.
+std::string GenerateIdFromHex(const std::string& input);
+
+// Generates an ID from the first |kIdSize| bytes of a SHA256 hash.
+// |hash_size| must be at least |kIdSize|.
+std::string GenerateIdFromHash(const uint8_t* hash, size_t hash_size);
+
+// Generates an ID for an extension in the given path.
+// Used while developing extensions, before they have a key.
+std::string GenerateIdForPath(const base::FilePath& path);
+
+// Returns the hash of an extension ID in hex.
+std::string HashedIdInHex(const std::string& id);
+
+// Normalizes the path for use by the extension. On Windows, this will make
+// sure the drive letter is uppercase.
+base::FilePath MaybeNormalizePath(const base::FilePath& path);
+
+// Checks if |id| is a valid extension-id. Extension-ids are used for anything
+// that comes in a CRX file, including apps, extensions, and components.
+bool IdIsValid(const std::string& id);
+
+}  // namespace id_util
+}  // namespace crx_file
+
+#endif  // COMPONENTS_CRX_FILE_ID_UTIL_H_
diff --git a/src/components/crx_file/id_util_unittest.cc b/src/components/crx_file/id_util_unittest.cc
new file mode 100644
index 0000000..ee31110
--- /dev/null
+++ b/src/components/crx_file/id_util_unittest.cc
@@ -0,0 +1,56 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/crx_file/id_util.h"
+
+#include <stdint.h>
+
+#include "base/stl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace crx_file {
+namespace id_util {
+
+TEST(IDUtilTest, GenerateID) {
+  const uint8_t public_key_info[] = {
+      0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+      0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81,
+      0x89, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x7f, 0x2b, 0x20, 0xdc, 0x7c, 0x9b,
+      0x0c, 0xdc, 0x51, 0x61, 0x99, 0x0d, 0x36, 0x0f, 0xd4, 0x66, 0x88, 0x08,
+      0x55, 0x84, 0xd5, 0x3a, 0xbf, 0x2b, 0xa4, 0x64, 0x85, 0x7b, 0x0c, 0x04,
+      0x13, 0x3f, 0x8d, 0xf4, 0xbc, 0x38, 0x0d, 0x49, 0xfe, 0x6b, 0xc4, 0x5a,
+      0xb0, 0x40, 0x53, 0x3a, 0xd7, 0x66, 0x09, 0x0f, 0x9e, 0x36, 0x74, 0x30,
+      0xda, 0x8a, 0x31, 0x4f, 0x1f, 0x14, 0x50, 0xd7, 0xc7, 0x20, 0x94, 0x17,
+      0xde, 0x4e, 0xb9, 0x57, 0x5e, 0x7e, 0x0a, 0xe5, 0xb2, 0x65, 0x7a, 0x89,
+      0x4e, 0xb6, 0x47, 0xff, 0x1c, 0xbd, 0xb7, 0x38, 0x13, 0xaf, 0x47, 0x85,
+      0x84, 0x32, 0x33, 0xf3, 0x17, 0x49, 0xbf, 0xe9, 0x96, 0xd0, 0xd6, 0x14,
+      0x6f, 0x13, 0x8d, 0xc5, 0xfc, 0x2c, 0x72, 0xba, 0xac, 0xea, 0x7e, 0x18,
+      0x53, 0x56, 0xa6, 0x83, 0xa2, 0xce, 0x93, 0x93, 0xe7, 0x1f, 0x0f, 0xe6,
+      0x0f, 0x02, 0x03, 0x01, 0x00, 0x01};
+  std::string extension_id =
+      GenerateId(std::string(reinterpret_cast<const char*>(&public_key_info[0]),
+                             base::size(public_key_info)));
+  EXPECT_EQ("melddjfinppjdikinhbgehiennejpfhp", extension_id);
+
+  EXPECT_EQ("daibjpdaanagajckigeiigphanababab",
+            GenerateIdFromHash(public_key_info, sizeof(public_key_info)));
+
+  EXPECT_EQ("jpignaibiiemhngfjkcpokkamffknabf", GenerateId("test"));
+
+  EXPECT_EQ("ncocknphbhhlhkikpnnlmbcnbgdempcd", GenerateId("_"));
+
+  EXPECT_EQ("a", GenerateIdFromHex("_"));
+
+  EXPECT_EQ(
+      "bjbdkfoakgmkndalgpadobhgbhhoanhongcmfnghaakjmggnkffgnhmdpfngkeho",
+      GenerateIdFromHex(
+          "1913a5e0a6cad30b6f03e176177e0d7ed62c5d6700a9c66da556d7c3f5d6a47e"));
+
+  EXPECT_EQ(
+      "jimneklojkjdibfkgiiophfhjhbdgcfi",
+      GenerateId("this_string_is_longer_than_a_single_sha256_hash_digest"));
+}
+
+}  // namespace id_util
+}  // namespace crx_file
diff --git a/src/components/prefs/BUILD.gn b/src/components/prefs/BUILD.gn
new file mode 100644
index 0000000..1954a7f
--- /dev/null
+++ b/src/components/prefs/BUILD.gn
@@ -0,0 +1,107 @@
+# Copyright 2015 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.
+
+component("prefs") {
+  sources = [
+    "command_line_pref_store.cc",
+    "command_line_pref_store.h",
+    "default_pref_store.cc",
+    "default_pref_store.h",
+    "in_memory_pref_store.cc",
+    "in_memory_pref_store.h",
+    "json_pref_store.cc",
+    "json_pref_store.h",
+    "overlay_user_pref_store.cc",
+    "overlay_user_pref_store.h",
+    "persistent_pref_store.cc",
+    "persistent_pref_store.h",
+    "pref_change_registrar.cc",
+    "pref_change_registrar.h",
+    "pref_filter.h",
+    "pref_member.cc",
+    "pref_member.h",
+    "pref_notifier.h",
+    "pref_notifier_impl.cc",
+    "pref_notifier_impl.h",
+    "pref_observer.h",
+    "pref_registry.cc",
+    "pref_registry.h",
+    "pref_registry_simple.cc",
+    "pref_registry_simple.h",
+    "pref_service.cc",
+    "pref_service.h",
+    "pref_service_factory.cc",
+    "pref_service_factory.h",
+    "pref_store.cc",
+    "pref_store.h",
+    "pref_value_map.cc",
+    "pref_value_map.h",
+    "pref_value_store.cc",
+    "pref_value_store.h",
+    "prefs_export.h",
+    "scoped_user_pref_update.cc",
+    "scoped_user_pref_update.h",
+    "value_map_pref_store.cc",
+    "value_map_pref_store.h",
+    "writeable_pref_store.cc",
+    "writeable_pref_store.h",
+  ]
+
+  defines = [ "COMPONENTS_PREFS_IMPLEMENTATION" ]
+
+  deps = [
+    "//base",
+    "//base/util/values:values_util",
+  ]
+}
+
+static_library("test_support") {
+  testonly = true
+  sources = [
+    "mock_pref_change_callback.cc",
+    "mock_pref_change_callback.h",
+    "pref_store_observer_mock.cc",
+    "pref_store_observer_mock.h",
+    "testing_pref_service.cc",
+    "testing_pref_service.h",
+    "testing_pref_store.cc",
+    "testing_pref_store.h",
+  ]
+
+  public_deps = [
+    ":prefs",
+  ]
+  deps = [
+    "//base",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "default_pref_store_unittest.cc",
+    "in_memory_pref_store_unittest.cc",
+    "json_pref_store_unittest.cc",
+    "overlay_user_pref_store_unittest.cc",
+    "persistent_pref_store_unittest.cc",
+    "persistent_pref_store_unittest.h",
+    "pref_change_registrar_unittest.cc",
+    "pref_member_unittest.cc",
+    "pref_notifier_impl_unittest.cc",
+    "pref_service_unittest.cc",
+    "pref_value_map_unittest.cc",
+    "pref_value_store_unittest.cc",
+    "scoped_user_pref_update_unittest.cc",
+  ]
+
+  deps = [
+    ":test_support",
+    "//base",
+    "//base/test:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/src/components/prefs/command_line_pref_store.cc b/src/components/prefs/command_line_pref_store.cc
new file mode 100644
index 0000000..a3befc5
--- /dev/null
+++ b/src/components/prefs/command_line_pref_store.cc
@@ -0,0 +1,78 @@
+// Copyright 2016 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 "components/prefs/command_line_pref_store.h"
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/strings/string_number_conversions.h"
+
+CommandLinePrefStore::CommandLinePrefStore(
+    const base::CommandLine* command_line)
+    : command_line_(command_line) {}
+
+CommandLinePrefStore::~CommandLinePrefStore() {}
+
+void CommandLinePrefStore::ApplyStringSwitches(
+    const CommandLinePrefStore::SwitchToPreferenceMapEntry string_switch[],
+    size_t size) {
+  for (size_t i = 0; i < size; ++i) {
+    if (command_line_->HasSwitch(string_switch[i].switch_name)) {
+      SetValue(string_switch[i].preference_path,
+               std::make_unique<base::Value>(command_line_->GetSwitchValueASCII(
+                   string_switch[i].switch_name)),
+               WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+    }
+  }
+}
+
+void CommandLinePrefStore::ApplyPathSwitches(
+    const CommandLinePrefStore::SwitchToPreferenceMapEntry path_switch[],
+    size_t size) {
+  for (size_t i = 0; i < size; ++i) {
+    if (command_line_->HasSwitch(path_switch[i].switch_name)) {
+      SetValue(path_switch[i].preference_path,
+               std::make_unique<base::Value>(
+                   command_line_->GetSwitchValuePath(path_switch[i].switch_name)
+                       .value()),
+               WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+    }
+  }
+}
+
+void CommandLinePrefStore::ApplyIntegerSwitches(
+    const CommandLinePrefStore::SwitchToPreferenceMapEntry integer_switch[],
+    size_t size) {
+  for (size_t i = 0; i < size; ++i) {
+    if (command_line_->HasSwitch(integer_switch[i].switch_name)) {
+      std::string str_value =
+          command_line_->GetSwitchValueASCII(integer_switch[i].switch_name);
+      int int_value = 0;
+      if (!base::StringToInt(str_value, &int_value)) {
+        LOG(ERROR) << "The value " << str_value << " of "
+                   << integer_switch[i].switch_name
+                   << " can not be converted to integer, ignoring!";
+        continue;
+      }
+      SetValue(integer_switch[i].preference_path,
+               std::make_unique<base::Value>(int_value),
+               WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+    }
+  }
+}
+
+void CommandLinePrefStore::ApplyBooleanSwitches(
+    const CommandLinePrefStore::BooleanSwitchToPreferenceMapEntry
+        boolean_switch_map[],
+    size_t size) {
+  for (size_t i = 0; i < size; ++i) {
+    if (command_line_->HasSwitch(boolean_switch_map[i].switch_name)) {
+      SetValue(boolean_switch_map[i].preference_path,
+               std::make_unique<base::Value>(boolean_switch_map[i].set_value),
+               WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+    }
+  }
+}
diff --git a/src/components/prefs/command_line_pref_store.h b/src/components/prefs/command_line_pref_store.h
new file mode 100644
index 0000000..8f47feb
--- /dev/null
+++ b/src/components/prefs/command_line_pref_store.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2016 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 COMPONENTS_PREFS_COMMAND_LINE_PREF_STORE_H_
+#define COMPONENTS_PREFS_COMMAND_LINE_PREF_STORE_H_
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/values.h"
+#include "components/prefs/value_map_pref_store.h"
+
+// Base class for a PrefStore that maps command line switches to preferences.
+// The Apply...Switches() methods can be called by subclasses with their own
+// maps, or delegated to other code.
+class COMPONENTS_PREFS_EXPORT CommandLinePrefStore : public ValueMapPrefStore {
+ public:
+  struct SwitchToPreferenceMapEntry {
+    const char* switch_name;
+    const char* preference_path;
+  };
+
+  // |set_value| indicates what the preference should be set to if the switch
+  // is present.
+  struct BooleanSwitchToPreferenceMapEntry {
+    const char* switch_name;
+    const char* preference_path;
+    bool set_value;
+  };
+
+  // Apply command-line switches to the corresponding preferences of the switch
+  // map, where the value associated with the switch is a string.
+  void ApplyStringSwitches(const SwitchToPreferenceMapEntry string_switch_map[],
+                           size_t size);
+
+  // Apply command-line switches to the corresponding preferences of the switch
+  // map, where the value associated with the switch is a path.
+  void ApplyPathSwitches(const SwitchToPreferenceMapEntry path_switch_map[],
+                         size_t size);
+
+  // Apply command-line switches to the corresponding preferences of the switch
+  // map, where the value associated with the switch is an integer.
+  void ApplyIntegerSwitches(
+      const SwitchToPreferenceMapEntry integer_switch_map[],
+      size_t size);
+
+  // Apply command-line switches to the corresponding preferences of the
+  // boolean switch map.
+  void ApplyBooleanSwitches(
+      const BooleanSwitchToPreferenceMapEntry boolean_switch_map[],
+      size_t size);
+
+ protected:
+  explicit CommandLinePrefStore(const base::CommandLine* command_line);
+  ~CommandLinePrefStore() override;
+
+  const base::CommandLine* command_line() { return command_line_; }
+
+ private:
+  // Weak reference.
+  const base::CommandLine* command_line_;
+
+  DISALLOW_COPY_AND_ASSIGN(CommandLinePrefStore);
+};
+
+#endif  // COMPONENTS_PREFS_COMMAND_LINE_PREF_STORE_H_
diff --git a/src/components/prefs/default_pref_store.cc b/src/components/prefs/default_pref_store.cc
new file mode 100644
index 0000000..d8d13ec
--- /dev/null
+++ b/src/components/prefs/default_pref_store.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 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 "components/prefs/default_pref_store.h"
+
+#include <utility>
+
+#include "base/logging.h"
+
+using base::Value;
+
+DefaultPrefStore::DefaultPrefStore() {}
+
+bool DefaultPrefStore::GetValue(const std::string& key,
+                                const Value** result) const {
+  return prefs_.GetValue(key, result);
+}
+
+std::unique_ptr<base::DictionaryValue> DefaultPrefStore::GetValues() const {
+  return prefs_.AsDictionaryValue();
+}
+
+void DefaultPrefStore::AddObserver(PrefStore::Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void DefaultPrefStore::RemoveObserver(PrefStore::Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+bool DefaultPrefStore::HasObservers() const {
+  return observers_.might_have_observers();
+}
+
+void DefaultPrefStore::SetDefaultValue(const std::string& key, Value value) {
+  DCHECK(!GetValue(key, nullptr));
+  prefs_.SetValue(key, std::move(value));
+}
+
+void DefaultPrefStore::ReplaceDefaultValue(const std::string& key,
+                                           Value value) {
+  DCHECK(GetValue(key, nullptr));
+  bool notify = prefs_.SetValue(key, std::move(value));
+  if (notify) {
+    for (Observer& observer : observers_)
+      observer.OnPrefValueChanged(key);
+  }
+}
+
+DefaultPrefStore::const_iterator DefaultPrefStore::begin() const {
+  return prefs_.begin();
+}
+
+DefaultPrefStore::const_iterator DefaultPrefStore::end() const {
+  return prefs_.end();
+}
+
+DefaultPrefStore::~DefaultPrefStore() {}
diff --git a/src/components/prefs/default_pref_store.h b/src/components/prefs/default_pref_store.h
new file mode 100644
index 0000000..17ba3ec
--- /dev/null
+++ b/src/components/prefs/default_pref_store.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_DEFAULT_PREF_STORE_H_
+#define COMPONENTS_PREFS_DEFAULT_PREF_STORE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/values.h"
+#include "components/prefs/pref_store.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/prefs/prefs_export.h"
+
+// Used within a PrefRegistry to keep track of default preference values.
+class COMPONENTS_PREFS_EXPORT DefaultPrefStore : public PrefStore {
+ public:
+  typedef PrefValueMap::const_iterator const_iterator;
+
+  DefaultPrefStore();
+
+  // PrefStore implementation:
+  bool GetValue(const std::string& key,
+                const base::Value** result) const override;
+  std::unique_ptr<base::DictionaryValue> GetValues() const override;
+  void AddObserver(PrefStore::Observer* observer) override;
+  void RemoveObserver(PrefStore::Observer* observer) override;
+  bool HasObservers() const override;
+
+  // Sets a |value| for |key|. Should only be called if a value has not been
+  // set yet; otherwise call ReplaceDefaultValue().
+  void SetDefaultValue(const std::string& key, base::Value value);
+
+  // Replaces the the value for |key| with a new value. Should only be called
+  // if a value has alreday been set; otherwise call SetDefaultValue().
+  void ReplaceDefaultValue(const std::string& key, base::Value value);
+
+  const_iterator begin() const;
+  const_iterator end() const;
+
+ private:
+  ~DefaultPrefStore() override;
+
+  PrefValueMap prefs_;
+
+  base::ObserverList<PrefStore::Observer, true>::Unchecked observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(DefaultPrefStore);
+};
+
+#endif  // COMPONENTS_PREFS_DEFAULT_PREF_STORE_H_
diff --git a/src/components/prefs/default_pref_store_unittest.cc b/src/components/prefs/default_pref_store_unittest.cc
new file mode 100644
index 0000000..78f945c
--- /dev/null
+++ b/src/components/prefs/default_pref_store_unittest.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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 "components/prefs/default_pref_store.h"
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Value;
+
+namespace {
+
+class MockPrefStoreObserver : public PrefStore::Observer {
+ public:
+  explicit MockPrefStoreObserver(DefaultPrefStore* pref_store);
+  ~MockPrefStoreObserver() override;
+
+  int change_count() { return change_count_; }
+
+  // PrefStore::Observer implementation:
+  void OnPrefValueChanged(const std::string& key) override;
+  void OnInitializationCompleted(bool succeeded) override {}
+
+ private:
+  DefaultPrefStore* pref_store_;
+
+  int change_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockPrefStoreObserver);
+};
+
+MockPrefStoreObserver::MockPrefStoreObserver(DefaultPrefStore* pref_store)
+    : pref_store_(pref_store), change_count_(0) {
+  pref_store_->AddObserver(this);
+}
+
+MockPrefStoreObserver::~MockPrefStoreObserver() {
+  pref_store_->RemoveObserver(this);
+}
+
+void MockPrefStoreObserver::OnPrefValueChanged(const std::string& key) {
+  change_count_++;
+}
+
+}  // namespace
+
+TEST(DefaultPrefStoreTest, NotifyPrefValueChanged) {
+  scoped_refptr<DefaultPrefStore> pref_store(new DefaultPrefStore);
+  MockPrefStoreObserver observer(pref_store.get());
+  std::string kPrefKey("pref_key");
+
+  // Setting a default value shouldn't send a change notification.
+  pref_store->SetDefaultValue(kPrefKey, Value("foo"));
+  EXPECT_EQ(0, observer.change_count());
+
+  // Replacing the default value should send a change notification...
+  pref_store->ReplaceDefaultValue(kPrefKey, Value("bar"));
+  EXPECT_EQ(1, observer.change_count());
+
+  // But only if the value actually changed.
+  pref_store->ReplaceDefaultValue(kPrefKey, Value("bar"));
+  EXPECT_EQ(1, observer.change_count());
+}
diff --git a/src/components/prefs/in_memory_pref_store.cc b/src/components/prefs/in_memory_pref_store.cc
new file mode 100644
index 0000000..fac639f
--- /dev/null
+++ b/src/components/prefs/in_memory_pref_store.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2016 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 "components/prefs/in_memory_pref_store.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/values.h"
+
+InMemoryPrefStore::InMemoryPrefStore() {}
+
+InMemoryPrefStore::~InMemoryPrefStore() {}
+
+bool InMemoryPrefStore::GetValue(const std::string& key,
+                                 const base::Value** value) const {
+  return prefs_.GetValue(key, value);
+}
+
+std::unique_ptr<base::DictionaryValue> InMemoryPrefStore::GetValues() const {
+  return prefs_.AsDictionaryValue();
+}
+
+bool InMemoryPrefStore::GetMutableValue(const std::string& key,
+                                        base::Value** value) {
+  return prefs_.GetValue(key, value);
+}
+
+void InMemoryPrefStore::AddObserver(PrefStore::Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void InMemoryPrefStore::RemoveObserver(PrefStore::Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+bool InMemoryPrefStore::HasObservers() const {
+  return observers_.might_have_observers();
+}
+
+bool InMemoryPrefStore::IsInitializationComplete() const {
+  return true;
+}
+
+void InMemoryPrefStore::SetValue(const std::string& key,
+                                 std::unique_ptr<base::Value> value,
+                                 uint32_t flags) {
+  DCHECK(value);
+  if (prefs_.SetValue(key, base::Value::FromUniquePtrValue(std::move(value))))
+    ReportValueChanged(key, flags);
+}
+
+void InMemoryPrefStore::SetValueSilently(const std::string& key,
+                                         std::unique_ptr<base::Value> value,
+                                         uint32_t flags) {
+  DCHECK(value);
+  prefs_.SetValue(key, base::Value::FromUniquePtrValue(std::move(value)));
+}
+
+void InMemoryPrefStore::RemoveValue(const std::string& key, uint32_t flags) {
+  if (prefs_.RemoveValue(key))
+    ReportValueChanged(key, flags);
+}
+
+bool InMemoryPrefStore::ReadOnly() const {
+  return false;
+}
+
+PersistentPrefStore::PrefReadError InMemoryPrefStore::GetReadError() const {
+  return PersistentPrefStore::PREF_READ_ERROR_NONE;
+}
+
+PersistentPrefStore::PrefReadError InMemoryPrefStore::ReadPrefs() {
+  return PersistentPrefStore::PREF_READ_ERROR_NONE;
+}
+
+void InMemoryPrefStore::ReportValueChanged(const std::string& key,
+                                           uint32_t flags) {
+  for (Observer& observer : observers_)
+    observer.OnPrefValueChanged(key);
+}
+
+bool InMemoryPrefStore::IsInMemoryPrefStore() const {
+  return true;
+}
diff --git a/src/components/prefs/in_memory_pref_store.h b/src/components/prefs/in_memory_pref_store.h
new file mode 100644
index 0000000..c623adb
--- /dev/null
+++ b/src/components/prefs/in_memory_pref_store.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2016 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 COMPONENTS_PREFS_IN_MEMORY_PREF_STORE_H_
+#define COMPONENTS_PREFS_IN_MEMORY_PREF_STORE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_value_map.h"
+
+// A light-weight prefstore implementation that keeps preferences
+// in a memory backed store. This is not a persistent prefstore -- we
+// subclass the PersistentPrefStore here since it is needed by the
+// PrefService, which in turn is needed by various components.
+class COMPONENTS_PREFS_EXPORT InMemoryPrefStore : public PersistentPrefStore {
+ public:
+  InMemoryPrefStore();
+
+  // PrefStore implementation.
+  bool GetValue(const std::string& key,
+                const base::Value** result) const override;
+  std::unique_ptr<base::DictionaryValue> GetValues() const override;
+  void AddObserver(PrefStore::Observer* observer) override;
+  void RemoveObserver(PrefStore::Observer* observer) override;
+  bool HasObservers() const override;
+  bool IsInitializationComplete() const override;
+
+  // PersistentPrefStore implementation.
+  bool GetMutableValue(const std::string& key, base::Value** result) override;
+  void ReportValueChanged(const std::string& key, uint32_t flags) override;
+  void SetValue(const std::string& key,
+                std::unique_ptr<base::Value> value,
+                uint32_t flags) override;
+  void SetValueSilently(const std::string& key,
+                        std::unique_ptr<base::Value> value,
+                        uint32_t flags) override;
+  void RemoveValue(const std::string& key, uint32_t flags) override;
+  bool ReadOnly() const override;
+  PrefReadError GetReadError() const override;
+  PersistentPrefStore::PrefReadError ReadPrefs() override;
+  void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override {}
+  void SchedulePendingLossyWrites() override {}
+  void ClearMutableValues() override {}
+  void OnStoreDeletionFromDisk() override {}
+  bool IsInMemoryPrefStore() const override;
+
+ protected:
+  ~InMemoryPrefStore() override;
+
+ private:
+  // Stores the preference values.
+  PrefValueMap prefs_;
+
+  base::ObserverList<PrefStore::Observer, true>::Unchecked observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(InMemoryPrefStore);
+};
+
+#endif  // COMPONENTS_PREFS_IN_MEMORY_PREF_STORE_H_
diff --git a/src/components/prefs/in_memory_pref_store_unittest.cc b/src/components/prefs/in_memory_pref_store_unittest.cc
new file mode 100644
index 0000000..748a68b
--- /dev/null
+++ b/src/components/prefs/in_memory_pref_store_unittest.cc
@@ -0,0 +1,114 @@
+// Copyright 2016 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 "components/prefs/in_memory_pref_store.h"
+
+#include <memory>
+
+#include "base/test/scoped_task_environment.h"
+#include "base/values.h"
+#include "components/prefs/persistent_pref_store_unittest.h"
+#include "components/prefs/pref_store_observer_mock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+const char kTestPref[] = "test.pref";
+
+class InMemoryPrefStoreTest : public testing::Test {
+ public:
+  InMemoryPrefStoreTest() {}
+
+  void SetUp() override { store_ = new InMemoryPrefStore(); }
+
+ protected:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  scoped_refptr<InMemoryPrefStore> store_;
+  PrefStoreObserverMock observer_;
+};
+
+TEST_F(InMemoryPrefStoreTest, SetGetValue) {
+  const base::Value* value = nullptr;
+  base::Value* mutable_value = nullptr;
+  EXPECT_FALSE(store_->GetValue(kTestPref, &value));
+  EXPECT_FALSE(store_->GetMutableValue(kTestPref, &mutable_value));
+
+  store_->SetValue(kTestPref, std::make_unique<base::Value>(42),
+                   WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_TRUE(store_->GetValue(kTestPref, &value));
+  EXPECT_TRUE(base::Value(42).Equals(value));
+  EXPECT_TRUE(store_->GetMutableValue(kTestPref, &mutable_value));
+  EXPECT_TRUE(base::Value(42).Equals(mutable_value));
+
+  store_->RemoveValue(kTestPref, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_FALSE(store_->GetValue(kTestPref, &value));
+  EXPECT_FALSE(store_->GetMutableValue(kTestPref, &mutable_value));
+}
+
+TEST_F(InMemoryPrefStoreTest, GetSetObserver) {
+  // Starts with no observers.
+  EXPECT_FALSE(store_->HasObservers());
+
+  // Add one.
+  store_->AddObserver(&observer_);
+  EXPECT_TRUE(store_->HasObservers());
+
+  // Remove only observer.
+  store_->RemoveObserver(&observer_);
+  EXPECT_FALSE(store_->HasObservers());
+}
+
+TEST_F(InMemoryPrefStoreTest, CallObserver) {
+  // With observer included.
+  store_->AddObserver(&observer_);
+
+  // Triggers on SetValue.
+  store_->SetValue(kTestPref, std::make_unique<base::Value>(42),
+                   WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  observer_.VerifyAndResetChangedKey(kTestPref);
+
+  // And RemoveValue.
+  store_->RemoveValue(kTestPref, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  observer_.VerifyAndResetChangedKey(kTestPref);
+
+  // But not SetValueSilently.
+  store_->SetValueSilently(kTestPref, std::make_unique<base::Value>(42),
+                           WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_EQ(0u, observer_.changed_keys.size());
+
+  // On multiple RemoveValues only the first one triggers observer.
+  store_->RemoveValue(kTestPref, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  observer_.VerifyAndResetChangedKey(kTestPref);
+  store_->RemoveValue(kTestPref, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_EQ(0u, observer_.changed_keys.size());
+
+  // Doesn't make call on removed observers.
+  store_->RemoveObserver(&observer_);
+  store_->SetValue(kTestPref, std::make_unique<base::Value>(42),
+                   WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  store_->RemoveValue(kTestPref, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_EQ(0u, observer_.changed_keys.size());
+}
+
+TEST_F(InMemoryPrefStoreTest, Initialization) {
+  EXPECT_TRUE(store_->IsInitializationComplete());
+}
+
+TEST_F(InMemoryPrefStoreTest, ReadOnly) {
+  EXPECT_FALSE(store_->ReadOnly());
+}
+
+TEST_F(InMemoryPrefStoreTest, GetReadError) {
+  EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, store_->GetReadError());
+}
+
+TEST_F(InMemoryPrefStoreTest, ReadPrefs) {
+  EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, store_->ReadPrefs());
+}
+
+TEST_F(InMemoryPrefStoreTest, CommitPendingWriteWithCallback) {
+  TestCommitPendingWriteWithCallback(store_.get(), &scoped_task_environment_);
+}
+
+}  // namespace
diff --git a/src/components/prefs/ios/BUILD.gn b/src/components/prefs/ios/BUILD.gn
new file mode 100644
index 0000000..82aa654
--- /dev/null
+++ b/src/components/prefs/ios/BUILD.gn
@@ -0,0 +1,15 @@
+# Copyright 2018 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.
+
+source_set("ios") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "pref_observer_bridge.h",
+    "pref_observer_bridge.mm",
+  ]
+  deps = [
+    "//base",
+    "//components/prefs",
+  ]
+}
diff --git a/src/components/prefs/ios/pref_observer_bridge.h b/src/components/prefs/ios/pref_observer_bridge.h
new file mode 100644
index 0000000..425ef87
--- /dev/null
+++ b/src/components/prefs/ios/pref_observer_bridge.h
@@ -0,0 +1,32 @@
+// Copyright 2012 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 COMPONENTS_PREFS_IOS_PREF_OBSERVER_BRIDGE_H_
+#define COMPONENTS_PREFS_IOS_PREF_OBSERVER_BRIDGE_H_
+
+#import <Foundation/Foundation.h>
+
+#include <string>
+
+class PrefChangeRegistrar;
+
+@protocol PrefObserverDelegate
+- (void)onPreferenceChanged:(const std::string&)preferenceName;
+@end
+
+class PrefObserverBridge {
+ public:
+  explicit PrefObserverBridge(id<PrefObserverDelegate> delegate);
+  virtual ~PrefObserverBridge();
+
+  virtual void ObserveChangesForPreference(const std::string& pref_name,
+                                           PrefChangeRegistrar* registrar);
+
+ private:
+  virtual void OnPreferenceChanged(const std::string& pref_name);
+
+  __weak id<PrefObserverDelegate> delegate_ = nil;
+};
+
+#endif  // COMPONENTS_PREFS_IOS_PREF_OBSERVER_BRIDGE_H_
diff --git a/src/components/prefs/ios/pref_observer_bridge.mm b/src/components/prefs/ios/pref_observer_bridge.mm
new file mode 100644
index 0000000..852309f
--- /dev/null
+++ b/src/components/prefs/ios/pref_observer_bridge.mm
@@ -0,0 +1,29 @@
+// Copyright 2012 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 "components/prefs/ios/pref_observer_bridge.h"
+
+#include "base/bind.h"
+#include "components/prefs/pref_change_registrar.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+PrefObserverBridge::PrefObserverBridge(id<PrefObserverDelegate> delegate)
+    : delegate_(delegate) {}
+
+PrefObserverBridge::~PrefObserverBridge() {}
+
+void PrefObserverBridge::ObserveChangesForPreference(
+    const std::string& pref_name,
+    PrefChangeRegistrar* registrar) {
+  PrefChangeRegistrar::NamedChangeCallback callback = base::BindRepeating(
+      &PrefObserverBridge::OnPreferenceChanged, base::Unretained(this));
+  registrar->Add(pref_name.c_str(), callback);
+}
+
+void PrefObserverBridge::OnPreferenceChanged(const std::string& pref_name) {
+  [delegate_ onPreferenceChanged:pref_name];
+}
diff --git a/src/components/prefs/json_pref_store.cc b/src/components/prefs/json_pref_store.cc
new file mode 100644
index 0000000..ef86d93
--- /dev/null
+++ b/src/components/prefs/json_pref_store.cc
@@ -0,0 +1,518 @@
+// Copyright (c) 2012 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 "components/prefs/json_pref_store.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/histogram.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/task_runner_util.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/default_clock.h"
+#include "base/values.h"
+#include "components/prefs/pref_filter.h"
+
+// Result returned from internal read tasks.
+struct JsonPrefStore::ReadResult {
+ public:
+  ReadResult();
+  ~ReadResult();
+
+  std::unique_ptr<base::Value> value;
+  PrefReadError error;
+  bool no_dir;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ReadResult);
+};
+
+JsonPrefStore::ReadResult::ReadResult()
+    : error(PersistentPrefStore::PREF_READ_ERROR_NONE), no_dir(false) {}
+
+JsonPrefStore::ReadResult::~ReadResult() {}
+
+namespace {
+
+// Some extensions we'll tack on to copies of the Preferences files.
+const base::FilePath::CharType kBadExtension[] = FILE_PATH_LITERAL("bad");
+
+PersistentPrefStore::PrefReadError HandleReadErrors(
+    const base::Value* value,
+    const base::FilePath& path,
+    int error_code,
+    const std::string& error_msg) {
+  if (!value) {
+    DVLOG(1) << "Error while loading JSON file: " << error_msg
+             << ", file: " << path.value();
+    switch (error_code) {
+      case JSONFileValueDeserializer::JSON_ACCESS_DENIED:
+        return PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED;
+      case JSONFileValueDeserializer::JSON_CANNOT_READ_FILE:
+        return PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER;
+      case JSONFileValueDeserializer::JSON_FILE_LOCKED:
+        return PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED;
+      case JSONFileValueDeserializer::JSON_NO_SUCH_FILE:
+        return PersistentPrefStore::PREF_READ_ERROR_NO_FILE;
+      default:
+        // JSON errors indicate file corruption of some sort.
+        // Since the file is corrupt, move it to the side and continue with
+        // empty preferences.  This will result in them losing their settings.
+        // We keep the old file for possible support and debugging assistance
+        // as well as to detect if they're seeing these errors repeatedly.
+        // TODO(erikkay) Instead, use the last known good file.
+        base::FilePath bad = path.ReplaceExtension(kBadExtension);
+
+        // If they've ever had a parse error before, put them in another bucket.
+        // TODO(erikkay) if we keep this error checking for very long, we may
+        // want to differentiate between recent and long ago errors.
+        bool bad_existed = base::PathExists(bad);
+        base::CopyFile(path, bad);
+        base::DeleteFile(path, false);
+        return bad_existed ? PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT
+                           : PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE;
+    }
+  }
+  if (!value->is_dict())
+    return PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE;
+  return PersistentPrefStore::PREF_READ_ERROR_NONE;
+}
+
+// Records a sample for |size| in the Settings.JsonDataReadSizeKilobytes
+// histogram suffixed with the base name of the JSON file under |path|.
+void RecordJsonDataSizeHistogram(const base::FilePath& path, size_t size) {
+  std::string spaceless_basename;
+  base::ReplaceChars(path.BaseName().MaybeAsASCII(), " ", "_",
+                     &spaceless_basename);
+
+  // The histogram below is an expansion of the UMA_HISTOGRAM_CUSTOM_COUNTS
+  // macro adapted to allow for a dynamically suffixed histogram name.
+  // Note: The factory creates and owns the histogram.
+  // This histogram is expired but the code was intentionally left behind so
+  // it can be re-enabled on Stable in a single config tweak if needed.
+  base::HistogramBase* histogram = base::Histogram::FactoryGet(
+      "Settings.JsonDataReadSizeKilobytes." + spaceless_basename, 1, 10000, 50,
+      base::HistogramBase::kUmaTargetedHistogramFlag);
+  histogram->Add(static_cast<int>(size) / 1024);
+}
+
+std::unique_ptr<JsonPrefStore::ReadResult> ReadPrefsFromDisk(
+    const base::FilePath& path) {
+  int error_code;
+  std::string error_msg;
+  std::unique_ptr<JsonPrefStore::ReadResult> read_result(
+      new JsonPrefStore::ReadResult);
+  JSONFileValueDeserializer deserializer(path);
+  read_result->value = deserializer.Deserialize(&error_code, &error_msg);
+  read_result->error =
+      HandleReadErrors(read_result->value.get(), path, error_code, error_msg);
+  read_result->no_dir = !base::PathExists(path.DirName());
+
+  if (read_result->error == PersistentPrefStore::PREF_READ_ERROR_NONE)
+    RecordJsonDataSizeHistogram(path, deserializer.get_last_read_size());
+
+  return read_result;
+}
+
+}  // namespace
+
+JsonPrefStore::JsonPrefStore(
+    const base::FilePath& pref_filename,
+    std::unique_ptr<PrefFilter> pref_filter,
+    scoped_refptr<base::SequencedTaskRunner> file_task_runner)
+    : path_(pref_filename),
+      file_task_runner_(std::move(file_task_runner)),
+      prefs_(new base::DictionaryValue()),
+      read_only_(false),
+      writer_(pref_filename, file_task_runner_),
+      pref_filter_(std::move(pref_filter)),
+      initialized_(false),
+      filtering_in_progress_(false),
+      pending_lossy_write_(false),
+      read_error_(PREF_READ_ERROR_NONE),
+      has_pending_write_reply_(false) {
+  DCHECK(!path_.empty());
+}
+
+bool JsonPrefStore::GetValue(const std::string& key,
+                             const base::Value** result) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  base::Value* tmp = nullptr;
+  if (!prefs_->Get(key, &tmp))
+    return false;
+
+  if (result)
+    *result = tmp;
+  return true;
+}
+
+std::unique_ptr<base::DictionaryValue> JsonPrefStore::GetValues() const {
+  return prefs_->CreateDeepCopy();
+}
+
+void JsonPrefStore::AddObserver(PrefStore::Observer* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  observers_.AddObserver(observer);
+}
+
+void JsonPrefStore::RemoveObserver(PrefStore::Observer* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  observers_.RemoveObserver(observer);
+}
+
+bool JsonPrefStore::HasObservers() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  return observers_.might_have_observers();
+}
+
+bool JsonPrefStore::IsInitializationComplete() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  return initialized_;
+}
+
+bool JsonPrefStore::GetMutableValue(const std::string& key,
+                                    base::Value** result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  return prefs_->Get(key, result);
+}
+
+void JsonPrefStore::SetValue(const std::string& key,
+                             std::unique_ptr<base::Value> value,
+                             uint32_t flags) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DCHECK(value);
+  base::Value* old_value = nullptr;
+  prefs_->Get(key, &old_value);
+  if (!old_value || !value->Equals(old_value)) {
+    prefs_->Set(key, std::move(value));
+    ReportValueChanged(key, flags);
+  }
+}
+
+void JsonPrefStore::SetValueSilently(const std::string& key,
+                                     std::unique_ptr<base::Value> value,
+                                     uint32_t flags) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DCHECK(value);
+  base::Value* old_value = nullptr;
+  prefs_->Get(key, &old_value);
+  if (!old_value || !value->Equals(old_value)) {
+    prefs_->Set(key, std::move(value));
+    ScheduleWrite(flags);
+  }
+}
+
+void JsonPrefStore::RemoveValue(const std::string& key, uint32_t flags) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (prefs_->RemovePath(key, nullptr))
+    ReportValueChanged(key, flags);
+}
+
+void JsonPrefStore::RemoveValueSilently(const std::string& key,
+                                        uint32_t flags) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  prefs_->RemovePath(key, nullptr);
+  ScheduleWrite(flags);
+}
+
+bool JsonPrefStore::ReadOnly() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  return read_only_;
+}
+
+PersistentPrefStore::PrefReadError JsonPrefStore::GetReadError() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  return read_error_;
+}
+
+PersistentPrefStore::PrefReadError JsonPrefStore::ReadPrefs() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  OnFileRead(ReadPrefsFromDisk(path_));
+  return filtering_in_progress_ ? PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE
+                                : read_error_;
+}
+
+void JsonPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  initialized_ = false;
+  error_delegate_.reset(error_delegate);
+
+  // Weakly binds the read task so that it doesn't kick in during shutdown.
+  base::PostTaskAndReplyWithResult(
+      file_task_runner_.get(), FROM_HERE, base::Bind(&ReadPrefsFromDisk, path_),
+      base::Bind(&JsonPrefStore::OnFileRead, AsWeakPtr()));
+}
+
+void JsonPrefStore::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Schedule a write for any lossy writes that are outstanding to ensure that
+  // they get flushed when this function is called.
+  SchedulePendingLossyWrites();
+
+  if (writer_.HasPendingWrite() && !read_only_)
+    writer_.DoScheduledWrite();
+
+  // Since disk operations occur on |file_task_runner_|, the reply of a task
+  // posted to |file_task_runner_| will run after currently pending disk
+  // operations. Also, by definition of PostTaskAndReply(), the reply (in the
+  // |reply_callback| case will run on the current sequence.
+
+  if (synchronous_done_callback) {
+    file_task_runner_->PostTask(FROM_HERE,
+                                std::move(synchronous_done_callback));
+  }
+
+  if (reply_callback) {
+    file_task_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(),
+                                        std::move(reply_callback));
+  }
+}
+
+void JsonPrefStore::SchedulePendingLossyWrites() {
+  if (pending_lossy_write_)
+    writer_.ScheduleWrite(this);
+}
+
+void JsonPrefStore::ReportValueChanged(const std::string& key, uint32_t flags) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (pref_filter_)
+    pref_filter_->FilterUpdate(key);
+
+  for (PrefStore::Observer& observer : observers_)
+    observer.OnPrefValueChanged(key);
+
+  ScheduleWrite(flags);
+}
+
+void JsonPrefStore::RunOrScheduleNextSuccessfulWriteCallback(
+    bool write_success) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  has_pending_write_reply_ = false;
+  if (!on_next_successful_write_reply_.is_null()) {
+    base::Closure on_successful_write =
+        std::move(on_next_successful_write_reply_);
+    if (write_success) {
+      on_successful_write.Run();
+    } else {
+      RegisterOnNextSuccessfulWriteReply(on_successful_write);
+    }
+  }
+}
+
+// static
+void JsonPrefStore::PostWriteCallback(
+    const base::Callback<void(bool success)>& on_next_write_callback,
+    const base::Callback<void(bool success)>& on_next_write_reply,
+    scoped_refptr<base::SequencedTaskRunner> reply_task_runner,
+    bool write_success) {
+  if (!on_next_write_callback.is_null())
+    on_next_write_callback.Run(write_success);
+
+  // We can't run |on_next_write_reply| on the current thread. Bounce back to
+  // the |reply_task_runner| which is the correct sequenced thread.
+  reply_task_runner->PostTask(
+      FROM_HERE, base::BindOnce(on_next_write_reply, write_success));
+}
+
+void JsonPrefStore::RegisterOnNextSuccessfulWriteReply(
+    const base::Closure& on_next_successful_write_reply) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(on_next_successful_write_reply_.is_null());
+
+  on_next_successful_write_reply_ = on_next_successful_write_reply;
+
+  // If there are pending callbacks, avoid erasing them; the reply will be used
+  // as we set |on_next_successful_write_reply_|. Otherwise, setup a reply with
+  // an empty callback.
+  if (!has_pending_write_reply_) {
+    has_pending_write_reply_ = true;
+    writer_.RegisterOnNextWriteCallbacks(
+        base::Closure(),
+        base::Bind(
+            &PostWriteCallback, base::Callback<void(bool success)>(),
+            base::Bind(&JsonPrefStore::RunOrScheduleNextSuccessfulWriteCallback,
+                       AsWeakPtr()),
+            base::SequencedTaskRunnerHandle::Get()));
+  }
+}
+
+void JsonPrefStore::RegisterOnNextWriteSynchronousCallbacks(
+    OnWriteCallbackPair callbacks) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  has_pending_write_reply_ = true;
+
+  writer_.RegisterOnNextWriteCallbacks(
+      callbacks.first,
+      base::Bind(
+          &PostWriteCallback, callbacks.second,
+          base::Bind(&JsonPrefStore::RunOrScheduleNextSuccessfulWriteCallback,
+                     AsWeakPtr()),
+          base::SequencedTaskRunnerHandle::Get()));
+}
+
+void JsonPrefStore::ClearMutableValues() {
+  NOTIMPLEMENTED();
+}
+
+void JsonPrefStore::OnStoreDeletionFromDisk() {
+  if (pref_filter_)
+    pref_filter_->OnStoreDeletionFromDisk();
+}
+
+void JsonPrefStore::OnFileRead(std::unique_ptr<ReadResult> read_result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DCHECK(read_result);
+
+  std::unique_ptr<base::DictionaryValue> unfiltered_prefs(
+      new base::DictionaryValue);
+
+  read_error_ = read_result->error;
+
+  bool initialization_successful = !read_result->no_dir;
+
+  if (initialization_successful) {
+    switch (read_error_) {
+      case PREF_READ_ERROR_ACCESS_DENIED:
+      case PREF_READ_ERROR_FILE_OTHER:
+      case PREF_READ_ERROR_FILE_LOCKED:
+      case PREF_READ_ERROR_JSON_TYPE:
+      case PREF_READ_ERROR_FILE_NOT_SPECIFIED:
+        read_only_ = true;
+        break;
+      case PREF_READ_ERROR_NONE:
+        DCHECK(read_result->value);
+        unfiltered_prefs.reset(
+            static_cast<base::DictionaryValue*>(read_result->value.release()));
+        break;
+      case PREF_READ_ERROR_NO_FILE:
+
+      // If the file just doesn't exist, maybe this is first run.  In any case
+      // there's no harm in writing out default prefs in this case.
+      case PREF_READ_ERROR_JSON_PARSE:
+      case PREF_READ_ERROR_JSON_REPEAT:
+        break;
+      case PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE:
+      // This is a special error code to be returned by ReadPrefs when it
+      // can't complete synchronously, it should never be returned by the read
+      // operation itself.
+      case PREF_READ_ERROR_MAX_ENUM:
+        NOTREACHED();
+        break;
+    }
+  }
+
+  if (pref_filter_) {
+    filtering_in_progress_ = true;
+    const PrefFilter::PostFilterOnLoadCallback post_filter_on_load_callback(
+        base::Bind(&JsonPrefStore::FinalizeFileRead, AsWeakPtr(),
+                   initialization_successful));
+    pref_filter_->FilterOnLoad(post_filter_on_load_callback,
+                               std::move(unfiltered_prefs));
+  } else {
+    FinalizeFileRead(initialization_successful, std::move(unfiltered_prefs),
+                     false);
+  }
+}
+
+JsonPrefStore::~JsonPrefStore() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CommitPendingWrite();
+}
+
+bool JsonPrefStore::SerializeData(std::string* output) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  pending_lossy_write_ = false;
+
+  if (pref_filter_) {
+    OnWriteCallbackPair callbacks =
+        pref_filter_->FilterSerializeData(prefs_.get());
+    if (!callbacks.first.is_null() || !callbacks.second.is_null())
+      RegisterOnNextWriteSynchronousCallbacks(callbacks);
+  }
+
+  JSONStringValueSerializer serializer(output);
+  // Not pretty-printing prefs shrinks pref file size by ~30%. To obtain
+  // readable prefs for debugging purposes, you can dump your prefs into any
+  // command-line or online JSON pretty printing tool.
+  serializer.set_pretty_print(false);
+  bool success = serializer.Serialize(*prefs_);
+  DCHECK(success);
+  return success;
+}
+
+void JsonPrefStore::FinalizeFileRead(
+    bool initialization_successful,
+    std::unique_ptr<base::DictionaryValue> prefs,
+    bool schedule_write) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  filtering_in_progress_ = false;
+
+  if (!initialization_successful) {
+    for (PrefStore::Observer& observer : observers_)
+      observer.OnInitializationCompleted(false);
+    return;
+  }
+
+  prefs_ = std::move(prefs);
+
+  initialized_ = true;
+
+  if (schedule_write)
+    ScheduleWrite(DEFAULT_PREF_WRITE_FLAGS);
+
+  if (error_delegate_ && read_error_ != PREF_READ_ERROR_NONE)
+    error_delegate_->OnError(read_error_);
+
+  for (PrefStore::Observer& observer : observers_)
+    observer.OnInitializationCompleted(true);
+
+  return;
+}
+
+void JsonPrefStore::ScheduleWrite(uint32_t flags) {
+  if (read_only_)
+    return;
+
+  if (flags & LOSSY_PREF_WRITE_FLAG)
+    pending_lossy_write_ = true;
+  else
+    writer_.ScheduleWrite(this);
+}
diff --git a/src/components/prefs/json_pref_store.h b/src/components/prefs/json_pref_store.h
new file mode 100644
index 0000000..23c882a
--- /dev/null
+++ b/src/components/prefs/json_pref_store.h
@@ -0,0 +1,202 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_JSON_PREF_STORE_H_
+#define COMPONENTS_PREFS_JSON_PREF_STORE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/files/important_file_writer.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "base/task/post_task.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_filter.h"
+#include "components/prefs/prefs_export.h"
+
+class PrefFilter;
+
+namespace base {
+class DictionaryValue;
+class FilePath;
+class JsonPrefStoreCallbackTest;
+class JsonPrefStoreLossyWriteTest;
+class SequencedTaskRunner;
+class WriteCallbacksObserver;
+class Value;
+}  // namespace base
+
+// A writable PrefStore implementation that is used for user preferences.
+class COMPONENTS_PREFS_EXPORT JsonPrefStore
+    : public PersistentPrefStore,
+      public base::ImportantFileWriter::DataSerializer,
+      public base::SupportsWeakPtr<JsonPrefStore> {
+ public:
+  struct ReadResult;
+
+  // A pair of callbacks to call before and after the preference file is written
+  // to disk.
+  using OnWriteCallbackPair =
+      std::pair<base::Closure, base::Callback<void(bool success)>>;
+
+  // |pref_filename| is the path to the file to read prefs from. It is incorrect
+  // to create multiple JsonPrefStore with the same |pref_filename|.
+  // |file_task_runner| is used for asynchronous reads and writes. It must
+  // have the base::TaskShutdownBehavior::BLOCK_SHUTDOWN and base::MayBlock()
+  // traits. Unless external tasks need to run on the same sequence as
+  // JsonPrefStore tasks, keep the default value.
+  // The initial read is done synchronously, the TaskPriority is thus only used
+  // for flushes to disks and BEST_EFFORT is therefore appropriate. Priority of
+  // remaining BEST_EFFORT+BLOCK_SHUTDOWN tasks is bumped by the ThreadPool on
+  // shutdown. However, some shutdown use cases happen without
+  // ThreadPoolInstance::Shutdown() (e.g. ChromeRestartRequest::Start() and
+  // BrowserProcessImpl::EndSession()) and we must thus unfortunately make this
+  // USER_VISIBLE until we solve https://crbug.com/747495 to allow bumping
+  // priority of a sequence on demand.
+  JsonPrefStore(const base::FilePath& pref_filename,
+                std::unique_ptr<PrefFilter> pref_filter = nullptr,
+                scoped_refptr<base::SequencedTaskRunner> file_task_runner =
+                    base::CreateSequencedTaskRunnerWithTraits(
+                        {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
+                         base::TaskShutdownBehavior::BLOCK_SHUTDOWN}));
+
+  // PrefStore overrides:
+  bool GetValue(const std::string& key,
+                const base::Value** result) const override;
+  std::unique_ptr<base::DictionaryValue> GetValues() const override;
+  void AddObserver(PrefStore::Observer* observer) override;
+  void RemoveObserver(PrefStore::Observer* observer) override;
+  bool HasObservers() const override;
+  bool IsInitializationComplete() const override;
+
+  // PersistentPrefStore overrides:
+  bool GetMutableValue(const std::string& key, base::Value** result) override;
+  void SetValue(const std::string& key,
+                std::unique_ptr<base::Value> value,
+                uint32_t flags) override;
+  void SetValueSilently(const std::string& key,
+                        std::unique_ptr<base::Value> value,
+                        uint32_t flags) override;
+  void RemoveValue(const std::string& key, uint32_t flags) override;
+  bool ReadOnly() const override;
+  PrefReadError GetReadError() const override;
+  // Note this method may be asynchronous if this instance has a |pref_filter_|
+  // in which case it will return PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE.
+  // See details in pref_filter.h.
+  PrefReadError ReadPrefs() override;
+  void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
+  void CommitPendingWrite(
+      base::OnceClosure reply_callback = base::OnceClosure(),
+      base::OnceClosure synchronous_done_callback =
+          base::OnceClosure()) override;
+  void SchedulePendingLossyWrites() override;
+  void ReportValueChanged(const std::string& key, uint32_t flags) override;
+
+  // Just like RemoveValue(), but doesn't notify observers. Used when doing some
+  // cleanup that shouldn't otherwise alert observers.
+  void RemoveValueSilently(const std::string& key, uint32_t flags);
+
+  // Registers |on_next_successful_write_reply| to be called once, on the next
+  // successful write event of |writer_|.
+  // |on_next_successful_write_reply| will be called on the thread from which
+  // this method is called and does not need to be thread safe.
+  void RegisterOnNextSuccessfulWriteReply(
+      const base::Closure& on_next_successful_write_reply);
+
+  void ClearMutableValues() override;
+
+  void OnStoreDeletionFromDisk() override;
+
+ private:
+  friend class base::JsonPrefStoreCallbackTest;
+  friend class base::JsonPrefStoreLossyWriteTest;
+  friend class base::WriteCallbacksObserver;
+
+  ~JsonPrefStore() override;
+
+  // If |write_success| is true, runs |on_next_successful_write_|.
+  // Otherwise, re-registers |on_next_successful_write_|.
+  void RunOrScheduleNextSuccessfulWriteCallback(bool write_success);
+
+  // Handles the result of a write with result |write_success|. Runs
+  // |on_next_write_callback| on the current thread and posts
+  // |on_next_write_reply| on |reply_task_runner|.
+  static void PostWriteCallback(
+      const base::Callback<void(bool success)>& on_next_write_callback,
+      const base::Callback<void(bool success)>& on_next_write_reply,
+      scoped_refptr<base::SequencedTaskRunner> reply_task_runner,
+      bool write_success);
+
+  // Registers the |callbacks| pair to be called once synchronously before and
+  // after, respectively, the next write event of |writer_|.
+  // Both callbacks must be thread-safe.
+  void RegisterOnNextWriteSynchronousCallbacks(OnWriteCallbackPair callbacks);
+
+  // This method is called after the JSON file has been read.  It then hands
+  // |value| (or an empty dictionary in some read error cases) to the
+  // |pref_filter| if one is set. It also gives a callback pointing at
+  // FinalizeFileRead() to that |pref_filter_| which is then responsible for
+  // invoking it when done. If there is no |pref_filter_|, FinalizeFileRead()
+  // is invoked directly.
+  void OnFileRead(std::unique_ptr<ReadResult> read_result);
+
+  // ImportantFileWriter::DataSerializer overrides:
+  bool SerializeData(std::string* output) override;
+
+  // This method is called after the JSON file has been read and the result has
+  // potentially been intercepted and modified by |pref_filter_|.
+  // |initialization_successful| is pre-determined by OnFileRead() and should
+  // be used when reporting OnInitializationCompleted().
+  // |schedule_write| indicates whether a write should be immediately scheduled
+  // (typically because the |pref_filter_| has already altered the |prefs|) --
+  // this will be ignored if this store is read-only.
+  void FinalizeFileRead(bool initialization_successful,
+                        std::unique_ptr<base::DictionaryValue> prefs,
+                        bool schedule_write);
+
+  // Schedule a write with the file writer as long as |flags| doesn't contain
+  // WriteablePrefStore::LOSSY_PREF_WRITE_FLAG.
+  void ScheduleWrite(uint32_t flags);
+
+  const base::FilePath path_;
+  const scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
+
+  std::unique_ptr<base::DictionaryValue> prefs_;
+
+  bool read_only_;
+
+  // Helper for safely writing pref data.
+  base::ImportantFileWriter writer_;
+
+  std::unique_ptr<PrefFilter> pref_filter_;
+  base::ObserverList<PrefStore::Observer, true>::Unchecked observers_;
+
+  std::unique_ptr<ReadErrorDelegate> error_delegate_;
+
+  bool initialized_;
+  bool filtering_in_progress_;
+  bool pending_lossy_write_;
+  PrefReadError read_error_;
+
+  std::set<std::string> keys_need_empty_value_;
+
+  bool has_pending_write_reply_ = true;
+  base::Closure on_next_successful_write_reply_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(JsonPrefStore);
+};
+
+#endif  // COMPONENTS_PREFS_JSON_PREF_STORE_H_
diff --git a/src/components/prefs/json_pref_store_unittest.cc b/src/components/prefs/json_pref_store_unittest.cc
new file mode 100644
index 0000000..3e6e3ad
--- /dev/null
+++ b/src/components/prefs/json_pref_store_unittest.cc
@@ -0,0 +1,990 @@
+// Copyright (c) 2012 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 "components/prefs/json_pref_store.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread.h"
+#include "base/values.h"
+#include "components/prefs/persistent_pref_store_unittest.h"
+#include "components/prefs/pref_filter.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+const char kHomePage[] = "homepage";
+
+const char kReadJson[] =
+    "{\n"
+    "  \"homepage\": \"http://www.cnn.com\",\n"
+    "  \"some_directory\": \"/usr/local/\",\n"
+    "  \"tabs\": {\n"
+    "    \"new_windows_in_tabs\": true,\n"
+    "    \"max_tabs\": 20\n"
+    "  }\n"
+    "}";
+
+const char kInvalidJson[] = "!@#$%^&";
+
+// Expected output for tests using RunBasicJsonPrefStoreTest().
+const char kWriteGolden[] =
+    "{\"homepage\":\"http://www.cnn.com\","
+    "\"long_int\":{\"pref\":\"214748364842\"},"
+    "\"some_directory\":\"/usr/sbin/\","
+    "\"tabs\":{\"max_tabs\":10,\"new_windows_in_tabs\":false}}";
+
+// A PrefFilter that will intercept all calls to FilterOnLoad() and hold on
+// to the |prefs| until explicitly asked to release them.
+class InterceptingPrefFilter : public PrefFilter {
+ public:
+  InterceptingPrefFilter();
+  InterceptingPrefFilter(OnWriteCallbackPair callback_pair);
+  ~InterceptingPrefFilter() override;
+
+  // PrefFilter implementation:
+  void FilterOnLoad(
+      const PostFilterOnLoadCallback& post_filter_on_load_callback,
+      std::unique_ptr<base::DictionaryValue> pref_store_contents) override;
+  void FilterUpdate(const std::string& path) override {}
+  OnWriteCallbackPair FilterSerializeData(
+      base::DictionaryValue* pref_store_contents) override {
+    return on_write_callback_pair_;
+  }
+  void OnStoreDeletionFromDisk() override {}
+
+  bool has_intercepted_prefs() const { return intercepted_prefs_ != nullptr; }
+
+  // Finalize an intercepted read, handing |intercepted_prefs_| back to its
+  // JsonPrefStore.
+  void ReleasePrefs();
+
+ private:
+  PostFilterOnLoadCallback post_filter_on_load_callback_;
+  std::unique_ptr<base::DictionaryValue> intercepted_prefs_;
+  OnWriteCallbackPair on_write_callback_pair_;
+
+  DISALLOW_COPY_AND_ASSIGN(InterceptingPrefFilter);
+};
+
+InterceptingPrefFilter::InterceptingPrefFilter() {}
+
+InterceptingPrefFilter::InterceptingPrefFilter(
+    OnWriteCallbackPair callback_pair) {
+  on_write_callback_pair_ = callback_pair;
+}
+
+InterceptingPrefFilter::~InterceptingPrefFilter() {}
+
+void InterceptingPrefFilter::FilterOnLoad(
+    const PostFilterOnLoadCallback& post_filter_on_load_callback,
+    std::unique_ptr<base::DictionaryValue> pref_store_contents) {
+  post_filter_on_load_callback_ = post_filter_on_load_callback;
+  intercepted_prefs_ = std::move(pref_store_contents);
+}
+
+void InterceptingPrefFilter::ReleasePrefs() {
+  EXPECT_FALSE(post_filter_on_load_callback_.is_null());
+  post_filter_on_load_callback_.Run(std::move(intercepted_prefs_), false);
+  post_filter_on_load_callback_.Reset();
+}
+
+class MockPrefStoreObserver : public PrefStore::Observer {
+ public:
+  MOCK_METHOD1(OnPrefValueChanged, void(const std::string&));
+  MOCK_METHOD1(OnInitializationCompleted, void(bool));
+};
+
+class MockReadErrorDelegate : public PersistentPrefStore::ReadErrorDelegate {
+ public:
+  MOCK_METHOD1(OnError, void(PersistentPrefStore::PrefReadError));
+};
+
+enum class CommitPendingWriteMode {
+  // Basic mode.
+  WITHOUT_CALLBACK,
+  // With reply callback.
+  WITH_CALLBACK,
+  // With synchronous notify callback (synchronous after the write -- shouldn't
+  // require pumping messages to observe).
+  WITH_SYNCHRONOUS_CALLBACK,
+};
+
+base::test::ScopedTaskEnvironment::ThreadPoolExecutionMode GetExecutionMode(
+    CommitPendingWriteMode commit_mode) {
+  switch (commit_mode) {
+    case CommitPendingWriteMode::WITHOUT_CALLBACK:
+      FALLTHROUGH;
+    case CommitPendingWriteMode::WITH_CALLBACK:
+      return base::test::ScopedTaskEnvironment::ThreadPoolExecutionMode::QUEUED;
+    case CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK:
+      // Synchronous callbacks require async tasks to run on their own.
+      return base::test::ScopedTaskEnvironment::ThreadPoolExecutionMode::ASYNC;
+  }
+}
+
+void CommitPendingWrite(
+    JsonPrefStore* pref_store,
+    CommitPendingWriteMode commit_pending_write_mode,
+    base::test::ScopedTaskEnvironment* scoped_task_environment) {
+  switch (commit_pending_write_mode) {
+    case CommitPendingWriteMode::WITHOUT_CALLBACK: {
+      pref_store->CommitPendingWrite();
+      scoped_task_environment->RunUntilIdle();
+      break;
+    }
+    case CommitPendingWriteMode::WITH_CALLBACK: {
+      TestCommitPendingWriteWithCallback(pref_store, scoped_task_environment);
+      break;
+    }
+    case CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK: {
+      base::WaitableEvent written;
+      pref_store->CommitPendingWrite(
+          base::OnceClosure(),
+          base::BindOnce(&base::WaitableEvent::Signal, Unretained(&written)));
+      written.Wait();
+      break;
+    }
+  }
+}
+
+class JsonPrefStoreTest
+    : public testing::TestWithParam<CommitPendingWriteMode> {
+ public:
+  JsonPrefStoreTest()
+      : scoped_task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::DEFAULT,
+            GetExecutionMode(GetParam())) {}
+
+ protected:
+  void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  // The path to temporary directory used to contain the test operations.
+  base::ScopedTempDir temp_dir_;
+
+  DISALLOW_COPY_AND_ASSIGN(JsonPrefStoreTest);
+};
+
+}  // namespace
+
+// Test fallback behavior for a nonexistent file.
+TEST_P(JsonPrefStoreTest, NonExistentFile) {
+  base::FilePath bogus_input_file = temp_dir_.GetPath().AppendASCII("read.txt");
+  ASSERT_FALSE(PathExists(bogus_input_file));
+  auto pref_store = base::MakeRefCounted<JsonPrefStore>(bogus_input_file);
+  EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE,
+            pref_store->ReadPrefs());
+  EXPECT_FALSE(pref_store->ReadOnly());
+}
+
+// Test fallback behavior for an invalid file.
+TEST_P(JsonPrefStoreTest, InvalidFile) {
+  base::FilePath invalid_file = temp_dir_.GetPath().AppendASCII("invalid.json");
+  ASSERT_LT(0, base::WriteFile(invalid_file, kInvalidJson,
+                               base::size(kInvalidJson) - 1));
+
+  auto pref_store = base::MakeRefCounted<JsonPrefStore>(invalid_file);
+  EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE,
+            pref_store->ReadPrefs());
+  EXPECT_FALSE(pref_store->ReadOnly());
+
+  // The file should have been moved aside.
+  EXPECT_FALSE(PathExists(invalid_file));
+  base::FilePath moved_aside = temp_dir_.GetPath().AppendASCII("invalid.bad");
+  EXPECT_TRUE(PathExists(moved_aside));
+
+  std::string moved_aside_contents;
+  ASSERT_TRUE(base::ReadFileToString(moved_aside, &moved_aside_contents));
+  EXPECT_EQ(kInvalidJson, moved_aside_contents);
+}
+
+// This function is used to avoid code duplication while testing synchronous
+// and asynchronous version of the JsonPrefStore loading. It validates that the
+// given output file's contents matches kWriteGolden.
+void RunBasicJsonPrefStoreTest(
+    JsonPrefStore* pref_store,
+    const base::FilePath& output_file,
+    CommitPendingWriteMode commit_pending_write_mode,
+    base::test::ScopedTaskEnvironment* scoped_task_environment) {
+  const char kNewWindowsInTabs[] = "tabs.new_windows_in_tabs";
+  const char kMaxTabs[] = "tabs.max_tabs";
+  const char kLongIntPref[] = "long_int.pref";
+
+  std::string cnn("http://www.cnn.com");
+
+  const Value* actual;
+  EXPECT_TRUE(pref_store->GetValue(kHomePage, &actual));
+  std::string string_value;
+  EXPECT_TRUE(actual->GetAsString(&string_value));
+  EXPECT_EQ(cnn, string_value);
+
+  const char kSomeDirectory[] = "some_directory";
+
+  EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual));
+  base::FilePath::StringType path;
+  EXPECT_TRUE(actual->GetAsString(&path));
+  EXPECT_EQ(base::FilePath::StringType(FILE_PATH_LITERAL("/usr/local/")), path);
+  base::FilePath some_path(FILE_PATH_LITERAL("/usr/sbin/"));
+
+  pref_store->SetValue(kSomeDirectory,
+                       std::make_unique<Value>(some_path.value()),
+                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual));
+  EXPECT_TRUE(actual->GetAsString(&path));
+  EXPECT_EQ(some_path.value(), path);
+
+  // Test reading some other data types from sub-dictionaries.
+  EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual));
+  bool boolean = false;
+  EXPECT_TRUE(actual->GetAsBoolean(&boolean));
+  EXPECT_TRUE(boolean);
+
+  pref_store->SetValue(kNewWindowsInTabs, std::make_unique<Value>(false),
+                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual));
+  EXPECT_TRUE(actual->GetAsBoolean(&boolean));
+  EXPECT_FALSE(boolean);
+
+  EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual));
+  int integer = 0;
+  EXPECT_TRUE(actual->GetAsInteger(&integer));
+  EXPECT_EQ(20, integer);
+  pref_store->SetValue(kMaxTabs, std::make_unique<Value>(10),
+                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual));
+  EXPECT_TRUE(actual->GetAsInteger(&integer));
+  EXPECT_EQ(10, integer);
+
+  pref_store->SetValue(
+      kLongIntPref,
+      std::make_unique<Value>(base::NumberToString(214748364842LL)),
+      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_TRUE(pref_store->GetValue(kLongIntPref, &actual));
+  EXPECT_TRUE(actual->GetAsString(&string_value));
+  int64_t value;
+  base::StringToInt64(string_value, &value);
+  EXPECT_EQ(214748364842LL, value);
+
+  // Serialize and compare to expected output.
+
+  CommitPendingWrite(pref_store, commit_pending_write_mode,
+                     scoped_task_environment);
+
+  std::string output_contents;
+  ASSERT_TRUE(base::ReadFileToString(output_file, &output_contents));
+  EXPECT_EQ(kWriteGolden, output_contents);
+  ASSERT_TRUE(base::DeleteFile(output_file, false));
+}
+
+TEST_P(JsonPrefStoreTest, Basic) {
+  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
+  ASSERT_LT(0,
+            base::WriteFile(input_file, kReadJson, base::size(kReadJson) - 1));
+
+  // Test that the persistent value can be loaded.
+  ASSERT_TRUE(PathExists(input_file));
+  auto pref_store = base::MakeRefCounted<JsonPrefStore>(input_file);
+  ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs());
+  EXPECT_FALSE(pref_store->ReadOnly());
+  EXPECT_TRUE(pref_store->IsInitializationComplete());
+
+  // The JSON file looks like this:
+  // {
+  //   "homepage": "http://www.cnn.com",
+  //   "some_directory": "/usr/local/",
+  //   "tabs": {
+  //     "new_windows_in_tabs": true,
+  //     "max_tabs": 20
+  //   }
+  // }
+
+  RunBasicJsonPrefStoreTest(pref_store.get(), input_file, GetParam(),
+                            &scoped_task_environment_);
+}
+
+TEST_P(JsonPrefStoreTest, BasicAsync) {
+  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
+  ASSERT_LT(0,
+            base::WriteFile(input_file, kReadJson, base::size(kReadJson) - 1));
+
+  // Test that the persistent value can be loaded.
+  auto pref_store = base::MakeRefCounted<JsonPrefStore>(input_file);
+
+  {
+    MockPrefStoreObserver mock_observer;
+    pref_store->AddObserver(&mock_observer);
+
+    MockReadErrorDelegate* mock_error_delegate = new MockReadErrorDelegate;
+    pref_store->ReadPrefsAsync(mock_error_delegate);
+
+    EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
+    EXPECT_CALL(*mock_error_delegate,
+                OnError(PersistentPrefStore::PREF_READ_ERROR_NONE))
+        .Times(0);
+    scoped_task_environment_.RunUntilIdle();
+    pref_store->RemoveObserver(&mock_observer);
+
+    EXPECT_FALSE(pref_store->ReadOnly());
+    EXPECT_TRUE(pref_store->IsInitializationComplete());
+  }
+
+  // The JSON file looks like this:
+  // {
+  //   "homepage": "http://www.cnn.com",
+  //   "some_directory": "/usr/local/",
+  //   "tabs": {
+  //     "new_windows_in_tabs": true,
+  //     "max_tabs": 20
+  //   }
+  // }
+
+  RunBasicJsonPrefStoreTest(pref_store.get(), input_file, GetParam(),
+                            &scoped_task_environment_);
+}
+
+TEST_P(JsonPrefStoreTest, PreserveEmptyValues) {
+  FilePath pref_file = temp_dir_.GetPath().AppendASCII("empty_values.json");
+
+  auto pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);
+
+  // Set some keys with empty values.
+  pref_store->SetValue("list", std::make_unique<base::ListValue>(),
+                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  pref_store->SetValue("dict", std::make_unique<base::DictionaryValue>(),
+                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+  // Write to file.
+  CommitPendingWrite(pref_store.get(), GetParam(), &scoped_task_environment_);
+
+  // Reload.
+  pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);
+  ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs());
+  ASSERT_FALSE(pref_store->ReadOnly());
+
+  // Check values.
+  const Value* result = nullptr;
+  EXPECT_TRUE(pref_store->GetValue("list", &result));
+  EXPECT_TRUE(ListValue().Equals(result));
+  EXPECT_TRUE(pref_store->GetValue("dict", &result));
+  EXPECT_TRUE(DictionaryValue().Equals(result));
+}
+
+// This test is just documenting some potentially non-obvious behavior. It
+// shouldn't be taken as normative.
+TEST_P(JsonPrefStoreTest, RemoveClearsEmptyParent) {
+  FilePath pref_file = temp_dir_.GetPath().AppendASCII("empty_values.json");
+
+  auto pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);
+
+  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+  dict->SetString("key", "value");
+  pref_store->SetValue("dict", std::move(dict),
+                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+  pref_store->RemoveValue("dict.key",
+                          WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+  const base::Value* retrieved_dict = nullptr;
+  bool has_dict = pref_store->GetValue("dict", &retrieved_dict);
+  EXPECT_FALSE(has_dict);
+}
+
+// Tests asynchronous reading of the file when there is no file.
+TEST_P(JsonPrefStoreTest, AsyncNonExistingFile) {
+  base::FilePath bogus_input_file = temp_dir_.GetPath().AppendASCII("read.txt");
+  ASSERT_FALSE(PathExists(bogus_input_file));
+  auto pref_store = base::MakeRefCounted<JsonPrefStore>(bogus_input_file);
+  MockPrefStoreObserver mock_observer;
+  pref_store->AddObserver(&mock_observer);
+
+  MockReadErrorDelegate* mock_error_delegate = new MockReadErrorDelegate;
+  pref_store->ReadPrefsAsync(mock_error_delegate);
+
+  EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
+  EXPECT_CALL(*mock_error_delegate,
+              OnError(PersistentPrefStore::PREF_READ_ERROR_NO_FILE))
+      .Times(1);
+  scoped_task_environment_.RunUntilIdle();
+  pref_store->RemoveObserver(&mock_observer);
+
+  EXPECT_FALSE(pref_store->ReadOnly());
+}
+
+TEST_P(JsonPrefStoreTest, ReadWithInterceptor) {
+  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
+  ASSERT_LT(0,
+            base::WriteFile(input_file, kReadJson, base::size(kReadJson) - 1));
+
+  std::unique_ptr<InterceptingPrefFilter> intercepting_pref_filter(
+      new InterceptingPrefFilter());
+  InterceptingPrefFilter* raw_intercepting_pref_filter_ =
+      intercepting_pref_filter.get();
+  auto pref_store = base::MakeRefCounted<JsonPrefStore>(
+      input_file, std::move(intercepting_pref_filter));
+
+  ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE,
+            pref_store->ReadPrefs());
+  EXPECT_FALSE(pref_store->ReadOnly());
+
+  // The store shouldn't be considered initialized until the interceptor
+  // returns.
+  EXPECT_TRUE(raw_intercepting_pref_filter_->has_intercepted_prefs());
+  EXPECT_FALSE(pref_store->IsInitializationComplete());
+  EXPECT_FALSE(pref_store->GetValue(kHomePage, nullptr));
+
+  raw_intercepting_pref_filter_->ReleasePrefs();
+
+  EXPECT_FALSE(raw_intercepting_pref_filter_->has_intercepted_prefs());
+  EXPECT_TRUE(pref_store->IsInitializationComplete());
+  EXPECT_TRUE(pref_store->GetValue(kHomePage, nullptr));
+
+  // The JSON file looks like this:
+  // {
+  //   "homepage": "http://www.cnn.com",
+  //   "some_directory": "/usr/local/",
+  //   "tabs": {
+  //     "new_windows_in_tabs": true,
+  //     "max_tabs": 20
+  //   }
+  // }
+
+  RunBasicJsonPrefStoreTest(pref_store.get(), input_file, GetParam(),
+                            &scoped_task_environment_);
+}
+
+TEST_P(JsonPrefStoreTest, ReadAsyncWithInterceptor) {
+  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
+  ASSERT_LT(0,
+            base::WriteFile(input_file, kReadJson, base::size(kReadJson) - 1));
+
+  std::unique_ptr<InterceptingPrefFilter> intercepting_pref_filter(
+      new InterceptingPrefFilter());
+  InterceptingPrefFilter* raw_intercepting_pref_filter_ =
+      intercepting_pref_filter.get();
+  auto pref_store = base::MakeRefCounted<JsonPrefStore>(
+      input_file, std::move(intercepting_pref_filter));
+
+  MockPrefStoreObserver mock_observer;
+  pref_store->AddObserver(&mock_observer);
+
+  // Ownership of the |mock_error_delegate| is handed to the |pref_store| below.
+  MockReadErrorDelegate* mock_error_delegate = new MockReadErrorDelegate;
+
+  {
+    pref_store->ReadPrefsAsync(mock_error_delegate);
+
+    EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(0);
+    // EXPECT_CALL(*mock_error_delegate,
+    //             OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0);
+    scoped_task_environment_.RunUntilIdle();
+
+    EXPECT_FALSE(pref_store->ReadOnly());
+    EXPECT_TRUE(raw_intercepting_pref_filter_->has_intercepted_prefs());
+    EXPECT_FALSE(pref_store->IsInitializationComplete());
+    EXPECT_FALSE(pref_store->GetValue(kHomePage, nullptr));
+  }
+
+  {
+    EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
+    // EXPECT_CALL(*mock_error_delegate,
+    //             OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0);
+
+    raw_intercepting_pref_filter_->ReleasePrefs();
+
+    EXPECT_FALSE(pref_store->ReadOnly());
+    EXPECT_FALSE(raw_intercepting_pref_filter_->has_intercepted_prefs());
+    EXPECT_TRUE(pref_store->IsInitializationComplete());
+    EXPECT_TRUE(pref_store->GetValue(kHomePage, nullptr));
+  }
+
+  pref_store->RemoveObserver(&mock_observer);
+
+  // The JSON file looks like this:
+  // {
+  //   "homepage": "http://www.cnn.com",
+  //   "some_directory": "/usr/local/",
+  //   "tabs": {
+  //     "new_windows_in_tabs": true,
+  //     "max_tabs": 20
+  //   }
+  // }
+
+  RunBasicJsonPrefStoreTest(pref_store.get(), input_file, GetParam(),
+                            &scoped_task_environment_);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    WithoutCallback,
+    JsonPrefStoreTest,
+    ::testing::Values(CommitPendingWriteMode::WITHOUT_CALLBACK));
+INSTANTIATE_TEST_SUITE_P(
+    WithCallback,
+    JsonPrefStoreTest,
+    ::testing::Values(CommitPendingWriteMode::WITH_CALLBACK));
+INSTANTIATE_TEST_SUITE_P(
+    WithSynchronousCallback,
+    JsonPrefStoreTest,
+    ::testing::Values(CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK));
+
+class JsonPrefStoreLossyWriteTest : public JsonPrefStoreTest {
+ public:
+  JsonPrefStoreLossyWriteTest() = default;
+
+ protected:
+  void SetUp() override {
+    JsonPrefStoreTest::SetUp();
+    test_file_ = temp_dir_.GetPath().AppendASCII("test.json");
+  }
+
+  scoped_refptr<JsonPrefStore> CreatePrefStore() {
+    return base::MakeRefCounted<JsonPrefStore>(test_file_);
+  }
+
+  // Return the ImportantFileWriter for a given JsonPrefStore.
+  ImportantFileWriter* GetImportantFileWriter(JsonPrefStore* pref_store) {
+    return &(pref_store->writer_);
+  }
+
+  // Get the contents of kTestFile. Pumps the message loop before returning the
+  // result.
+  std::string GetTestFileContents() {
+    scoped_task_environment_.RunUntilIdle();
+    std::string file_contents;
+    ReadFileToString(test_file_, &file_contents);
+    return file_contents;
+  }
+
+ private:
+  base::FilePath test_file_;
+
+  DISALLOW_COPY_AND_ASSIGN(JsonPrefStoreLossyWriteTest);
+};
+
+TEST_P(JsonPrefStoreLossyWriteTest, LossyWriteBasic) {
+  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
+  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
+
+  // Set a normal pref and check that it gets scheduled to be written.
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+  pref_store->SetValue("normal", std::make_unique<base::Value>("normal"),
+                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  ASSERT_TRUE(file_writer->HasPendingWrite());
+  file_writer->DoScheduledWrite();
+  ASSERT_EQ("{\"normal\":\"normal\"}", GetTestFileContents());
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+
+  // Set a lossy pref and check that it is not scheduled to be written.
+  // SetValue/RemoveValue.
+  pref_store->SetValue("lossy", std::make_unique<base::Value>("lossy"),
+                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+  pref_store->RemoveValue("lossy", WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+
+  // SetValueSilently/RemoveValueSilently.
+  pref_store->SetValueSilently("lossy", std::make_unique<base::Value>("lossy"),
+                               WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+  pref_store->RemoveValueSilently("lossy",
+                                  WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+
+  // ReportValueChanged.
+  pref_store->SetValue("lossy", std::make_unique<base::Value>("lossy"),
+                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+  pref_store->ReportValueChanged("lossy",
+                                 WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+
+  // Call CommitPendingWrite and check that the lossy pref and the normal pref
+  // are there with the last values set above.
+  pref_store->CommitPendingWrite(base::OnceClosure());
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+  ASSERT_EQ("{\"lossy\":\"lossy\",\"normal\":\"normal\"}",
+            GetTestFileContents());
+}
+
+TEST_P(JsonPrefStoreLossyWriteTest, LossyWriteMixedLossyFirst) {
+  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
+  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
+
+  // Set a lossy pref and check that it is not scheduled to be written.
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+  pref_store->SetValue("lossy", std::make_unique<base::Value>("lossy"),
+                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+
+  // Set a normal pref and check that it is scheduled to be written.
+  pref_store->SetValue("normal", std::make_unique<base::Value>("normal"),
+                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  ASSERT_TRUE(file_writer->HasPendingWrite());
+
+  // Call DoScheduledWrite and check both prefs get written.
+  file_writer->DoScheduledWrite();
+  ASSERT_EQ("{\"lossy\":\"lossy\",\"normal\":\"normal\"}",
+            GetTestFileContents());
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+}
+
+TEST_P(JsonPrefStoreLossyWriteTest, LossyWriteMixedLossySecond) {
+  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
+  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
+
+  // Set a normal pref and check that it is scheduled to be written.
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+  pref_store->SetValue("normal", std::make_unique<base::Value>("normal"),
+                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  ASSERT_TRUE(file_writer->HasPendingWrite());
+
+  // Set a lossy pref and check that the write is still scheduled.
+  pref_store->SetValue("lossy", std::make_unique<base::Value>("lossy"),
+                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
+  ASSERT_TRUE(file_writer->HasPendingWrite());
+
+  // Call DoScheduledWrite and check both prefs get written.
+  file_writer->DoScheduledWrite();
+  ASSERT_EQ("{\"lossy\":\"lossy\",\"normal\":\"normal\"}",
+            GetTestFileContents());
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+}
+
+TEST_P(JsonPrefStoreLossyWriteTest, ScheduleLossyWrite) {
+  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
+  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
+
+  // Set a lossy pref and check that it is not scheduled to be written.
+  pref_store->SetValue("lossy", std::make_unique<base::Value>("lossy"),
+                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+
+  // Schedule pending lossy writes and check that it is scheduled.
+  pref_store->SchedulePendingLossyWrites();
+  ASSERT_TRUE(file_writer->HasPendingWrite());
+
+  // Call CommitPendingWrite and check that the lossy pref is there with the
+  // last value set above.
+  pref_store->CommitPendingWrite(base::OnceClosure());
+  ASSERT_FALSE(file_writer->HasPendingWrite());
+  ASSERT_EQ("{\"lossy\":\"lossy\"}", GetTestFileContents());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    WithoutCallback,
+    JsonPrefStoreLossyWriteTest,
+    ::testing::Values(CommitPendingWriteMode::WITHOUT_CALLBACK));
+INSTANTIATE_TEST_SUITE_P(
+    WithReply,
+    JsonPrefStoreLossyWriteTest,
+    ::testing::Values(CommitPendingWriteMode::WITH_CALLBACK));
+INSTANTIATE_TEST_SUITE_P(
+    WithNotify,
+    JsonPrefStoreLossyWriteTest,
+    ::testing::Values(CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK));
+
+class SuccessfulWriteReplyObserver {
+ public:
+  SuccessfulWriteReplyObserver() = default;
+
+  // Returns true if a successful write was observed via on_successful_write()
+  // and resets the observation state to false regardless.
+  bool GetAndResetObservationState() {
+    bool was_successful_write_observed = successful_write_reply_observed_;
+    successful_write_reply_observed_ = false;
+    return was_successful_write_observed;
+  }
+
+  // Register OnWrite() to be called on the next write of |json_pref_store|.
+  void ObserveNextWriteCallback(JsonPrefStore* json_pref_store);
+
+  void OnSuccessfulWrite() {
+    EXPECT_FALSE(successful_write_reply_observed_);
+    successful_write_reply_observed_ = true;
+  }
+
+ private:
+  bool successful_write_reply_observed_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(SuccessfulWriteReplyObserver);
+};
+
+void SuccessfulWriteReplyObserver::ObserveNextWriteCallback(
+    JsonPrefStore* json_pref_store) {
+  json_pref_store->RegisterOnNextSuccessfulWriteReply(
+      base::Bind(&SuccessfulWriteReplyObserver::OnSuccessfulWrite,
+                 base::Unretained(this)));
+}
+
+enum WriteCallbackObservationState {
+  NOT_CALLED,
+  CALLED_WITH_ERROR,
+  CALLED_WITH_SUCCESS,
+};
+
+class WriteCallbacksObserver {
+ public:
+  WriteCallbacksObserver() = default;
+
+  // Register OnWrite() to be called on the next write of |json_pref_store|.
+  void ObserveNextWriteCallback(JsonPrefStore* json_pref_store);
+
+  // Returns whether OnPreWrite() was called, and resets the observation state
+  // to false.
+  bool GetAndResetPreWriteObservationState();
+
+  // Returns the |WriteCallbackObservationState| which was observed, then resets
+  // it to |NOT_CALLED|.
+  WriteCallbackObservationState GetAndResetPostWriteObservationState();
+
+  JsonPrefStore::OnWriteCallbackPair GetCallbackPair() {
+    return std::make_pair(
+        base::Bind(&WriteCallbacksObserver::OnPreWrite, base::Unretained(this)),
+        base::Bind(&WriteCallbacksObserver::OnPostWrite,
+                   base::Unretained(this)));
+  }
+
+  void OnPreWrite() {
+    EXPECT_FALSE(pre_write_called_);
+    pre_write_called_ = true;
+  }
+
+  void OnPostWrite(bool success) {
+    EXPECT_EQ(NOT_CALLED, post_write_observation_state_);
+    post_write_observation_state_ =
+        success ? CALLED_WITH_SUCCESS : CALLED_WITH_ERROR;
+  }
+
+ private:
+  bool pre_write_called_ = false;
+  WriteCallbackObservationState post_write_observation_state_ = NOT_CALLED;
+
+  DISALLOW_COPY_AND_ASSIGN(WriteCallbacksObserver);
+};
+
+void WriteCallbacksObserver::ObserveNextWriteCallback(JsonPrefStore* writer) {
+  writer->RegisterOnNextWriteSynchronousCallbacks(GetCallbackPair());
+}
+
+bool WriteCallbacksObserver::GetAndResetPreWriteObservationState() {
+  bool observation_state = pre_write_called_;
+  pre_write_called_ = false;
+  return observation_state;
+}
+
+WriteCallbackObservationState
+WriteCallbacksObserver::GetAndResetPostWriteObservationState() {
+  WriteCallbackObservationState state = post_write_observation_state_;
+  pre_write_called_ = false;
+  post_write_observation_state_ = NOT_CALLED;
+  return state;
+}
+
+class JsonPrefStoreCallbackTest : public testing::Test {
+ public:
+  JsonPrefStoreCallbackTest() = default;
+
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    test_file_ = temp_dir_.GetPath().AppendASCII("test.json");
+  }
+
+  scoped_refptr<JsonPrefStore> CreatePrefStore() {
+    return base::MakeRefCounted<JsonPrefStore>(test_file_);
+  }
+
+  // Return the ImportantFileWriter for a given JsonPrefStore.
+  ImportantFileWriter* GetImportantFileWriter(JsonPrefStore* pref_store) {
+    return &(pref_store->writer_);
+  }
+
+  void TriggerFakeWriteForCallback(JsonPrefStore* pref_store, bool success) {
+    JsonPrefStore::PostWriteCallback(
+        base::Bind(&JsonPrefStore::RunOrScheduleNextSuccessfulWriteCallback,
+                   pref_store->AsWeakPtr()),
+        base::Bind(&WriteCallbacksObserver::OnPostWrite,
+                   base::Unretained(&write_callback_observer_)),
+        base::SequencedTaskRunnerHandle::Get(), success);
+  }
+
+  SuccessfulWriteReplyObserver successful_write_reply_observer_;
+  WriteCallbacksObserver write_callback_observer_;
+
+ protected:
+  base::test::ScopedTaskEnvironment scoped_task_environment_{
+      base::test::ScopedTaskEnvironment::MainThreadType::DEFAULT,
+      base::test::ScopedTaskEnvironment::ThreadPoolExecutionMode::QUEUED};
+
+  base::ScopedTempDir temp_dir_;
+
+ private:
+  base::FilePath test_file_;
+
+  DISALLOW_COPY_AND_ASSIGN(JsonPrefStoreCallbackTest);
+};
+
+TEST_F(JsonPrefStoreCallbackTest, TestSerializeDataCallbacks) {
+  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
+  ASSERT_LT(0,
+            base::WriteFile(input_file, kReadJson, base::size(kReadJson) - 1));
+
+  std::unique_ptr<InterceptingPrefFilter> intercepting_pref_filter(
+      new InterceptingPrefFilter(write_callback_observer_.GetCallbackPair()));
+  auto pref_store = base::MakeRefCounted<JsonPrefStore>(
+      input_file, std::move(intercepting_pref_filter));
+  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
+
+  EXPECT_EQ(NOT_CALLED,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+  pref_store->SetValue("normal", std::make_unique<base::Value>("normal"),
+                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  file_writer->DoScheduledWrite();
+
+  // The observer should not be invoked right away.
+  EXPECT_FALSE(write_callback_observer_.GetAndResetPreWriteObservationState());
+  EXPECT_EQ(NOT_CALLED,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+
+  scoped_task_environment_.RunUntilIdle();
+
+  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
+  EXPECT_EQ(CALLED_WITH_SUCCESS,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+}
+
+TEST_F(JsonPrefStoreCallbackTest, TestPostWriteCallbacks) {
+  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
+  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
+
+  // Test RegisterOnNextWriteSynchronousCallbacks after
+  // RegisterOnNextSuccessfulWriteReply.
+  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
+  write_callback_observer_.ObserveNextWriteCallback(pref_store.get());
+  file_writer->WriteNow(std::make_unique<std::string>("foo"));
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
+  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
+  EXPECT_EQ(CALLED_WITH_SUCCESS,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+
+  // Test RegisterOnNextSuccessfulWriteReply after
+  // RegisterOnNextWriteSynchronousCallbacks.
+  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
+  write_callback_observer_.ObserveNextWriteCallback(pref_store.get());
+  file_writer->WriteNow(std::make_unique<std::string>("foo"));
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
+  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
+  EXPECT_EQ(CALLED_WITH_SUCCESS,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+
+  // Test RegisterOnNextSuccessfulWriteReply only.
+  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
+  file_writer->WriteNow(std::make_unique<std::string>("foo"));
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
+  EXPECT_FALSE(write_callback_observer_.GetAndResetPreWriteObservationState());
+  EXPECT_EQ(NOT_CALLED,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+
+  // Test RegisterOnNextWriteSynchronousCallbacks only.
+  write_callback_observer_.ObserveNextWriteCallback(pref_store.get());
+  file_writer->WriteNow(std::make_unique<std::string>("foo"));
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
+  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
+  EXPECT_EQ(CALLED_WITH_SUCCESS,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+}
+
+TEST_F(JsonPrefStoreCallbackTest, TestPostWriteCallbacksWithFakeFailure) {
+  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
+
+  // Confirm that the observers are invoked.
+  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
+  TriggerFakeWriteForCallback(pref_store.get(), true);
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
+  EXPECT_EQ(CALLED_WITH_SUCCESS,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+
+  // Confirm that the observation states were reset.
+  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
+  EXPECT_EQ(NOT_CALLED,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+
+  // Confirm that re-installing the observers works for another write.
+  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
+  TriggerFakeWriteForCallback(pref_store.get(), true);
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
+  EXPECT_EQ(CALLED_WITH_SUCCESS,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+
+  // Confirm that the successful observer is not invoked by an unsuccessful
+  // write, and that the synchronous observer is invoked.
+  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
+  TriggerFakeWriteForCallback(pref_store.get(), false);
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
+  EXPECT_EQ(CALLED_WITH_ERROR,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+
+  // Do a real write, and confirm that the successful observer was invoked after
+  // being set by |PostWriteCallback| by the last TriggerFakeWriteCallback.
+  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
+  file_writer->WriteNow(std::make_unique<std::string>("foo"));
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
+  EXPECT_EQ(NOT_CALLED,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+}
+
+TEST_F(JsonPrefStoreCallbackTest, TestPostWriteCallbacksDuringProfileDeath) {
+  // Create a JsonPrefStore and attach observers to it, then delete it by making
+  // it go out of scope to simulate profile switch or Chrome shutdown.
+  {
+    scoped_refptr<JsonPrefStore> soon_out_of_scope_pref_store =
+        CreatePrefStore();
+    ImportantFileWriter* file_writer =
+        GetImportantFileWriter(soon_out_of_scope_pref_store.get());
+    successful_write_reply_observer_.ObserveNextWriteCallback(
+        soon_out_of_scope_pref_store.get());
+    write_callback_observer_.ObserveNextWriteCallback(
+        soon_out_of_scope_pref_store.get());
+    file_writer->WriteNow(std::make_unique<std::string>("foo"));
+  }
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
+  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
+  EXPECT_EQ(CALLED_WITH_SUCCESS,
+            write_callback_observer_.GetAndResetPostWriteObservationState());
+}
+
+}  // namespace base
diff --git a/src/components/prefs/mock_pref_change_callback.cc b/src/components/prefs/mock_pref_change_callback.cc
new file mode 100644
index 0000000..5ddf58d
--- /dev/null
+++ b/src/components/prefs/mock_pref_change_callback.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/prefs/mock_pref_change_callback.h"
+
+#include "base/bind.h"
+
+MockPrefChangeCallback::MockPrefChangeCallback(PrefService* prefs)
+    : prefs_(prefs) {}
+
+MockPrefChangeCallback::~MockPrefChangeCallback() {}
+
+PrefChangeRegistrar::NamedChangeCallback MockPrefChangeCallback::GetCallback() {
+  return base::Bind(&MockPrefChangeCallback::OnPreferenceChanged,
+                    base::Unretained(this));
+}
+
+void MockPrefChangeCallback::Expect(const std::string& pref_name,
+                                    const base::Value* value) {
+  EXPECT_CALL(*this, OnPreferenceChanged(pref_name))
+      .With(PrefValueMatches(prefs_, pref_name, value));
+}
diff --git a/src/components/prefs/mock_pref_change_callback.h b/src/components/prefs/mock_pref_change_callback.h
new file mode 100644
index 0000000..3fe5ffb
--- /dev/null
+++ b/src/components/prefs/mock_pref_change_callback.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PREFS_MOCK_PREF_CHANGE_CALLBACK_H_
+#define COMPONENTS_PREFS_MOCK_PREF_CHANGE_CALLBACK_H_
+
+#include <string>
+
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::Pointee;
+using testing::Property;
+using testing::Truly;
+
+// Matcher that checks whether the current value of the preference named
+// |pref_name| in |prefs| matches |value|. If |value| is NULL, the matcher
+// checks that the value is not set.
+MATCHER_P3(PrefValueMatches, prefs, pref_name, value, "") {
+  const PrefService::Preference* pref = prefs->FindPreference(pref_name);
+  if (!pref)
+    return false;
+
+  const base::Value* actual_value = pref->GetValue();
+  if (!actual_value)
+    return value == NULL;
+  if (!value)
+    return actual_value == NULL;
+  return value->Equals(actual_value);
+}
+
+// A mock for testing preference notifications and easy setup of expectations.
+class MockPrefChangeCallback {
+ public:
+  explicit MockPrefChangeCallback(PrefService* prefs);
+  virtual ~MockPrefChangeCallback();
+
+  PrefChangeRegistrar::NamedChangeCallback GetCallback();
+
+  MOCK_METHOD1(OnPreferenceChanged, void(const std::string&));
+
+  void Expect(const std::string& pref_name, const base::Value* value);
+
+ private:
+  PrefService* prefs_;
+};
+
+#endif  // COMPONENTS_PREFS_MOCK_PREF_CHANGE_CALLBACK_H_
diff --git a/src/components/prefs/overlay_user_pref_store.cc b/src/components/prefs/overlay_user_pref_store.cc
new file mode 100644
index 0000000..0baa6c4
--- /dev/null
+++ b/src/components/prefs/overlay_user_pref_store.cc
@@ -0,0 +1,247 @@
+// Copyright (c) 2012 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 "components/prefs/overlay_user_pref_store.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/values.h"
+#include "components/prefs/in_memory_pref_store.h"
+
+// Allows us to monitor two pref stores and tell updates from them apart. It
+// essentially mimics a Callback for the Observer interface (e.g. it allows
+// binding additional arguments).
+class OverlayUserPrefStore::ObserverAdapter : public PrefStore::Observer {
+ public:
+  ObserverAdapter(bool ephemeral, OverlayUserPrefStore* parent)
+      : ephemeral_user_pref_store_(ephemeral), parent_(parent) {}
+
+  // Methods of PrefStore::Observer.
+  void OnPrefValueChanged(const std::string& key) override {
+    parent_->OnPrefValueChanged(ephemeral_user_pref_store_, key);
+  }
+  void OnInitializationCompleted(bool succeeded) override {
+    parent_->OnInitializationCompleted(ephemeral_user_pref_store_, succeeded);
+  }
+
+ private:
+  // Is the update for the ephemeral?
+  const bool ephemeral_user_pref_store_;
+  OverlayUserPrefStore* const parent_;
+};
+
+OverlayUserPrefStore::OverlayUserPrefStore(PersistentPrefStore* persistent)
+    : OverlayUserPrefStore(new InMemoryPrefStore(), persistent) {}
+
+OverlayUserPrefStore::OverlayUserPrefStore(PersistentPrefStore* ephemeral,
+                                           PersistentPrefStore* persistent)
+    : ephemeral_pref_store_observer_(
+          std::make_unique<OverlayUserPrefStore::ObserverAdapter>(true, this)),
+      persistent_pref_store_observer_(
+          std::make_unique<OverlayUserPrefStore::ObserverAdapter>(false, this)),
+      ephemeral_user_pref_store_(ephemeral),
+      persistent_user_pref_store_(persistent) {
+  DCHECK(ephemeral->IsInitializationComplete());
+  ephemeral_user_pref_store_->AddObserver(ephemeral_pref_store_observer_.get());
+  persistent_user_pref_store_->AddObserver(
+      persistent_pref_store_observer_.get());
+}
+
+bool OverlayUserPrefStore::IsSetInOverlay(const std::string& key) const {
+  return ephemeral_user_pref_store_->GetValue(key, nullptr);
+}
+
+void OverlayUserPrefStore::AddObserver(PrefStore::Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void OverlayUserPrefStore::RemoveObserver(PrefStore::Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+bool OverlayUserPrefStore::HasObservers() const {
+  return observers_.might_have_observers();
+}
+
+bool OverlayUserPrefStore::IsInitializationComplete() const {
+  return persistent_user_pref_store_->IsInitializationComplete() &&
+         ephemeral_user_pref_store_->IsInitializationComplete();
+}
+
+bool OverlayUserPrefStore::GetValue(const std::string& key,
+                                    const base::Value** result) const {
+  // If the |key| shall NOT be stored in the ephemeral store, there must not
+  // be an entry.
+  DCHECK(!ShallBeStoredInPersistent(key) ||
+         !ephemeral_user_pref_store_->GetValue(key, nullptr));
+
+  if (ephemeral_user_pref_store_->GetValue(key, result))
+    return true;
+  return persistent_user_pref_store_->GetValue(key, result);
+}
+
+std::unique_ptr<base::DictionaryValue> OverlayUserPrefStore::GetValues() const {
+  auto values = ephemeral_user_pref_store_->GetValues();
+  auto persistent_values = persistent_user_pref_store_->GetValues();
+
+  // Output |values| are read from |ephemeral_user_pref_store_| (in-memory
+  // store). Then the values of preferences in |persistent_names_set_| are
+  // overwritten by the content of |persistent_user_pref_store_| (the persistent
+  // store).
+  for (const auto& key : persistent_names_set_) {
+    std::unique_ptr<base::Value> out_value;
+    persistent_values->Remove(key, &out_value);
+    if (out_value) {
+      values->Set(key, std::move(out_value));
+    }
+  }
+  return values;
+}
+
+bool OverlayUserPrefStore::GetMutableValue(const std::string& key,
+                                           base::Value** result) {
+  if (ShallBeStoredInPersistent(key))
+    return persistent_user_pref_store_->GetMutableValue(key, result);
+
+  written_ephemeral_names_.insert(key);
+  if (ephemeral_user_pref_store_->GetMutableValue(key, result))
+    return true;
+
+  // Try to create copy of persistent if the ephemeral does not contain a value.
+  base::Value* persistent_value = nullptr;
+  if (!persistent_user_pref_store_->GetMutableValue(key, &persistent_value))
+    return false;
+
+  ephemeral_user_pref_store_->SetValue(
+      key, persistent_value->CreateDeepCopy(),
+      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  ephemeral_user_pref_store_->GetMutableValue(key, result);
+  return true;
+}
+
+void OverlayUserPrefStore::SetValue(const std::string& key,
+                                    std::unique_ptr<base::Value> value,
+                                    uint32_t flags) {
+  if (ShallBeStoredInPersistent(key)) {
+    persistent_user_pref_store_->SetValue(key, std::move(value), flags);
+    return;
+  }
+
+  // TODO(https://crbug.com/861722): If we always store in in-memory storage
+  // and conditionally also stored in persistent one, we wouldn't have to do a
+  // complex merge in GetValues().
+  written_ephemeral_names_.insert(key);
+  ephemeral_user_pref_store_->SetValue(key, std::move(value), flags);
+}
+
+void OverlayUserPrefStore::SetValueSilently(const std::string& key,
+                                            std::unique_ptr<base::Value> value,
+                                            uint32_t flags) {
+  if (ShallBeStoredInPersistent(key)) {
+    persistent_user_pref_store_->SetValueSilently(key, std::move(value), flags);
+    return;
+  }
+
+  written_ephemeral_names_.insert(key);
+  ephemeral_user_pref_store_->SetValueSilently(key, std::move(value), flags);
+}
+
+void OverlayUserPrefStore::RemoveValue(const std::string& key, uint32_t flags) {
+  if (ShallBeStoredInPersistent(key)) {
+    persistent_user_pref_store_->RemoveValue(key, flags);
+    return;
+  }
+
+  written_ephemeral_names_.insert(key);
+  ephemeral_user_pref_store_->RemoveValue(key, flags);
+}
+
+bool OverlayUserPrefStore::ReadOnly() const {
+  return false;
+}
+
+PersistentPrefStore::PrefReadError OverlayUserPrefStore::GetReadError() const {
+  return PersistentPrefStore::PREF_READ_ERROR_NONE;
+}
+
+PersistentPrefStore::PrefReadError OverlayUserPrefStore::ReadPrefs() {
+  // We do not read intentionally.
+  OnInitializationCompleted(/* ephemeral */ false, true);
+  return PersistentPrefStore::PREF_READ_ERROR_NONE;
+}
+
+void OverlayUserPrefStore::ReadPrefsAsync(
+    ReadErrorDelegate* error_delegate_raw) {
+  std::unique_ptr<ReadErrorDelegate> error_delegate(error_delegate_raw);
+  // We do not read intentionally.
+  OnInitializationCompleted(/* ephemeral */ false, true);
+}
+
+void OverlayUserPrefStore::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
+  persistent_user_pref_store_->CommitPendingWrite(
+      std::move(reply_callback), std::move(synchronous_done_callback));
+  // We do not write our content intentionally.
+}
+
+void OverlayUserPrefStore::SchedulePendingLossyWrites() {
+  persistent_user_pref_store_->SchedulePendingLossyWrites();
+}
+
+void OverlayUserPrefStore::ReportValueChanged(const std::string& key,
+                                              uint32_t flags) {
+  for (PrefStore::Observer& observer : observers_)
+    observer.OnPrefValueChanged(key);
+}
+
+void OverlayUserPrefStore::RegisterPersistentPref(const std::string& key) {
+  DCHECK(!key.empty()) << "Key is empty";
+  DCHECK(persistent_names_set_.find(key) == persistent_names_set_.end())
+      << "Key already registered: " << key;
+  persistent_names_set_.insert(key);
+}
+
+void OverlayUserPrefStore::ClearMutableValues() {
+  for (const auto& key : written_ephemeral_names_) {
+    ephemeral_user_pref_store_->RemoveValue(
+        key, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  }
+}
+
+void OverlayUserPrefStore::OnStoreDeletionFromDisk() {
+  persistent_user_pref_store_->OnStoreDeletionFromDisk();
+}
+
+OverlayUserPrefStore::~OverlayUserPrefStore() {
+  ephemeral_user_pref_store_->RemoveObserver(
+      ephemeral_pref_store_observer_.get());
+  persistent_user_pref_store_->RemoveObserver(
+      persistent_pref_store_observer_.get());
+}
+
+void OverlayUserPrefStore::OnPrefValueChanged(bool ephemeral,
+                                              const std::string& key) {
+  if (ephemeral) {
+    ReportValueChanged(key, DEFAULT_PREF_WRITE_FLAGS);
+  } else {
+    if (!ephemeral_user_pref_store_->GetValue(key, nullptr))
+      ReportValueChanged(key, DEFAULT_PREF_WRITE_FLAGS);
+  }
+}
+
+void OverlayUserPrefStore::OnInitializationCompleted(bool ephemeral,
+                                                     bool succeeded) {
+  if (!IsInitializationComplete())
+    return;
+  for (PrefStore::Observer& observer : observers_)
+    observer.OnInitializationCompleted(succeeded);
+}
+
+bool OverlayUserPrefStore::ShallBeStoredInPersistent(
+    const std::string& key) const {
+  return persistent_names_set_.find(key) != persistent_names_set_.end();
+}
diff --git a/src/components/prefs/overlay_user_pref_store.h b/src/components/prefs/overlay_user_pref_store.h
new file mode 100644
index 0000000..9b7c95a
--- /dev/null
+++ b/src/components/prefs/overlay_user_pref_store.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_OVERLAY_USER_PREF_STORE_H_
+#define COMPONENTS_PREFS_OVERLAY_USER_PREF_STORE_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/prefs/prefs_export.h"
+
+// PersistentPrefStore that directs all write operations into an in-memory
+// PrefValueMap. Read operations are first answered by the PrefValueMap.
+// If the PrefValueMap does not contain a value for the requested key,
+// the look-up is passed on to an underlying PersistentPrefStore
+// |persistent_user_pref_store_|.
+class COMPONENTS_PREFS_EXPORT OverlayUserPrefStore
+    : public PersistentPrefStore {
+ public:
+  explicit OverlayUserPrefStore(PersistentPrefStore* persistent);
+  // The |ephemeral| store must already be initialized.
+  OverlayUserPrefStore(PersistentPrefStore* ephemeral,
+                       PersistentPrefStore* persistent);
+
+  // Returns true if a value has been set for the |key| in this
+  // OverlayUserPrefStore, i.e. if it potentially overrides a value
+  // from the |persistent_user_pref_store_|.
+  virtual bool IsSetInOverlay(const std::string& key) const;
+
+  // Methods of PrefStore.
+  void AddObserver(PrefStore::Observer* observer) override;
+  void RemoveObserver(PrefStore::Observer* observer) override;
+  bool HasObservers() const override;
+  bool IsInitializationComplete() const override;
+  bool GetValue(const std::string& key,
+                const base::Value** result) const override;
+  std::unique_ptr<base::DictionaryValue> GetValues() const override;
+
+  // Methods of PersistentPrefStore.
+  bool GetMutableValue(const std::string& key, base::Value** result) override;
+  void SetValue(const std::string& key,
+                std::unique_ptr<base::Value> value,
+                uint32_t flags) override;
+  void SetValueSilently(const std::string& key,
+                        std::unique_ptr<base::Value> value,
+                        uint32_t flags) override;
+  void RemoveValue(const std::string& key, uint32_t flags) override;
+  bool ReadOnly() const override;
+  PrefReadError GetReadError() const override;
+  PrefReadError ReadPrefs() override;
+  void ReadPrefsAsync(ReadErrorDelegate* delegate) override;
+  void CommitPendingWrite(base::OnceClosure reply_callback,
+                          base::OnceClosure synchronous_done_callback) override;
+  void SchedulePendingLossyWrites() override;
+  void ReportValueChanged(const std::string& key, uint32_t flags) override;
+
+  // Registers preferences that should be stored in the persistent preferences
+  // (|persistent_user_pref_store_|).
+  void RegisterPersistentPref(const std::string& key);
+
+  void ClearMutableValues() override;
+  void OnStoreDeletionFromDisk() override;
+
+ protected:
+  ~OverlayUserPrefStore() override;
+
+ private:
+  typedef std::set<std::string> NamesSet;
+  class ObserverAdapter;
+
+  void OnPrefValueChanged(bool ephemeral, const std::string& key);
+  void OnInitializationCompleted(bool ephemeral, bool succeeded);
+
+  // Returns true if |key| corresponds to a preference that shall be stored in
+  // persistent PrefStore.
+  bool ShallBeStoredInPersistent(const std::string& key) const;
+
+  base::ObserverList<PrefStore::Observer, true>::Unchecked observers_;
+  std::unique_ptr<ObserverAdapter> ephemeral_pref_store_observer_;
+  std::unique_ptr<ObserverAdapter> persistent_pref_store_observer_;
+  scoped_refptr<PersistentPrefStore> ephemeral_user_pref_store_;
+  scoped_refptr<PersistentPrefStore> persistent_user_pref_store_;
+  NamesSet persistent_names_set_;
+  NamesSet written_ephemeral_names_;
+
+  DISALLOW_COPY_AND_ASSIGN(OverlayUserPrefStore);
+};
+
+#endif  // COMPONENTS_PREFS_OVERLAY_USER_PREF_STORE_H_
diff --git a/src/components/prefs/overlay_user_pref_store_unittest.cc b/src/components/prefs/overlay_user_pref_store_unittest.cc
new file mode 100644
index 0000000..5f0d725
--- /dev/null
+++ b/src/components/prefs/overlay_user_pref_store_unittest.cc
@@ -0,0 +1,281 @@
+// Copyright (c) 2012 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 "components/prefs/overlay_user_pref_store.h"
+#include <memory>
+
+#include "base/test/scoped_task_environment.h"
+#include "base/values.h"
+#include "components/prefs/persistent_pref_store_unittest.h"
+#include "components/prefs/pref_store_observer_mock.h"
+#include "components/prefs/testing_pref_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::Mock;
+using ::testing::StrEq;
+
+namespace base {
+namespace {
+
+const char kBrowserWindowPlacement[] = "browser.window_placement";
+const char kShowBookmarkBar[] = "bookmark_bar.show_on_all_tabs";
+const char kSharedKey[] = "sync_promo.show_on_first_run_allowed";
+
+const char* const regular_key = kBrowserWindowPlacement;
+const char* const persistent_key = kShowBookmarkBar;
+const char* const shared_key = kSharedKey;
+
+}  // namespace
+
+class OverlayUserPrefStoreTest : public testing::Test {
+ protected:
+  OverlayUserPrefStoreTest()
+      : underlay_(new TestingPrefStore()),
+        overlay_(new OverlayUserPrefStore(underlay_.get())) {
+    overlay_->RegisterPersistentPref(persistent_key);
+    overlay_->RegisterPersistentPref(shared_key);
+  }
+
+  ~OverlayUserPrefStoreTest() override {}
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  scoped_refptr<TestingPrefStore> underlay_;
+  scoped_refptr<OverlayUserPrefStore> overlay_;
+};
+
+TEST_F(OverlayUserPrefStoreTest, Observer) {
+  PrefStoreObserverMock obs;
+  overlay_->AddObserver(&obs);
+
+  // Check that underlay first value is reported.
+  underlay_->SetValue(regular_key, std::make_unique<Value>(42),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  obs.VerifyAndResetChangedKey(regular_key);
+
+  // Check that underlay overwriting is reported.
+  underlay_->SetValue(regular_key, std::make_unique<Value>(43),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  obs.VerifyAndResetChangedKey(regular_key);
+
+  // Check that overwriting change in overlay is reported.
+  overlay_->SetValue(regular_key, std::make_unique<Value>(44),
+                     WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  obs.VerifyAndResetChangedKey(regular_key);
+
+  // Check that hidden underlay change is not reported.
+  underlay_->SetValue(regular_key, std::make_unique<Value>(45),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_TRUE(obs.changed_keys.empty());
+
+  // Check that overlay remove is reported.
+  overlay_->RemoveValue(regular_key,
+                        WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  obs.VerifyAndResetChangedKey(regular_key);
+
+  // Check that underlay remove is reported.
+  underlay_->RemoveValue(regular_key,
+                         WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  obs.VerifyAndResetChangedKey(regular_key);
+
+  // Check respecting of silence.
+  overlay_->SetValueSilently(regular_key, std::make_unique<Value>(46),
+                             WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_TRUE(obs.changed_keys.empty());
+
+  overlay_->RemoveObserver(&obs);
+
+  // Check successful unsubscription.
+  underlay_->SetValue(regular_key, std::make_unique<Value>(47),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  overlay_->SetValue(regular_key, std::make_unique<Value>(48),
+                     WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_TRUE(obs.changed_keys.empty());
+}
+
+TEST_F(OverlayUserPrefStoreTest, GetAndSet) {
+  const Value* value = nullptr;
+  EXPECT_FALSE(overlay_->GetValue(regular_key, &value));
+  EXPECT_FALSE(underlay_->GetValue(regular_key, &value));
+
+  underlay_->SetValue(regular_key, std::make_unique<Value>(42),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+  // Value shines through:
+  EXPECT_TRUE(overlay_->GetValue(regular_key, &value));
+  EXPECT_TRUE(base::Value(42).Equals(value));
+
+  EXPECT_TRUE(underlay_->GetValue(regular_key, &value));
+  EXPECT_TRUE(base::Value(42).Equals(value));
+
+  overlay_->SetValue(regular_key, std::make_unique<Value>(43),
+                     WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+  EXPECT_TRUE(overlay_->GetValue(regular_key, &value));
+  EXPECT_TRUE(base::Value(43).Equals(value));
+
+  EXPECT_TRUE(underlay_->GetValue(regular_key, &value));
+  EXPECT_TRUE(base::Value(42).Equals(value));
+
+  overlay_->RemoveValue(regular_key,
+                        WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+  // Value shines through:
+  EXPECT_TRUE(overlay_->GetValue(regular_key, &value));
+  EXPECT_TRUE(base::Value(42).Equals(value));
+
+  EXPECT_TRUE(underlay_->GetValue(regular_key, &value));
+  EXPECT_TRUE(base::Value(42).Equals(value));
+}
+
+// Check that GetMutableValue does not return the dictionary of the underlay.
+TEST_F(OverlayUserPrefStoreTest, ModifyDictionaries) {
+  underlay_->SetValue(regular_key, std::make_unique<DictionaryValue>(),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+  Value* modify = nullptr;
+  EXPECT_TRUE(overlay_->GetMutableValue(regular_key, &modify));
+  ASSERT_TRUE(modify);
+  ASSERT_TRUE(modify->is_dict());
+  static_cast<DictionaryValue*>(modify)->SetInteger(regular_key, 42);
+
+  Value* original_in_underlay = nullptr;
+  EXPECT_TRUE(underlay_->GetMutableValue(regular_key, &original_in_underlay));
+  ASSERT_TRUE(original_in_underlay);
+  ASSERT_TRUE(original_in_underlay->is_dict());
+  EXPECT_TRUE(static_cast<DictionaryValue*>(original_in_underlay)->empty());
+
+  Value* modified = nullptr;
+  EXPECT_TRUE(overlay_->GetMutableValue(regular_key, &modified));
+  ASSERT_TRUE(modified);
+  ASSERT_TRUE(modified->is_dict());
+  EXPECT_EQ(*modify, *modified);
+}
+
+// Here we consider a global preference that is not overlayed.
+TEST_F(OverlayUserPrefStoreTest, GlobalPref) {
+  PrefStoreObserverMock obs;
+  overlay_->AddObserver(&obs);
+
+  const Value* value = nullptr;
+
+  // Check that underlay first value is reported.
+  underlay_->SetValue(persistent_key, std::make_unique<Value>(42),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  obs.VerifyAndResetChangedKey(persistent_key);
+
+  // Check that underlay overwriting is reported.
+  underlay_->SetValue(persistent_key, std::make_unique<Value>(43),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  obs.VerifyAndResetChangedKey(persistent_key);
+
+  // Check that we get this value from the overlay
+  EXPECT_TRUE(overlay_->GetValue(persistent_key, &value));
+  EXPECT_TRUE(base::Value(43).Equals(value));
+
+  // Check that overwriting change in overlay is reported.
+  overlay_->SetValue(persistent_key, std::make_unique<Value>(44),
+                     WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  obs.VerifyAndResetChangedKey(persistent_key);
+
+  // Check that we get this value from the overlay and the underlay.
+  EXPECT_TRUE(overlay_->GetValue(persistent_key, &value));
+  EXPECT_TRUE(base::Value(44).Equals(value));
+  EXPECT_TRUE(underlay_->GetValue(persistent_key, &value));
+  EXPECT_TRUE(base::Value(44).Equals(value));
+
+  // Check that overlay remove is reported.
+  overlay_->RemoveValue(persistent_key,
+                        WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  obs.VerifyAndResetChangedKey(persistent_key);
+
+  // Check that value was removed from overlay and underlay
+  EXPECT_FALSE(overlay_->GetValue(persistent_key, &value));
+  EXPECT_FALSE(underlay_->GetValue(persistent_key, &value));
+
+  // Check respecting of silence.
+  overlay_->SetValueSilently(persistent_key, std::make_unique<Value>(46),
+                             WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_TRUE(obs.changed_keys.empty());
+
+  overlay_->RemoveObserver(&obs);
+
+  // Check successful unsubscription.
+  underlay_->SetValue(persistent_key, std::make_unique<Value>(47),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  overlay_->SetValue(persistent_key, std::make_unique<Value>(48),
+                     WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  EXPECT_TRUE(obs.changed_keys.empty());
+}
+
+// Check that mutable values are removed correctly.
+TEST_F(OverlayUserPrefStoreTest, ClearMutableValues) {
+  // Set in overlay and underlay the same preference.
+  underlay_->SetValue(regular_key, std::make_unique<Value>(42),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  overlay_->SetValue(regular_key, std::make_unique<Value>(43),
+                     WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+  const Value* value = nullptr;
+  // Check that an overlay preference is returned.
+  EXPECT_TRUE(overlay_->GetValue(regular_key, &value));
+  EXPECT_TRUE(base::Value(43).Equals(value));
+  overlay_->ClearMutableValues();
+
+  // Check that an underlay preference is returned.
+  EXPECT_TRUE(overlay_->GetValue(regular_key, &value));
+  EXPECT_TRUE(base::Value(42).Equals(value));
+}
+
+// Check that mutable values are removed correctly when using a silent set.
+TEST_F(OverlayUserPrefStoreTest, ClearMutableValues_Silently) {
+  // Set in overlay and underlay the same preference.
+  underlay_->SetValueSilently(regular_key, std::make_unique<Value>(42),
+                              WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  overlay_->SetValueSilently(regular_key, std::make_unique<Value>(43),
+                             WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+  const Value* value = nullptr;
+  // Check that an overlay preference is returned.
+  EXPECT_TRUE(overlay_->GetValue(regular_key, &value));
+  EXPECT_TRUE(base::Value(43).Equals(value));
+  overlay_->ClearMutableValues();
+
+  // Check that an underlay preference is returned.
+  EXPECT_TRUE(overlay_->GetValue(regular_key, &value));
+  EXPECT_TRUE(base::Value(42).Equals(value));
+}
+
+TEST_F(OverlayUserPrefStoreTest, GetValues) {
+  // To check merge behavior, create underlay and overlay so each has a key the
+  // other doesn't have and they have one key in common.
+  underlay_->SetValue(persistent_key, std::make_unique<Value>(42),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  overlay_->SetValue(regular_key, std::make_unique<Value>(43),
+                     WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  underlay_->SetValue(shared_key, std::make_unique<Value>(42),
+                      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  overlay_->SetValue(shared_key, std::make_unique<Value>(43),
+                     WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+  auto values = overlay_->GetValues();
+  const Value* value = nullptr;
+  // Check that an overlay preference is returned.
+  ASSERT_TRUE(values->Get(persistent_key, &value));
+  EXPECT_TRUE(base::Value(42).Equals(value));
+
+  // Check that an underlay preference is returned.
+  ASSERT_TRUE(values->Get(regular_key, &value));
+  EXPECT_TRUE(base::Value(43).Equals(value));
+
+  // Check that the overlay is preferred.
+  ASSERT_TRUE(values->Get(shared_key, &value));
+  EXPECT_TRUE(base::Value(43).Equals(value));
+}
+
+TEST_F(OverlayUserPrefStoreTest, CommitPendingWriteWithCallback) {
+  TestCommitPendingWriteWithCallback(overlay_.get(), &scoped_task_environment_);
+}
+
+}  // namespace base
diff --git a/src/components/prefs/persistent_pref_store.cc b/src/components/prefs/persistent_pref_store.cc
new file mode 100644
index 0000000..9086c1e
--- /dev/null
+++ b/src/components/prefs/persistent_pref_store.cc
@@ -0,0 +1,31 @@
+// 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 "components/prefs/persistent_pref_store.h"
+
+#include <utility>
+
+#include "base/threading/sequenced_task_runner_handle.h"
+
+void PersistentPrefStore::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
+  // Default behavior for PersistentPrefStore implementation that don't issue
+  // disk operations: schedule the callback immediately.
+  // |synchronous_done_callback| is allowed to be invoked synchronously (and
+  // must be here since we have no other way to post it which isn't the current
+  // sequence).
+
+  if (synchronous_done_callback)
+    std::move(synchronous_done_callback).Run();
+
+  if (reply_callback) {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                     std::move(reply_callback));
+  }
+}
+
+bool PersistentPrefStore::IsInMemoryPrefStore() const {
+  return false;
+}
diff --git a/src/components/prefs/persistent_pref_store.h b/src/components/prefs/persistent_pref_store.h
new file mode 100644
index 0000000..967ccda
--- /dev/null
+++ b/src/components/prefs/persistent_pref_store.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_PERSISTENT_PREF_STORE_H_
+#define COMPONENTS_PREFS_PERSISTENT_PREF_STORE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "components/prefs/prefs_export.h"
+#include "components/prefs/writeable_pref_store.h"
+
+// This interface is complementary to the PrefStore interface, declaring
+// additional functionality that adds support for setting values and persisting
+// the data to some backing store.
+class COMPONENTS_PREFS_EXPORT PersistentPrefStore : public WriteablePrefStore {
+ public:
+  // Unique integer code for each type of error so we can report them
+  // distinctly in a histogram.
+  // NOTE: Don't change the explicit values of the enums as it will change the
+  // server's meaning of the histogram.
+  enum PrefReadError {
+    PREF_READ_ERROR_NONE = 0,
+    PREF_READ_ERROR_JSON_PARSE = 1,
+    PREF_READ_ERROR_JSON_TYPE = 2,
+    PREF_READ_ERROR_ACCESS_DENIED = 3,
+    PREF_READ_ERROR_FILE_OTHER = 4,
+    PREF_READ_ERROR_FILE_LOCKED = 5,
+    PREF_READ_ERROR_NO_FILE = 6,
+    PREF_READ_ERROR_JSON_REPEAT = 7,
+    // PREF_READ_ERROR_OTHER = 8,  // Deprecated.
+    PREF_READ_ERROR_FILE_NOT_SPECIFIED = 9,
+    // Indicates that ReadPrefs() couldn't complete synchronously and is waiting
+    // for an asynchronous task to complete first.
+    PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE = 10,
+    PREF_READ_ERROR_MAX_ENUM
+  };
+
+  class ReadErrorDelegate {
+   public:
+    virtual ~ReadErrorDelegate() {}
+
+    virtual void OnError(PrefReadError error) = 0;
+  };
+
+  // Whether the store is in a pseudo-read-only mode where changes are not
+  // actually persisted to disk.  This happens in some cases when there are
+  // read errors during startup.
+  virtual bool ReadOnly() const = 0;
+
+  // Gets the read error. Only valid if IsInitializationComplete() returns true.
+  virtual PrefReadError GetReadError() const = 0;
+
+  // Reads the preferences from disk. Notifies observers via
+  // "PrefStore::OnInitializationCompleted" when done.
+  virtual PrefReadError ReadPrefs() = 0;
+
+  // Reads the preferences from disk asynchronously. Notifies observers via
+  // "PrefStore::OnInitializationCompleted" when done. Also it fires
+  // |error_delegate| if it is not NULL and reading error has occurred.
+  // Owns |error_delegate|.
+  virtual void ReadPrefsAsync(ReadErrorDelegate* error_delegate) = 0;
+
+  // Lands pending writes to disk. |reply_callback| will be posted to the
+  // current sequence when changes have been written.
+  // |synchronous_done_callback| on the other hand will be invoked right away
+  // wherever the writes complete (could even be invoked synchronously if no
+  // writes need to occur); this is useful when the current thread cannot pump
+  // messages to observe the reply (e.g. nested loops banned on main thread
+  // during shutdown). |synchronous_done_callback| must be thread-safe.
+  virtual void CommitPendingWrite(
+      base::OnceClosure reply_callback = base::OnceClosure(),
+      base::OnceClosure synchronous_done_callback = base::OnceClosure());
+
+  // Schedule a write if there is any lossy data pending. Unlike
+  // CommitPendingWrite() this does not immediately sync to disk, instead it
+  // triggers an eventual write if there is lossy data pending and if there
+  // isn't one scheduled already.
+  virtual void SchedulePendingLossyWrites() = 0;
+
+  // It should be called only for Incognito pref store.
+  virtual void ClearMutableValues() = 0;
+
+  // Cleans preference data that may have been saved outside of the store.
+  virtual void OnStoreDeletionFromDisk() = 0;
+
+  // TODO(crbug.com/942491) Remove this after fixing the bug.
+  virtual bool IsInMemoryPrefStore() const;
+
+ protected:
+  ~PersistentPrefStore() override {}
+};
+
+#endif  // COMPONENTS_PREFS_PERSISTENT_PREF_STORE_H_
diff --git a/src/components/prefs/persistent_pref_store_unittest.cc b/src/components/prefs/persistent_pref_store_unittest.cc
new file mode 100644
index 0000000..d297858
--- /dev/null
+++ b/src/components/prefs/persistent_pref_store_unittest.cc
@@ -0,0 +1,26 @@
+// 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 "components/prefs/persistent_pref_store.h"
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/sequence_checker_impl.h"
+#include "base/test/scoped_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+void TestCommitPendingWriteWithCallback(
+    PersistentPrefStore* store,
+    base::test::ScopedTaskEnvironment* scoped_task_environment) {
+  base::RunLoop run_loop;
+  base::SequenceCheckerImpl sequence_checker;
+  store->CommitPendingWrite(base::BindOnce(
+      [](base::SequenceCheckerImpl* sequence_checker, base::RunLoop* run_loop) {
+        EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
+        run_loop->Quit();
+      },
+      base::Unretained(&sequence_checker), base::Unretained(&run_loop)));
+  scoped_task_environment->RunUntilIdle();
+  run_loop.Run();
+}
diff --git a/src/components/prefs/persistent_pref_store_unittest.h b/src/components/prefs/persistent_pref_store_unittest.h
new file mode 100644
index 0000000..ea658e1
--- /dev/null
+++ b/src/components/prefs/persistent_pref_store_unittest.h
@@ -0,0 +1,24 @@
+// 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 COMPONENTS_PREFS_PERSISTENT_PREF_STORE_UNITTEST_H_
+#define COMPONENTS_PREFS_PERSISTENT_PREF_STORE_UNITTEST_H_
+
+namespace base {
+namespace test {
+class ScopedTaskEnvironment;
+}
+}  // namespace base
+
+class PersistentPrefStore;
+
+// Calls CommitPendingWrite() on |store| with a callback. Verifies that the
+// callback runs on the appropriate sequence. |scoped_task_environment| is the
+// test's ScopedTaskEnvironment. This function is meant to be reused in the
+// tests of various PersistentPrefStore implementations.
+void TestCommitPendingWriteWithCallback(
+    PersistentPrefStore* store,
+    base::test::ScopedTaskEnvironment* scoped_task_environment);
+
+#endif  // COMPONENTS_PREFS_PERSISTENT_PREF_STORE_UNITTEST_H_
diff --git a/src/components/prefs/pref_change_registrar.cc b/src/components/prefs/pref_change_registrar.cc
new file mode 100644
index 0000000..ef0e53a
--- /dev/null
+++ b/src/components/prefs/pref_change_registrar.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/prefs/pref_change_registrar.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "components/prefs/pref_service.h"
+
+PrefChangeRegistrar::PrefChangeRegistrar() : service_(nullptr) {}
+
+PrefChangeRegistrar::~PrefChangeRegistrar() {
+  // If you see an invalid memory access in this destructor, this
+  // PrefChangeRegistrar might be subscribed to an OffTheRecordProfileImpl that
+  // has been destroyed. This should not happen any more but be warned.
+  // Feel free to contact battre@chromium.org in case this happens.
+  RemoveAll();
+}
+
+void PrefChangeRegistrar::Init(PrefService* service) {
+  DCHECK(IsEmpty() || service_ == service);
+  service_ = service;
+}
+
+void PrefChangeRegistrar::Add(const std::string& path,
+                              const base::Closure& obs) {
+  Add(path, base::Bind(&PrefChangeRegistrar::InvokeUnnamedCallback, obs));
+}
+
+void PrefChangeRegistrar::Add(const std::string& path,
+                              const NamedChangeCallback& obs) {
+  if (!service_) {
+    NOTREACHED();
+    return;
+  }
+  DCHECK(!IsObserved(path))
+      << "Already had pref, \"" << path << "\", registered.";
+
+  service_->AddPrefObserver(path, this);
+  observers_[path] = obs;
+}
+
+void PrefChangeRegistrar::Remove(const std::string& path) {
+  DCHECK(IsObserved(path));
+
+  observers_.erase(path);
+  service_->RemovePrefObserver(path, this);
+}
+
+void PrefChangeRegistrar::RemoveAll() {
+  for (ObserverMap::const_iterator it = observers_.begin();
+       it != observers_.end(); ++it) {
+    service_->RemovePrefObserver(it->first, this);
+  }
+
+  observers_.clear();
+}
+
+bool PrefChangeRegistrar::IsEmpty() const {
+  return observers_.empty();
+}
+
+bool PrefChangeRegistrar::IsObserved(const std::string& pref) {
+  return observers_.find(pref) != observers_.end();
+}
+
+bool PrefChangeRegistrar::IsManaged() {
+  for (ObserverMap::const_iterator it = observers_.begin();
+       it != observers_.end(); ++it) {
+    const PrefService::Preference* pref = service_->FindPreference(it->first);
+    if (pref && pref->IsManaged())
+      return true;
+  }
+  return false;
+}
+
+void PrefChangeRegistrar::OnPreferenceChanged(PrefService* service,
+                                              const std::string& pref) {
+  if (IsObserved(pref))
+    observers_[pref].Run(pref);
+}
+
+void PrefChangeRegistrar::InvokeUnnamedCallback(const base::Closure& callback,
+                                                const std::string& pref_name) {
+  callback.Run();
+}
+
+PrefService* PrefChangeRegistrar::prefs() {
+  return service_;
+}
+
+const PrefService* PrefChangeRegistrar::prefs() const {
+  return service_;
+}
diff --git a/src/components/prefs/pref_change_registrar.h b/src/components/prefs/pref_change_registrar.h
new file mode 100644
index 0000000..8aba1b3
--- /dev/null
+++ b/src/components/prefs/pref_change_registrar.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PREFS_PREF_CHANGE_REGISTRAR_H_
+#define COMPONENTS_PREFS_PREF_CHANGE_REGISTRAR_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "components/prefs/pref_observer.h"
+#include "components/prefs/prefs_export.h"
+
+class PrefService;
+
+// Automatically manages the registration of one or more pref change observers
+// with a PrefStore. Functions much like NotificationRegistrar, but specifically
+// manages observers of preference changes. When the Registrar is destroyed,
+// all registered observers are automatically unregistered with the PrefStore.
+class COMPONENTS_PREFS_EXPORT PrefChangeRegistrar final : public PrefObserver {
+ public:
+  // You can register this type of callback if you need to know the
+  // path of the preference that is changing.
+  using NamedChangeCallback = base::RepeatingCallback<void(const std::string&)>;
+
+  PrefChangeRegistrar();
+  ~PrefChangeRegistrar();
+
+  // Must be called before adding or removing observers. Can be called more
+  // than once as long as the value of |service| doesn't change.
+  void Init(PrefService* service);
+
+  // Adds a pref observer for the specified pref |path| and |obs| observer
+  // object. All registered observers will be automatically unregistered
+  // when the registrar's destructor is called.
+  //
+  // The second version binds a callback that will receive the path of
+  // the preference that is changing as its parameter.
+  //
+  // Only one observer may be registered per path.
+  void Add(const std::string& path, const base::Closure& obs);
+  void Add(const std::string& path, const NamedChangeCallback& obs);
+
+  // Removes the pref observer registered for |path|.
+  void Remove(const std::string& path);
+
+  // Removes all observers that have been previously added with a call to Add.
+  void RemoveAll();
+
+  // Returns true if no pref observers are registered.
+  bool IsEmpty() const;
+
+  // Check whether |pref| is in the set of preferences being observed.
+  bool IsObserved(const std::string& pref);
+
+  // Check whether any of the observed preferences has the managed bit set.
+  bool IsManaged();
+
+  // Return the PrefService for this registrar.
+  PrefService* prefs();
+  const PrefService* prefs() const;
+
+ private:
+  // PrefObserver:
+  void OnPreferenceChanged(PrefService* service,
+                           const std::string& pref_name) override;
+
+  static void InvokeUnnamedCallback(const base::Closure& callback,
+                                    const std::string& pref_name);
+
+  using ObserverMap = std::map<std::string, NamedChangeCallback>;
+
+  ObserverMap observers_;
+  PrefService* service_;
+
+  DISALLOW_COPY_AND_ASSIGN(PrefChangeRegistrar);
+};
+
+#endif  // COMPONENTS_PREFS_PREF_CHANGE_REGISTRAR_H_
diff --git a/src/components/prefs/pref_change_registrar_unittest.cc b/src/components/prefs/pref_change_registrar_unittest.cc
new file mode 100644
index 0000000..da5a334
--- /dev/null
+++ b/src/components/prefs/pref_change_registrar_unittest.cc
@@ -0,0 +1,201 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/prefs/pref_change_registrar.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "components/prefs/pref_observer.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Mock;
+using testing::Eq;
+
+namespace base {
+namespace {
+
+const char kHomePage[] = "homepage";
+const char kHomePageIsNewTabPage[] = "homepage_is_newtabpage";
+const char kApplicationLocale[] = "intl.app_locale";
+
+// A mock provider that allows us to capture pref observer changes.
+class MockPrefService : public TestingPrefServiceSimple {
+ public:
+  MockPrefService() {}
+  ~MockPrefService() override {}
+
+  MOCK_METHOD2(AddPrefObserver, void(const std::string&, PrefObserver*));
+  MOCK_METHOD2(RemovePrefObserver, void(const std::string&, PrefObserver*));
+};
+
+}  // namespace
+
+class PrefChangeRegistrarTest : public testing::Test {
+ public:
+  PrefChangeRegistrarTest() {}
+  ~PrefChangeRegistrarTest() override {}
+
+ protected:
+  void SetUp() override;
+
+  base::Closure observer() const { return base::DoNothing(); }
+
+  MockPrefService* service() const { return service_.get(); }
+
+ private:
+  std::unique_ptr<MockPrefService> service_;
+};
+
+void PrefChangeRegistrarTest::SetUp() {
+  service_.reset(new MockPrefService());
+}
+
+TEST_F(PrefChangeRegistrarTest, AddAndRemove) {
+  PrefChangeRegistrar registrar;
+  registrar.Init(service());
+
+  // Test adding.
+  EXPECT_CALL(*service(),
+              AddPrefObserver(Eq(std::string("test.pref.1")), &registrar));
+  EXPECT_CALL(*service(),
+              AddPrefObserver(Eq(std::string("test.pref.2")), &registrar));
+  registrar.Add("test.pref.1", observer());
+  registrar.Add("test.pref.2", observer());
+  EXPECT_FALSE(registrar.IsEmpty());
+
+  // Test removing.
+  Mock::VerifyAndClearExpectations(service());
+  EXPECT_CALL(*service(),
+              RemovePrefObserver(Eq(std::string("test.pref.1")), &registrar));
+  EXPECT_CALL(*service(),
+              RemovePrefObserver(Eq(std::string("test.pref.2")), &registrar));
+  registrar.Remove("test.pref.1");
+  registrar.Remove("test.pref.2");
+  EXPECT_TRUE(registrar.IsEmpty());
+
+  // Explicitly check the expectations now to make sure that the Removes
+  // worked (rather than the registrar destructor doing the work).
+  Mock::VerifyAndClearExpectations(service());
+}
+
+TEST_F(PrefChangeRegistrarTest, AutoRemove) {
+  PrefChangeRegistrar registrar;
+  registrar.Init(service());
+
+  // Setup of auto-remove.
+  EXPECT_CALL(*service(),
+              AddPrefObserver(Eq(std::string("test.pref.1")), &registrar));
+  registrar.Add("test.pref.1", observer());
+  Mock::VerifyAndClearExpectations(service());
+  EXPECT_FALSE(registrar.IsEmpty());
+
+  // Test auto-removing.
+  EXPECT_CALL(*service(),
+              RemovePrefObserver(Eq(std::string("test.pref.1")), &registrar));
+}
+
+TEST_F(PrefChangeRegistrarTest, RemoveAll) {
+  PrefChangeRegistrar registrar;
+  registrar.Init(service());
+
+  EXPECT_CALL(*service(),
+              AddPrefObserver(Eq(std::string("test.pref.1")), &registrar));
+  EXPECT_CALL(*service(),
+              AddPrefObserver(Eq(std::string("test.pref.2")), &registrar));
+  registrar.Add("test.pref.1", observer());
+  registrar.Add("test.pref.2", observer());
+  Mock::VerifyAndClearExpectations(service());
+
+  EXPECT_CALL(*service(),
+              RemovePrefObserver(Eq(std::string("test.pref.1")), &registrar));
+  EXPECT_CALL(*service(),
+              RemovePrefObserver(Eq(std::string("test.pref.2")), &registrar));
+  registrar.RemoveAll();
+  EXPECT_TRUE(registrar.IsEmpty());
+
+  // Explicitly check the expectations now to make sure that the RemoveAll
+  // worked (rather than the registrar destructor doing the work).
+  Mock::VerifyAndClearExpectations(service());
+}
+
+class ObserveSetOfPreferencesTest : public testing::Test {
+ public:
+  void SetUp() override {
+    pref_service_.reset(new TestingPrefServiceSimple);
+    PrefRegistrySimple* registry = pref_service_->registry();
+    registry->RegisterStringPref(kHomePage, "http://google.com");
+    registry->RegisterBooleanPref(kHomePageIsNewTabPage, false);
+    registry->RegisterStringPref(kApplicationLocale, std::string());
+  }
+
+  PrefChangeRegistrar* CreatePrefChangeRegistrar() {
+    PrefChangeRegistrar* pref_set = new PrefChangeRegistrar();
+    base::Closure callback = base::DoNothing();
+    pref_set->Init(pref_service_.get());
+    pref_set->Add(kHomePage, callback);
+    pref_set->Add(kHomePageIsNewTabPage, callback);
+    return pref_set;
+  }
+
+  MOCK_METHOD1(OnPreferenceChanged, void(const std::string&));
+
+  std::unique_ptr<TestingPrefServiceSimple> pref_service_;
+};
+
+TEST_F(ObserveSetOfPreferencesTest, IsObserved) {
+  std::unique_ptr<PrefChangeRegistrar> pref_set(CreatePrefChangeRegistrar());
+  EXPECT_TRUE(pref_set->IsObserved(kHomePage));
+  EXPECT_TRUE(pref_set->IsObserved(kHomePageIsNewTabPage));
+  EXPECT_FALSE(pref_set->IsObserved(kApplicationLocale));
+}
+
+TEST_F(ObserveSetOfPreferencesTest, IsManaged) {
+  std::unique_ptr<PrefChangeRegistrar> pref_set(CreatePrefChangeRegistrar());
+  EXPECT_FALSE(pref_set->IsManaged());
+  pref_service_->SetManagedPref(kHomePage,
+                                std::make_unique<Value>("http://crbug.com"));
+  EXPECT_TRUE(pref_set->IsManaged());
+  pref_service_->SetManagedPref(kHomePageIsNewTabPage,
+                                std::make_unique<Value>(true));
+  EXPECT_TRUE(pref_set->IsManaged());
+  pref_service_->RemoveManagedPref(kHomePage);
+  EXPECT_TRUE(pref_set->IsManaged());
+  pref_service_->RemoveManagedPref(kHomePageIsNewTabPage);
+  EXPECT_FALSE(pref_set->IsManaged());
+}
+
+TEST_F(ObserveSetOfPreferencesTest, Observe) {
+  using testing::_;
+  using testing::Mock;
+
+  PrefChangeRegistrar pref_set;
+  PrefChangeRegistrar::NamedChangeCallback callback =
+      base::Bind(&ObserveSetOfPreferencesTest::OnPreferenceChanged,
+                 base::Unretained(this));
+  pref_set.Init(pref_service_.get());
+  pref_set.Add(kHomePage, callback);
+  pref_set.Add(kHomePageIsNewTabPage, callback);
+
+  EXPECT_CALL(*this, OnPreferenceChanged(kHomePage));
+  pref_service_->SetUserPref(kHomePage,
+                             std::make_unique<Value>("http://crbug.com"));
+  Mock::VerifyAndClearExpectations(this);
+
+  EXPECT_CALL(*this, OnPreferenceChanged(kHomePageIsNewTabPage));
+  pref_service_->SetUserPref(kHomePageIsNewTabPage,
+                             std::make_unique<Value>(true));
+  Mock::VerifyAndClearExpectations(this);
+
+  EXPECT_CALL(*this, OnPreferenceChanged(_)).Times(0);
+  pref_service_->SetUserPref(kApplicationLocale,
+                             std::make_unique<Value>("en_US.utf8"));
+  Mock::VerifyAndClearExpectations(this);
+}
+
+}  // namespace base
diff --git a/src/components/prefs/pref_filter.h b/src/components/prefs/pref_filter.h
new file mode 100644
index 0000000..3d52136
--- /dev/null
+++ b/src/components/prefs/pref_filter.h
@@ -0,0 +1,66 @@
+// Copyright 2013 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 COMPONENTS_PREFS_PREF_FILTER_H_
+#define COMPONENTS_PREFS_PREF_FILTER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/callback_forward.h"
+#include "components/prefs/prefs_export.h"
+
+namespace base {
+class DictionaryValue;
+}  // namespace base
+
+// Filters preferences as they are loaded from disk or updated at runtime.
+// Currently supported only by JsonPrefStore.
+class COMPONENTS_PREFS_EXPORT PrefFilter {
+ public:
+  // A pair of pre-write and post-write callbacks.
+  using OnWriteCallbackPair =
+      std::pair<base::Closure, base::Callback<void(bool success)>>;
+
+  // A callback to be invoked when |prefs| have been read (and possibly
+  // pre-modified) and are now ready to be handed back to this callback's
+  // builder. |schedule_write| indicates whether a write should be immediately
+  // scheduled (typically because the |prefs| were pre-modified).
+  using PostFilterOnLoadCallback =
+      base::Callback<void(std::unique_ptr<base::DictionaryValue> prefs,
+                          bool schedule_write)>;
+
+  virtual ~PrefFilter() {}
+
+  // This method is given ownership of the |pref_store_contents| read from disk
+  // before the underlying PersistentPrefStore gets to use them. It must hand
+  // them back via |post_filter_on_load_callback|, but may modify them first.
+  // Note: This method is asynchronous, which may make calls like
+  // PersistentPrefStore::ReadPrefs() asynchronous. The owner of filtered
+  // PersistentPrefStores should handle this to make the reads look synchronous
+  // to external users (see SegregatedPrefStore::ReadPrefs() for an example).
+  virtual void FilterOnLoad(
+      const PostFilterOnLoadCallback& post_filter_on_load_callback,
+      std::unique_ptr<base::DictionaryValue> pref_store_contents) = 0;
+
+  // Receives notification when a pref store value is changed, before Observers
+  // are notified.
+  virtual void FilterUpdate(const std::string& path) = 0;
+
+  // Receives notification when the pref store is about to serialize data
+  // contained in |pref_store_contents| to a string. Modifications to
+  // |pref_store_contents| will be persisted to disk and also affect the
+  // in-memory state.
+  // If the returned callbacks are non-null, they will be registered to be
+  // invoked synchronously after the next write (from the I/O TaskRunner so they
+  // must not be bound to thread-unsafe member state).
+  virtual OnWriteCallbackPair FilterSerializeData(
+      base::DictionaryValue* pref_store_contents) = 0;
+
+  // Cleans preference data that may have been saved outside of the store.
+  virtual void OnStoreDeletionFromDisk() = 0;
+};
+
+#endif  // COMPONENTS_PREFS_PREF_FILTER_H_
diff --git a/src/components/prefs/pref_member.cc b/src/components/prefs/pref_member.cc
new file mode 100644
index 0000000..1b39bbd
--- /dev/null
+++ b/src/components/prefs/pref_member.cc
@@ -0,0 +1,216 @@
+// Copyright (c) 2012 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 "components/prefs/pref_member.h"
+
+#include <utility>
+
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/value_conversions.h"
+#include "components/prefs/pref_service.h"
+
+using base::SequencedTaskRunner;
+
+namespace subtle {
+
+PrefMemberBase::PrefMemberBase() : prefs_(nullptr), setting_value_(false) {}
+
+PrefMemberBase::~PrefMemberBase() {
+  Destroy();
+}
+
+void PrefMemberBase::Init(const std::string& pref_name,
+                          PrefService* prefs,
+                          const NamedChangeCallback& observer) {
+  observer_ = observer;
+  Init(pref_name, prefs);
+}
+
+void PrefMemberBase::Init(const std::string& pref_name, PrefService* prefs) {
+  DCHECK(prefs);
+  DCHECK(pref_name_.empty());  // Check that Init is only called once.
+  prefs_ = prefs;
+  pref_name_ = pref_name;
+  // Check that the preference is registered.
+  DCHECK(prefs_->FindPreference(pref_name_)) << pref_name << " not registered.";
+
+  // Add ourselves as a pref observer so we can keep our local value in sync.
+  prefs_->AddPrefObserver(pref_name, this);
+}
+
+void PrefMemberBase::Destroy() {
+  if (prefs_ && !pref_name_.empty()) {
+    prefs_->RemovePrefObserver(pref_name_, this);
+    prefs_ = nullptr;
+  }
+}
+
+void PrefMemberBase::MoveToSequence(
+    scoped_refptr<SequencedTaskRunner> task_runner) {
+  VerifyValuePrefName();
+  // Load the value from preferences if it hasn't been loaded so far.
+  if (!internal())
+    UpdateValueFromPref(base::Closure());
+  internal()->MoveToSequence(std::move(task_runner));
+}
+
+void PrefMemberBase::OnPreferenceChanged(PrefService* service,
+                                         const std::string& pref_name) {
+  VerifyValuePrefName();
+  UpdateValueFromPref((!setting_value_ && !observer_.is_null())
+                          ? base::Bind(observer_, pref_name)
+                          : base::Closure());
+}
+
+void PrefMemberBase::UpdateValueFromPref(const base::Closure& callback) const {
+  VerifyValuePrefName();
+  const PrefService::Preference* pref = prefs_->FindPreference(pref_name_);
+  DCHECK(pref);
+  if (!internal())
+    CreateInternal();
+  internal()->UpdateValue(pref->GetValue()->DeepCopy(), pref->IsManaged(),
+                          pref->IsUserModifiable(), callback);
+}
+
+void PrefMemberBase::VerifyPref() const {
+  VerifyValuePrefName();
+  if (!internal())
+    UpdateValueFromPref(base::Closure());
+}
+
+void PrefMemberBase::InvokeUnnamedCallback(const base::Closure& callback,
+                                           const std::string& pref_name) {
+  callback.Run();
+}
+
+PrefMemberBase::Internal::Internal()
+    : owning_task_runner_(base::SequencedTaskRunnerHandle::Get()),
+      is_managed_(false),
+      is_user_modifiable_(false) {}
+
+PrefMemberBase::Internal::~Internal() {}
+
+bool PrefMemberBase::Internal::IsOnCorrectSequence() const {
+  return owning_task_runner_->RunsTasksInCurrentSequence();
+}
+
+void PrefMemberBase::Internal::UpdateValue(base::Value* v,
+                                           bool is_managed,
+                                           bool is_user_modifiable,
+                                           base::OnceClosure callback) const {
+  std::unique_ptr<base::Value> value(v);
+  base::ScopedClosureRunner closure_runner(std::move(callback));
+  if (IsOnCorrectSequence()) {
+    bool rv = UpdateValueInternal(*value);
+    DCHECK(rv);
+    is_managed_ = is_managed;
+    is_user_modifiable_ = is_user_modifiable;
+  } else {
+    bool may_run = owning_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&PrefMemberBase::Internal::UpdateValue, this,
+                       value.release(), is_managed, is_user_modifiable,
+                       closure_runner.Release()));
+    DCHECK(may_run);
+  }
+}
+
+void PrefMemberBase::Internal::MoveToSequence(
+    scoped_refptr<SequencedTaskRunner> task_runner) {
+  CheckOnCorrectSequence();
+  owning_task_runner_ = std::move(task_runner);
+}
+
+bool PrefMemberVectorStringUpdate(const base::Value& value,
+                                  std::vector<std::string>* string_vector) {
+  if (!value.is_list())
+    return false;
+  const base::ListValue* list = static_cast<const base::ListValue*>(&value);
+
+  std::vector<std::string> local_vector;
+  for (auto it = list->begin(); it != list->end(); ++it) {
+    std::string string_value;
+    if (!it->GetAsString(&string_value))
+      return false;
+
+    local_vector.push_back(string_value);
+  }
+
+  string_vector->swap(local_vector);
+  return true;
+}
+
+}  // namespace subtle
+
+template <>
+void PrefMember<bool>::UpdatePref(const bool& value) {
+  prefs()->SetBoolean(pref_name(), value);
+}
+
+template <>
+bool PrefMember<bool>::Internal::UpdateValueInternal(
+    const base::Value& value) const {
+  return value.GetAsBoolean(&value_);
+}
+
+template <>
+void PrefMember<int>::UpdatePref(const int& value) {
+  prefs()->SetInteger(pref_name(), value);
+}
+
+template <>
+bool PrefMember<int>::Internal::UpdateValueInternal(
+    const base::Value& value) const {
+  return value.GetAsInteger(&value_);
+}
+
+template <>
+void PrefMember<double>::UpdatePref(const double& value) {
+  prefs()->SetDouble(pref_name(), value);
+}
+
+template <>
+bool PrefMember<double>::Internal::UpdateValueInternal(
+    const base::Value& value) const {
+  return value.GetAsDouble(&value_);
+}
+
+template <>
+void PrefMember<std::string>::UpdatePref(const std::string& value) {
+  prefs()->SetString(pref_name(), value);
+}
+
+template <>
+bool PrefMember<std::string>::Internal::UpdateValueInternal(
+    const base::Value& value) const {
+  return value.GetAsString(&value_);
+}
+
+template <>
+void PrefMember<base::FilePath>::UpdatePref(const base::FilePath& value) {
+  prefs()->SetFilePath(pref_name(), value);
+}
+
+template <>
+bool PrefMember<base::FilePath>::Internal::UpdateValueInternal(
+    const base::Value& value) const {
+  return base::GetValueAsFilePath(value, &value_);
+}
+
+template <>
+void PrefMember<std::vector<std::string> >::UpdatePref(
+    const std::vector<std::string>& value) {
+  base::ListValue list_value;
+  list_value.AppendStrings(value);
+  prefs()->Set(pref_name(), list_value);
+}
+
+template <>
+bool PrefMember<std::vector<std::string> >::Internal::UpdateValueInternal(
+    const base::Value& value) const {
+  return subtle::PrefMemberVectorStringUpdate(value, &value_);
+}
diff --git a/src/components/prefs/pref_member.h b/src/components/prefs/pref_member.h
new file mode 100644
index 0000000..c07f9ce
--- /dev/null
+++ b/src/components/prefs/pref_member.h
@@ -0,0 +1,344 @@
+// Copyright (c) 2012 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.
+//
+// A helper class that stays in sync with a preference (bool, int, real,
+// string or filepath).  For example:
+//
+// class MyClass {
+//  public:
+//   MyClass(PrefService* prefs) {
+//     my_string_.Init(prefs::kHomePage, prefs);
+//   }
+//  private:
+//   StringPrefMember my_string_;
+// };
+//
+// my_string_ should stay in sync with the prefs::kHomePage pref and will
+// update if either the pref changes or if my_string_.SetValue is called.
+//
+// An optional observer can be passed into the Init method which can be used to
+// notify MyClass of changes. Note that if you use SetValue(), the observer
+// will not be notified.
+
+#ifndef COMPONENTS_PREFS_PREF_MEMBER_H_
+#define COMPONENTS_PREFS_PREF_MEMBER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "base/values.h"
+#include "components/prefs/pref_observer.h"
+#include "components/prefs/prefs_export.h"
+
+class PrefService;
+
+namespace subtle {
+
+class COMPONENTS_PREFS_EXPORT PrefMemberBase : public PrefObserver {
+ public:
+  // Type of callback you can register if you need to know the name of
+  // the pref that is changing.
+  typedef base::Callback<void(const std::string&)> NamedChangeCallback;
+
+  PrefService* prefs() { return prefs_; }
+  const PrefService* prefs() const { return prefs_; }
+
+ protected:
+  class COMPONENTS_PREFS_EXPORT Internal
+      : public base::RefCountedThreadSafe<Internal> {
+   public:
+    Internal();
+
+    // Update the value, either by calling |UpdateValueInternal| directly
+    // or by dispatching to the right sequence.
+    // Takes ownership of |value|.
+    void UpdateValue(base::Value* value,
+                     bool is_managed,
+                     bool is_user_modifiable,
+                     base::OnceClosure callback) const;
+
+    void MoveToSequence(scoped_refptr<base::SequencedTaskRunner> task_runner);
+
+    // See PrefMember<> for description.
+    bool IsManaged() const { return is_managed_; }
+
+    bool IsUserModifiable() const { return is_user_modifiable_; }
+
+   protected:
+    friend class base::RefCountedThreadSafe<Internal>;
+    virtual ~Internal();
+
+    void CheckOnCorrectSequence() const { DCHECK(IsOnCorrectSequence()); }
+
+   private:
+    // This method actually updates the value. It should only be called from
+    // the sequence the PrefMember is on.
+    virtual bool UpdateValueInternal(const base::Value& value) const = 0;
+
+    bool IsOnCorrectSequence() const;
+
+    scoped_refptr<base::SequencedTaskRunner> owning_task_runner_;
+    mutable bool is_managed_;
+    mutable bool is_user_modifiable_;
+
+    DISALLOW_COPY_AND_ASSIGN(Internal);
+  };
+
+  PrefMemberBase();
+  virtual ~PrefMemberBase();
+
+  // See PrefMember<> for description.
+  void Init(const std::string& pref_name,
+            PrefService* prefs,
+            const NamedChangeCallback& observer);
+  void Init(const std::string& pref_name, PrefService* prefs);
+
+  virtual void CreateInternal() const = 0;
+
+  // See PrefMember<> for description.
+  void Destroy();
+
+  void MoveToSequence(scoped_refptr<base::SequencedTaskRunner> task_runner);
+
+  // PrefObserver
+  void OnPreferenceChanged(PrefService* service,
+                           const std::string& pref_name) override;
+
+  void VerifyValuePrefName() const { DCHECK(!pref_name_.empty()); }
+
+  // This method is used to do the actual sync with the preference.
+  // Note: it is logically const, because it doesn't modify the state
+  // seen by the outside world. It is just doing a lazy load behind the scenes.
+  void UpdateValueFromPref(const base::Closure& callback) const;
+
+  // Verifies the preference name, and lazily loads the preference value if
+  // it hasn't been loaded yet.
+  void VerifyPref() const;
+
+  const std::string& pref_name() const { return pref_name_; }
+
+  virtual Internal* internal() const = 0;
+
+  // Used to allow registering plain base::Closure callbacks.
+  static void InvokeUnnamedCallback(const base::Closure& callback,
+                                    const std::string& pref_name);
+
+ private:
+  // Ordered the members to compact the class instance.
+  std::string pref_name_;
+  NamedChangeCallback observer_;
+  PrefService* prefs_;
+
+ protected:
+  bool setting_value_;
+};
+
+// This function implements StringListPrefMember::UpdateValue().
+// It is exposed here for testing purposes.
+bool COMPONENTS_PREFS_EXPORT
+PrefMemberVectorStringUpdate(const base::Value& value,
+                             std::vector<std::string>* string_vector);
+
+}  // namespace subtle
+
+template <typename ValueType>
+class PrefMember : public subtle::PrefMemberBase {
+ public:
+  // Defer initialization to an Init method so it's easy to make this class be
+  // a member variable.
+  PrefMember() {}
+  virtual ~PrefMember() {}
+
+  // Do the actual initialization of the class.  Use the two-parameter
+  // version if you don't want any notifications of changes.  This
+  // method should only be called on the UI thread.
+  void Init(const std::string& pref_name,
+            PrefService* prefs,
+            const NamedChangeCallback& observer) {
+    subtle::PrefMemberBase::Init(pref_name, prefs, observer);
+  }
+  void Init(const std::string& pref_name,
+            PrefService* prefs,
+            const base::Closure& observer) {
+    subtle::PrefMemberBase::Init(
+        pref_name, prefs,
+        base::Bind(&PrefMemberBase::InvokeUnnamedCallback, observer));
+  }
+  void Init(const std::string& pref_name, PrefService* prefs) {
+    subtle::PrefMemberBase::Init(pref_name, prefs);
+  }
+
+  // Unsubscribes the PrefMember from the PrefService. After calling this
+  // function, the PrefMember may not be used any more on the UI thread.
+  // Assuming |MoveToSequence| was previously called, |GetValue|, |IsManaged|,
+  // and |IsUserModifiable| can still be called from the other sequence but
+  // the results will no longer update from the PrefService.
+  // This method should only be called on the UI thread.
+  void Destroy() { subtle::PrefMemberBase::Destroy(); }
+
+  // Moves the PrefMember to another sequence, allowing read accesses from
+  // there. Changes from the PrefService will be propagated asynchronously
+  // via PostTask.
+  // This method should only be used from the sequence the PrefMember is
+  // currently on, which is the UI thread by default.
+  void MoveToSequence(scoped_refptr<base::SequencedTaskRunner> task_runner) {
+    subtle::PrefMemberBase::MoveToSequence(task_runner);
+  }
+
+  // Check whether the pref is managed, i.e. controlled externally through
+  // enterprise configuration management (e.g. windows group policy). Returns
+  // false for unknown prefs.
+  // This method should only be used from the sequence the PrefMember is
+  // currently on, which is the UI thread unless changed by |MoveToSequence|.
+  bool IsManaged() const {
+    VerifyPref();
+    return internal_->IsManaged();
+  }
+
+  // Checks whether the pref can be modified by the user. This returns false
+  // when the pref is managed by a policy or an extension, and when a command
+  // line flag overrides the pref.
+  // This method should only be used from the sequence the PrefMember is
+  // currently on, which is the UI thread unless changed by |MoveToSequence|.
+  bool IsUserModifiable() const {
+    VerifyPref();
+    return internal_->IsUserModifiable();
+  }
+
+  // Retrieve the value of the member variable.
+  // This method should only be used from the sequence the PrefMember is
+  // currently on, which is the UI thread unless changed by |MoveToSequence|.
+  ValueType GetValue() const {
+    VerifyPref();
+    return internal_->value();
+  }
+
+  // Provided as a convenience.
+  ValueType operator*() const { return GetValue(); }
+
+  // Set the value of the member variable.
+  // This method should only be called on the UI thread.
+  void SetValue(const ValueType& value) {
+    VerifyValuePrefName();
+    setting_value_ = true;
+    UpdatePref(value);
+    setting_value_ = false;
+  }
+
+  // Returns the pref name.
+  const std::string& GetPrefName() const { return pref_name(); }
+
+ private:
+  class Internal : public subtle::PrefMemberBase::Internal {
+   public:
+    Internal() : value_(ValueType()) {}
+
+    ValueType value() {
+      CheckOnCorrectSequence();
+      return value_;
+    }
+
+   protected:
+    ~Internal() override {}
+
+    COMPONENTS_PREFS_EXPORT bool UpdateValueInternal(
+        const base::Value& value) const override;
+
+    // We cache the value of the pref so we don't have to keep walking the pref
+    // tree.
+    mutable ValueType value_;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Internal);
+  };
+
+  Internal* internal() const override { return internal_.get(); }
+  void CreateInternal() const override { internal_ = new Internal(); }
+
+  // This method is used to do the actual sync with pref of the specified type.
+  void COMPONENTS_PREFS_EXPORT UpdatePref(const ValueType& value);
+
+  mutable scoped_refptr<Internal> internal_;
+
+  DISALLOW_COPY_AND_ASSIGN(PrefMember);
+};
+
+// Declaration of template specialization need to be repeated here
+// specifically for each specialization (rather than just once above)
+// or at least one of our compilers won't be happy in all cases.
+// Specifically, it was failing on ChromeOS with a complaint about
+// PrefMember<FilePath>::UpdateValueInternal not being defined when
+// built in a chroot with the following parameters:
+//
+// FEATURES="noclean nostrip" USE="-chrome_debug -chrome_remoting
+// -chrome_internal -chrome_pdf component_build"
+// ~/trunk/goma/goma-wrapper cros_chrome_make --board=${BOARD}
+// --install --runhooks
+
+template <>
+COMPONENTS_PREFS_EXPORT void PrefMember<bool>::UpdatePref(const bool& value);
+
+template <>
+COMPONENTS_PREFS_EXPORT bool PrefMember<bool>::Internal::UpdateValueInternal(
+    const base::Value& value) const;
+
+template <>
+COMPONENTS_PREFS_EXPORT void PrefMember<int>::UpdatePref(const int& value);
+
+template <>
+COMPONENTS_PREFS_EXPORT bool PrefMember<int>::Internal::UpdateValueInternal(
+    const base::Value& value) const;
+
+template <>
+
+COMPONENTS_PREFS_EXPORT void PrefMember<double>::UpdatePref(
+    const double& value);
+
+template <>
+COMPONENTS_PREFS_EXPORT bool PrefMember<double>::Internal::UpdateValueInternal(
+    const base::Value& value) const;
+
+template <>
+COMPONENTS_PREFS_EXPORT void PrefMember<std::string>::UpdatePref(
+    const std::string& value);
+
+template <>
+COMPONENTS_PREFS_EXPORT bool
+PrefMember<std::string>::Internal::UpdateValueInternal(
+    const base::Value& value) const;
+
+template <>
+COMPONENTS_PREFS_EXPORT void PrefMember<base::FilePath>::UpdatePref(
+    const base::FilePath& value);
+
+template <>
+COMPONENTS_PREFS_EXPORT bool
+PrefMember<base::FilePath>::Internal::UpdateValueInternal(
+    const base::Value& value) const;
+
+template <>
+COMPONENTS_PREFS_EXPORT void PrefMember<std::vector<std::string>>::UpdatePref(
+    const std::vector<std::string>& value);
+
+template <>
+COMPONENTS_PREFS_EXPORT bool
+PrefMember<std::vector<std::string>>::Internal::UpdateValueInternal(
+    const base::Value& value) const;
+
+typedef PrefMember<bool> BooleanPrefMember;
+typedef PrefMember<int> IntegerPrefMember;
+typedef PrefMember<double> DoublePrefMember;
+typedef PrefMember<std::string> StringPrefMember;
+typedef PrefMember<base::FilePath> FilePathPrefMember;
+// This preference member is expensive for large string arrays.
+typedef PrefMember<std::vector<std::string>> StringListPrefMember;
+
+#endif  // COMPONENTS_PREFS_PREF_MEMBER_H_
diff --git a/src/components/prefs/pref_member_unittest.cc b/src/components/prefs/pref_member_unittest.cc
new file mode 100644
index 0000000..6cf3b6e
--- /dev/null
+++ b/src/components/prefs/pref_member_unittest.cc
@@ -0,0 +1,319 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/prefs/pref_member.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task/post_task.h"
+#include "base/test/scoped_task_environment.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kBoolPref[] = "bool";
+const char kIntPref[] = "int";
+const char kDoublePref[] = "double";
+const char kStringPref[] = "string";
+const char kStringListPref[] = "string_list";
+
+void RegisterTestPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterBooleanPref(kBoolPref, false);
+  registry->RegisterIntegerPref(kIntPref, 0);
+  registry->RegisterDoublePref(kDoublePref, 0.0);
+  registry->RegisterStringPref(kStringPref, "default");
+  registry->RegisterListPref(kStringListPref);
+}
+
+class GetPrefValueHelper
+    : public base::RefCountedThreadSafe<GetPrefValueHelper> {
+ public:
+  GetPrefValueHelper()
+      : value_(false), task_runner_(base::CreateSequencedTaskRunner({})) {}
+
+  void Init(const std::string& pref_name, PrefService* prefs) {
+    pref_.Init(pref_name, prefs);
+    pref_.MoveToSequence(task_runner_);
+  }
+
+  void Destroy() { pref_.Destroy(); }
+
+  void FetchValue() {
+    base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                              base::WaitableEvent::InitialState::NOT_SIGNALED);
+    ASSERT_TRUE(task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&GetPrefValueHelper::GetPrefValue, this, &event)));
+    event.Wait();
+  }
+
+  bool value() { return value_; }
+
+ private:
+  friend class base::RefCountedThreadSafe<GetPrefValueHelper>;
+  ~GetPrefValueHelper() {}
+
+  void GetPrefValue(base::WaitableEvent* event) {
+    value_ = pref_.GetValue();
+    event->Signal();
+  }
+
+  BooleanPrefMember pref_;
+  bool value_;
+
+  // The sequence |pref_| runs on.
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+};
+
+class PrefMemberTestClass {
+ public:
+  explicit PrefMemberTestClass(PrefService* prefs)
+      : observe_cnt_(0), prefs_(prefs) {
+    str_.Init(kStringPref, prefs,
+              base::Bind(&PrefMemberTestClass::OnPreferenceChanged,
+                         base::Unretained(this)));
+  }
+
+  void OnPreferenceChanged(const std::string& pref_name) {
+    EXPECT_EQ(pref_name, kStringPref);
+    EXPECT_EQ(str_.GetValue(), prefs_->GetString(kStringPref));
+    ++observe_cnt_;
+  }
+
+  StringPrefMember str_;
+  int observe_cnt_;
+
+ private:
+  PrefService* prefs_;
+};
+
+}  // anonymous namespace
+
+class PrefMemberTest : public testing::Test {
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+};
+
+TEST_F(PrefMemberTest, BasicGetAndSet) {
+  TestingPrefServiceSimple prefs;
+  RegisterTestPrefs(prefs.registry());
+
+  // Test bool
+  BooleanPrefMember boolean;
+  boolean.Init(kBoolPref, &prefs);
+
+  // Check the defaults
+  EXPECT_FALSE(prefs.GetBoolean(kBoolPref));
+  EXPECT_FALSE(boolean.GetValue());
+  EXPECT_FALSE(*boolean);
+
+  // Try changing through the member variable.
+  boolean.SetValue(true);
+  EXPECT_TRUE(boolean.GetValue());
+  EXPECT_TRUE(prefs.GetBoolean(kBoolPref));
+  EXPECT_TRUE(*boolean);
+
+  // Try changing back through the pref.
+  prefs.SetBoolean(kBoolPref, false);
+  EXPECT_FALSE(prefs.GetBoolean(kBoolPref));
+  EXPECT_FALSE(boolean.GetValue());
+  EXPECT_FALSE(*boolean);
+
+  // Test int
+  IntegerPrefMember integer;
+  integer.Init(kIntPref, &prefs);
+
+  // Check the defaults
+  EXPECT_EQ(0, prefs.GetInteger(kIntPref));
+  EXPECT_EQ(0, integer.GetValue());
+  EXPECT_EQ(0, *integer);
+
+  // Try changing through the member variable.
+  integer.SetValue(5);
+  EXPECT_EQ(5, integer.GetValue());
+  EXPECT_EQ(5, prefs.GetInteger(kIntPref));
+  EXPECT_EQ(5, *integer);
+
+  // Try changing back through the pref.
+  prefs.SetInteger(kIntPref, 2);
+  EXPECT_EQ(2, prefs.GetInteger(kIntPref));
+  EXPECT_EQ(2, integer.GetValue());
+  EXPECT_EQ(2, *integer);
+
+  // Test double
+  DoublePrefMember double_member;
+  double_member.Init(kDoublePref, &prefs);
+
+  // Check the defaults
+  EXPECT_EQ(0.0, prefs.GetDouble(kDoublePref));
+  EXPECT_EQ(0.0, double_member.GetValue());
+  EXPECT_EQ(0.0, *double_member);
+
+  // Try changing through the member variable.
+  double_member.SetValue(1.0);
+  EXPECT_EQ(1.0, double_member.GetValue());
+  EXPECT_EQ(1.0, prefs.GetDouble(kDoublePref));
+  EXPECT_EQ(1.0, *double_member);
+
+  // Try changing back through the pref.
+  prefs.SetDouble(kDoublePref, 3.0);
+  EXPECT_EQ(3.0, prefs.GetDouble(kDoublePref));
+  EXPECT_EQ(3.0, double_member.GetValue());
+  EXPECT_EQ(3.0, *double_member);
+
+  // Test string
+  StringPrefMember string;
+  string.Init(kStringPref, &prefs);
+
+  // Check the defaults
+  EXPECT_EQ("default", prefs.GetString(kStringPref));
+  EXPECT_EQ("default", string.GetValue());
+  EXPECT_EQ("default", *string);
+
+  // Try changing through the member variable.
+  string.SetValue("foo");
+  EXPECT_EQ("foo", string.GetValue());
+  EXPECT_EQ("foo", prefs.GetString(kStringPref));
+  EXPECT_EQ("foo", *string);
+
+  // Try changing back through the pref.
+  prefs.SetString(kStringPref, "bar");
+  EXPECT_EQ("bar", prefs.GetString(kStringPref));
+  EXPECT_EQ("bar", string.GetValue());
+  EXPECT_EQ("bar", *string);
+
+  // Test string list
+  base::ListValue expected_list;
+  std::vector<std::string> expected_vector;
+  StringListPrefMember string_list;
+  string_list.Init(kStringListPref, &prefs);
+
+  // Check the defaults
+  EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref)));
+  EXPECT_EQ(expected_vector, string_list.GetValue());
+  EXPECT_EQ(expected_vector, *string_list);
+
+  // Try changing through the pref member.
+  expected_list.AppendString("foo");
+  expected_vector.push_back("foo");
+  string_list.SetValue(expected_vector);
+
+  EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref)));
+  EXPECT_EQ(expected_vector, string_list.GetValue());
+  EXPECT_EQ(expected_vector, *string_list);
+
+  // Try adding through the pref.
+  expected_list.AppendString("bar");
+  expected_vector.push_back("bar");
+  prefs.Set(kStringListPref, expected_list);
+
+  EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref)));
+  EXPECT_EQ(expected_vector, string_list.GetValue());
+  EXPECT_EQ(expected_vector, *string_list);
+
+  // Try removing through the pref.
+  expected_list.Remove(0, nullptr);
+  expected_vector.erase(expected_vector.begin());
+  prefs.Set(kStringListPref, expected_list);
+
+  EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref)));
+  EXPECT_EQ(expected_vector, string_list.GetValue());
+  EXPECT_EQ(expected_vector, *string_list);
+}
+
+TEST_F(PrefMemberTest, InvalidList) {
+  // Set the vector to an initial good value.
+  std::vector<std::string> expected_vector;
+  expected_vector.push_back("foo");
+
+  // Try to add a valid list first.
+  base::ListValue list;
+  list.AppendString("foo");
+  std::vector<std::string> vector;
+  EXPECT_TRUE(subtle::PrefMemberVectorStringUpdate(list, &vector));
+  EXPECT_EQ(expected_vector, vector);
+
+  // Now try to add an invalid list.  |vector| should not be changed.
+  list.AppendInteger(0);
+  EXPECT_FALSE(subtle::PrefMemberVectorStringUpdate(list, &vector));
+  EXPECT_EQ(expected_vector, vector);
+}
+
+TEST_F(PrefMemberTest, TwoPrefs) {
+  // Make sure two DoublePrefMembers stay in sync.
+  TestingPrefServiceSimple prefs;
+  RegisterTestPrefs(prefs.registry());
+
+  DoublePrefMember pref1;
+  pref1.Init(kDoublePref, &prefs);
+  DoublePrefMember pref2;
+  pref2.Init(kDoublePref, &prefs);
+
+  pref1.SetValue(2.3);
+  EXPECT_EQ(2.3, *pref2);
+
+  pref2.SetValue(3.5);
+  EXPECT_EQ(3.5, *pref1);
+
+  prefs.SetDouble(kDoublePref, 4.2);
+  EXPECT_EQ(4.2, *pref1);
+  EXPECT_EQ(4.2, *pref2);
+}
+
+TEST_F(PrefMemberTest, Observer) {
+  TestingPrefServiceSimple prefs;
+  RegisterTestPrefs(prefs.registry());
+
+  PrefMemberTestClass test_obj(&prefs);
+  EXPECT_EQ("default", *test_obj.str_);
+
+  // Calling SetValue should not fire the observer.
+  test_obj.str_.SetValue("hello");
+  EXPECT_EQ(0, test_obj.observe_cnt_);
+  EXPECT_EQ("hello", prefs.GetString(kStringPref));
+
+  // Changing the pref does fire the observer.
+  prefs.SetString(kStringPref, "world");
+  EXPECT_EQ(1, test_obj.observe_cnt_);
+  EXPECT_EQ("world", *(test_obj.str_));
+
+  // Not changing the value should not fire the observer.
+  prefs.SetString(kStringPref, "world");
+  EXPECT_EQ(1, test_obj.observe_cnt_);
+  EXPECT_EQ("world", *(test_obj.str_));
+
+  prefs.SetString(kStringPref, "hello");
+  EXPECT_EQ(2, test_obj.observe_cnt_);
+  EXPECT_EQ("hello", prefs.GetString(kStringPref));
+}
+
+TEST_F(PrefMemberTest, NoInit) {
+  // Make sure not calling Init on a PrefMember doesn't cause problems.
+  IntegerPrefMember pref;
+}
+
+TEST_F(PrefMemberTest, MoveToSequence) {
+  TestingPrefServiceSimple prefs;
+  scoped_refptr<GetPrefValueHelper> helper(new GetPrefValueHelper());
+  RegisterTestPrefs(prefs.registry());
+  helper->Init(kBoolPref, &prefs);
+
+  helper->FetchValue();
+  EXPECT_FALSE(helper->value());
+
+  prefs.SetBoolean(kBoolPref, true);
+
+  helper->FetchValue();
+  EXPECT_TRUE(helper->value());
+
+  helper->Destroy();
+
+  helper->FetchValue();
+  EXPECT_TRUE(helper->value());
+}
diff --git a/src/components/prefs/pref_notifier.h b/src/components/prefs/pref_notifier.h
new file mode 100644
index 0000000..2abc213
--- /dev/null
+++ b/src/components/prefs/pref_notifier.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PREFS_PREF_NOTIFIER_H_
+#define COMPONENTS_PREFS_PREF_NOTIFIER_H_
+
+#include <string>
+
+// Delegate interface used by PrefValueStore to notify its owner about changes
+// to the preference values.
+// TODO(mnissler, danno): Move this declaration to pref_value_store.h once we've
+// cleaned up all public uses of this interface.
+class PrefNotifier {
+ public:
+  virtual ~PrefNotifier() {}
+
+  // Sends out a change notification for the preference identified by
+  // |pref_name|.
+  virtual void OnPreferenceChanged(const std::string& pref_name) = 0;
+
+  // Broadcasts the intialization completed notification.
+  virtual void OnInitializationCompleted(bool succeeded) = 0;
+};
+
+#endif  // COMPONENTS_PREFS_PREF_NOTIFIER_H_
diff --git a/src/components/prefs/pref_notifier_impl.cc b/src/components/prefs/pref_notifier_impl.cc
new file mode 100644
index 0000000..1fdca89
--- /dev/null
+++ b/src/components/prefs/pref_notifier_impl.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 2012 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 "components/prefs/pref_notifier_impl.h"
+
+#include "base/debug/dump_without_crashing.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "components/prefs/pref_service.h"
+
+PrefNotifierImpl::PrefNotifierImpl() : pref_service_(nullptr) {}
+
+PrefNotifierImpl::PrefNotifierImpl(PrefService* service)
+    : pref_service_(service) {}
+
+PrefNotifierImpl::~PrefNotifierImpl() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // Verify that there are no pref observers when we shut down.
+  for (const auto& observer_list : pref_observers_) {
+    if (observer_list.second->begin() != observer_list.second->end()) {
+      // Generally, there should not be any subscribers left when the profile
+      // is destroyed because a) those may indicate that the subscriber class
+      // maintains an active pointer to the profile that might be used for
+      // accessing a destroyed profile and b) those subscribers will try to
+      // unsubscribe from a PrefService that has been destroyed with the
+      // profile.
+      // There is one exception that is safe: Static objects that are leaked
+      // on process termination, if these objects just subscribe to preferences
+      // and never access the profile after destruction. As these objects are
+      // leaked on termination, it is guaranteed that they don't attempt to
+      // unsubscribe.
+      const auto& pref_name = observer_list.first;
+      LOG(WARNING) << "Pref observer for " << pref_name
+                   << " found at shutdown.";
+
+      // TODO(crbug.com/942491, 946668, 945772) The following code collects
+      // stacktraces that show how the profile is destroyed that owns
+      // preferences which are known to have subscriptions outliving the
+      // profile.
+      if (
+          // For GlobalMenuBarX11, crbug.com/946668
+          pref_name == "bookmark_bar.show_on_all_tabs" ||
+          // For BrowserWindowPropertyManager, crbug.com/942491
+          pref_name == "profile.icon_version" ||
+          // For BrowserWindowDefaultTouchBar, crbug.com/945772
+          pref_name == "default_search_provider_data.template_url_data") {
+        base::debug::DumpWithoutCrashing();
+      }
+    }
+  }
+
+  // Same for initialization observers.
+  if (!init_observers_.empty())
+    LOG(WARNING) << "Init observer found at shutdown.";
+
+  pref_observers_.clear();
+  init_observers_.clear();
+}
+
+void PrefNotifierImpl::AddPrefObserver(const std::string& path,
+                                       PrefObserver* obs) {
+  // Get the pref observer list associated with the path.
+  PrefObserverList* observer_list = nullptr;
+  auto observer_iterator = pref_observers_.find(path);
+  if (observer_iterator == pref_observers_.end()) {
+    observer_list = new PrefObserverList;
+    pref_observers_[path] = base::WrapUnique(observer_list);
+  } else {
+    observer_list = observer_iterator->second.get();
+  }
+
+  // Add the pref observer. ObserverList will DCHECK if it already is
+  // in the list.
+  observer_list->AddObserver(obs);
+}
+
+void PrefNotifierImpl::RemovePrefObserver(const std::string& path,
+                                          PrefObserver* obs) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto observer_iterator = pref_observers_.find(path);
+  if (observer_iterator == pref_observers_.end()) {
+    return;
+  }
+
+  PrefObserverList* observer_list = observer_iterator->second.get();
+  observer_list->RemoveObserver(obs);
+}
+
+void PrefNotifierImpl::AddPrefObserverAllPrefs(PrefObserver* observer) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  all_prefs_pref_observers_.AddObserver(observer);
+}
+
+void PrefNotifierImpl::RemovePrefObserverAllPrefs(PrefObserver* observer) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  all_prefs_pref_observers_.RemoveObserver(observer);
+}
+
+void PrefNotifierImpl::AddInitObserver(base::OnceCallback<void(bool)> obs) {
+  init_observers_.push_back(std::move(obs));
+}
+
+void PrefNotifierImpl::OnPreferenceChanged(const std::string& path) {
+  FireObservers(path);
+}
+
+void PrefNotifierImpl::OnInitializationCompleted(bool succeeded) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // We must move init_observers_ to a local variable before we run
+  // observers, or we can end up in this method re-entrantly before
+  // clearing the observers list.
+  PrefInitObserverList observers;
+  std::swap(observers, init_observers_);
+
+  for (auto& observer : observers)
+    std::move(observer).Run(succeeded);
+}
+
+void PrefNotifierImpl::FireObservers(const std::string& path) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // Only send notifications for registered preferences.
+  if (!pref_service_->FindPreference(path))
+    return;
+
+  // Fire observers for any preference change.
+  for (auto& observer : all_prefs_pref_observers_)
+    observer.OnPreferenceChanged(pref_service_, path);
+
+  auto observer_iterator = pref_observers_.find(path);
+  if (observer_iterator == pref_observers_.end())
+    return;
+
+  for (PrefObserver& observer : *(observer_iterator->second))
+    observer.OnPreferenceChanged(pref_service_, path);
+}
+
+void PrefNotifierImpl::SetPrefService(PrefService* pref_service) {
+  DCHECK(pref_service_ == nullptr);
+  pref_service_ = pref_service;
+}
diff --git a/src/components/prefs/pref_notifier_impl.h b/src/components/prefs/pref_notifier_impl.h
new file mode 100644
index 0000000..c550103
--- /dev/null
+++ b/src/components/prefs/pref_notifier_impl.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PREFS_PREF_NOTIFIER_IMPL_H_
+#define COMPONENTS_PREFS_PREF_NOTIFIER_IMPL_H_
+
+#include <list>
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "components/prefs/pref_notifier.h"
+#include "components/prefs/pref_observer.h"
+#include "components/prefs/prefs_export.h"
+
+class PrefService;
+
+// The PrefNotifier implementation used by the PrefService.
+class COMPONENTS_PREFS_EXPORT PrefNotifierImpl : public PrefNotifier {
+ public:
+  PrefNotifierImpl();
+  explicit PrefNotifierImpl(PrefService* pref_service);
+  ~PrefNotifierImpl() override;
+
+  // If the pref at the given path changes, we call the observer's
+  // OnPreferenceChanged method.
+  void AddPrefObserver(const std::string& path, PrefObserver* observer);
+  void RemovePrefObserver(const std::string& path, PrefObserver* observer);
+
+  // These observers are called for any pref changes.
+  //
+  // AVOID ADDING THESE. See the long comment in the identically-named
+  // functions on PrefService for background.
+  void AddPrefObserverAllPrefs(PrefObserver* observer);
+  void RemovePrefObserverAllPrefs(PrefObserver* observer);
+
+  // We run the callback once, when initialization completes. The bool
+  // parameter will be set to true for successful initialization,
+  // false for unsuccessful.
+  void AddInitObserver(base::OnceCallback<void(bool)> observer);
+
+  void SetPrefService(PrefService* pref_service);
+
+  // PrefNotifier overrides.
+  void OnPreferenceChanged(const std::string& pref_name) override;
+
+ protected:
+  // PrefNotifier overrides.
+  void OnInitializationCompleted(bool succeeded) override;
+
+  // A map from pref names to a list of observers. Observers get fired in the
+  // order they are added. These should only be accessed externally for unit
+  // testing.
+  typedef base::ObserverList<PrefObserver>::Unchecked PrefObserverList;
+  typedef std::unordered_map<std::string, std::unique_ptr<PrefObserverList>>
+      PrefObserverMap;
+
+  typedef std::list<base::OnceCallback<void(bool)>> PrefInitObserverList;
+
+  const PrefObserverMap* pref_observers() const { return &pref_observers_; }
+
+ private:
+  // For the given pref_name, fire any observer of the pref. Virtual so it can
+  // be mocked for unit testing.
+  virtual void FireObservers(const std::string& path);
+
+  // Weak reference; the notifier is owned by the PrefService.
+  PrefService* pref_service_;
+
+  PrefObserverMap pref_observers_;
+  PrefInitObserverList init_observers_;
+
+  // Observers for changes to any preference.
+  PrefObserverList all_prefs_pref_observers_;
+
+  base::ThreadChecker thread_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(PrefNotifierImpl);
+};
+
+#endif  // COMPONENTS_PREFS_PREF_NOTIFIER_IMPL_H_
diff --git a/src/components/prefs/pref_notifier_impl_unittest.cc b/src/components/prefs/pref_notifier_impl_unittest.cc
new file mode 100644
index 0000000..44fcba4
--- /dev/null
+++ b/src/components/prefs/pref_notifier_impl_unittest.cc
@@ -0,0 +1,217 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "components/prefs/mock_pref_change_callback.h"
+#include "components/prefs/pref_notifier_impl.h"
+#include "components/prefs/pref_observer.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/pref_value_store.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Field;
+using testing::Invoke;
+using testing::Mock;
+using testing::Truly;
+
+namespace {
+
+const char kChangedPref[] = "changed_pref";
+const char kUnchangedPref[] = "unchanged_pref";
+
+class MockPrefInitObserver {
+ public:
+  MOCK_METHOD1(OnInitializationCompleted, void(bool));
+};
+
+// This is an unmodified PrefNotifierImpl, except we make
+// OnPreferenceChanged public for tests.
+class TestingPrefNotifierImpl : public PrefNotifierImpl {
+ public:
+  explicit TestingPrefNotifierImpl(PrefService* service)
+      : PrefNotifierImpl(service) {}
+
+  // Make public for tests.
+  using PrefNotifierImpl::OnPreferenceChanged;
+};
+
+// Mock PrefNotifier that allows tracking of observers and notifications.
+class MockPrefNotifier : public PrefNotifierImpl {
+ public:
+  explicit MockPrefNotifier(PrefService* pref_service)
+      : PrefNotifierImpl(pref_service) {}
+  ~MockPrefNotifier() override {}
+
+  MOCK_METHOD1(FireObservers, void(const std::string& path));
+
+  size_t CountObserver(const std::string& path, PrefObserver* obs) {
+    auto observer_iterator = pref_observers()->find(path);
+    if (observer_iterator == pref_observers()->end())
+      return false;
+
+    size_t count = 0;
+    for (auto& existing_obs : *observer_iterator->second) {
+      if (&existing_obs == obs)
+        count++;
+    }
+
+    return count;
+  }
+
+  // Make public for tests below.
+  using PrefNotifierImpl::OnPreferenceChanged;
+  using PrefNotifierImpl::OnInitializationCompleted;
+};
+
+class PrefObserverMock : public PrefObserver {
+ public:
+  PrefObserverMock() {}
+  virtual ~PrefObserverMock() {}
+
+  MOCK_METHOD2(OnPreferenceChanged, void(PrefService*, const std::string&));
+};
+
+// Test fixture class.
+class PrefNotifierTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    pref_service_.registry()->RegisterBooleanPref(kChangedPref, true);
+    pref_service_.registry()->RegisterBooleanPref(kUnchangedPref, true);
+  }
+
+  TestingPrefServiceSimple pref_service_;
+
+  PrefObserverMock obs1_;
+  PrefObserverMock obs2_;
+};
+
+TEST_F(PrefNotifierTest, OnPreferenceChanged) {
+  MockPrefNotifier notifier(&pref_service_);
+  EXPECT_CALL(notifier, FireObservers(kChangedPref)).Times(1);
+  notifier.OnPreferenceChanged(kChangedPref);
+}
+
+TEST_F(PrefNotifierTest, OnInitializationCompleted) {
+  MockPrefNotifier notifier(&pref_service_);
+  MockPrefInitObserver observer;
+  notifier.AddInitObserver(
+      base::BindOnce(&MockPrefInitObserver::OnInitializationCompleted,
+                     base::Unretained(&observer)));
+  EXPECT_CALL(observer, OnInitializationCompleted(true));
+  notifier.OnInitializationCompleted(true);
+}
+
+TEST_F(PrefNotifierTest, AddAndRemovePrefObservers) {
+  const char pref_name[] = "homepage";
+  const char pref_name2[] = "proxy";
+
+  MockPrefNotifier notifier(&pref_service_);
+  notifier.AddPrefObserver(pref_name, &obs1_);
+  ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs1_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs1_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_));
+
+  // Re-adding the same observer for the same pref doesn't change anything.
+  // Skip this in debug mode, since it hits a DCHECK and death tests aren't
+  // thread-safe.
+#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+  notifier.AddPrefObserver(pref_name, &obs1_);
+  ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs1_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs1_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_));
+#endif
+
+  // Ensure that we can add the same observer to a different pref.
+  notifier.AddPrefObserver(pref_name2, &obs1_);
+  ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs1_));
+  ASSERT_EQ(1u, notifier.CountObserver(pref_name2, &obs1_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_));
+
+  // Ensure that we can add another observer to the same pref.
+  notifier.AddPrefObserver(pref_name, &obs2_);
+  ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs1_));
+  ASSERT_EQ(1u, notifier.CountObserver(pref_name2, &obs1_));
+  ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs2_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_));
+
+  // Ensure that we can remove all observers, and that removing a non-existent
+  // observer is harmless.
+  notifier.RemovePrefObserver(pref_name, &obs1_);
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs1_));
+  ASSERT_EQ(1u, notifier.CountObserver(pref_name2, &obs1_));
+  ASSERT_EQ(1u, notifier.CountObserver(pref_name, &obs2_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_));
+
+  notifier.RemovePrefObserver(pref_name, &obs2_);
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs1_));
+  ASSERT_EQ(1u, notifier.CountObserver(pref_name2, &obs1_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_));
+
+  notifier.RemovePrefObserver(pref_name, &obs1_);
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs1_));
+  ASSERT_EQ(1u, notifier.CountObserver(pref_name2, &obs1_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_));
+
+  notifier.RemovePrefObserver(pref_name2, &obs1_);
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs1_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs1_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name, &obs2_));
+  ASSERT_EQ(0u, notifier.CountObserver(pref_name2, &obs2_));
+}
+
+TEST_F(PrefNotifierTest, FireObservers) {
+  TestingPrefNotifierImpl notifier(&pref_service_);
+  notifier.AddPrefObserver(kChangedPref, &obs1_);
+  notifier.AddPrefObserver(kUnchangedPref, &obs1_);
+
+  EXPECT_CALL(obs1_, OnPreferenceChanged(&pref_service_, kChangedPref));
+  EXPECT_CALL(obs2_, OnPreferenceChanged(_, _)).Times(0);
+  notifier.OnPreferenceChanged(kChangedPref);
+  Mock::VerifyAndClearExpectations(&obs1_);
+  Mock::VerifyAndClearExpectations(&obs2_);
+
+  notifier.AddPrefObserver(kChangedPref, &obs2_);
+  notifier.AddPrefObserver(kUnchangedPref, &obs2_);
+
+  EXPECT_CALL(obs1_, OnPreferenceChanged(&pref_service_, kChangedPref));
+  EXPECT_CALL(obs2_, OnPreferenceChanged(&pref_service_, kChangedPref));
+  notifier.OnPreferenceChanged(kChangedPref);
+  Mock::VerifyAndClearExpectations(&obs1_);
+  Mock::VerifyAndClearExpectations(&obs2_);
+
+  // Make sure removing an observer from one pref doesn't affect anything else.
+  notifier.RemovePrefObserver(kChangedPref, &obs1_);
+
+  EXPECT_CALL(obs1_, OnPreferenceChanged(_, _)).Times(0);
+  EXPECT_CALL(obs2_, OnPreferenceChanged(&pref_service_, kChangedPref));
+  notifier.OnPreferenceChanged(kChangedPref);
+  Mock::VerifyAndClearExpectations(&obs1_);
+  Mock::VerifyAndClearExpectations(&obs2_);
+
+  // Make sure removing an observer entirely doesn't affect anything else.
+  notifier.RemovePrefObserver(kUnchangedPref, &obs1_);
+
+  EXPECT_CALL(obs1_, OnPreferenceChanged(_, _)).Times(0);
+  EXPECT_CALL(obs2_, OnPreferenceChanged(&pref_service_, kChangedPref));
+  notifier.OnPreferenceChanged(kChangedPref);
+  Mock::VerifyAndClearExpectations(&obs1_);
+  Mock::VerifyAndClearExpectations(&obs2_);
+
+  notifier.RemovePrefObserver(kChangedPref, &obs2_);
+  notifier.RemovePrefObserver(kUnchangedPref, &obs2_);
+}
+
+}  // namespace
diff --git a/src/components/prefs/pref_observer.h b/src/components/prefs/pref_observer.h
new file mode 100644
index 0000000..3f48931
--- /dev/null
+++ b/src/components/prefs/pref_observer.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_PREF_OBSERVER_H_
+#define COMPONENTS_PREFS_PREF_OBSERVER_H_
+
+#include <string>
+
+class PrefService;
+
+// Used internally to the Prefs subsystem to pass preference change
+// notifications between PrefService, PrefNotifierImpl and
+// PrefChangeRegistrar.
+class PrefObserver {
+ public:
+  virtual void OnPreferenceChanged(PrefService* service,
+                                   const std::string& pref_name) = 0;
+};
+
+#endif  // COMPONENTS_PREFS_PREF_OBSERVER_H_
diff --git a/src/components/prefs/pref_registry.cc b/src/components/prefs/pref_registry.cc
new file mode 100644
index 0000000..6d1ed5f
--- /dev/null
+++ b/src/components/prefs/pref_registry.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 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 "components/prefs/pref_registry.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "components/prefs/default_pref_store.h"
+#include "components/prefs/pref_store.h"
+
+PrefRegistry::PrefRegistry()
+    : defaults_(base::MakeRefCounted<DefaultPrefStore>()) {}
+
+PrefRegistry::~PrefRegistry() {}
+
+uint32_t PrefRegistry::GetRegistrationFlags(
+    const std::string& pref_name) const {
+  const auto& it = registration_flags_.find(pref_name);
+  return it != registration_flags_.end() ? it->second : NO_REGISTRATION_FLAGS;
+}
+
+scoped_refptr<PrefStore> PrefRegistry::defaults() {
+  return defaults_.get();
+}
+
+PrefRegistry::const_iterator PrefRegistry::begin() const {
+  return defaults_->begin();
+}
+
+PrefRegistry::const_iterator PrefRegistry::end() const {
+  return defaults_->end();
+}
+
+void PrefRegistry::SetDefaultPrefValue(const std::string& pref_name,
+                                       base::Value value) {
+  const base::Value* current_value = nullptr;
+  DCHECK(defaults_->GetValue(pref_name, &current_value))
+      << "Setting default for unregistered pref: " << pref_name;
+  DCHECK(value.type() == current_value->type())
+      << "Wrong type for new default: " << pref_name;
+
+  defaults_->ReplaceDefaultValue(pref_name, std::move(value));
+}
+
+void PrefRegistry::SetDefaultForeignPrefValue(const std::string& path,
+                                              base::Value default_value,
+                                              uint32_t flags) {
+  auto erased = foreign_pref_keys_.erase(path);
+  DCHECK_EQ(1u, erased);
+  RegisterPreference(path, std::move(default_value), flags);
+}
+
+void PrefRegistry::RegisterPreference(const std::string& path,
+                                      base::Value default_value,
+                                      uint32_t flags) {
+  base::Value::Type orig_type = default_value.type();
+  DCHECK(orig_type != base::Value::Type::NONE &&
+         orig_type != base::Value::Type::BINARY)
+      << "invalid preference type: " << orig_type;
+  DCHECK(!defaults_->GetValue(path, nullptr))
+      << "Trying to register a previously registered pref: " << path;
+  DCHECK(!base::ContainsKey(registration_flags_, path))
+      << "Trying to register a previously registered pref: " << path;
+
+  defaults_->SetDefaultValue(path, std::move(default_value));
+  if (flags != NO_REGISTRATION_FLAGS)
+    registration_flags_[path] = flags;
+
+  OnPrefRegistered(path, flags);
+}
+
+void PrefRegistry::RegisterForeignPref(const std::string& path) {
+  bool inserted = foreign_pref_keys_.insert(path).second;
+  DCHECK(inserted);
+}
+
+void PrefRegistry::OnPrefRegistered(const std::string& path, uint32_t flags) {}
diff --git a/src/components/prefs/pref_registry.h b/src/components/prefs/pref_registry.h
new file mode 100644
index 0000000..4a499cb
--- /dev/null
+++ b/src/components/prefs/pref_registry.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_PREF_REGISTRY_H_
+#define COMPONENTS_PREFS_PREF_REGISTRY_H_
+
+#include <stdint.h>
+
+#include <set>
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/prefs/prefs_export.h"
+
+namespace base {
+class Value;
+}
+
+class DefaultPrefStore;
+class PrefStore;
+
+// Preferences need to be registered with a type and default value
+// before they are used.
+//
+// The way you use a PrefRegistry is that you register all required
+// preferences on it (via one of its subclasses), then pass it as a
+// construction parameter to PrefService.
+//
+// Currently, registrations after constructing the PrefService will
+// also work, but this is being deprecated.
+class COMPONENTS_PREFS_EXPORT PrefRegistry
+    : public base::RefCounted<PrefRegistry> {
+ public:
+  // Registration flags that can be specified which impact how the pref will
+  // behave or be stored. This will be passed in a bitmask when the pref is
+  // registered. Subclasses of PrefRegistry can specify their own flags. Care
+  // must be taken to ensure none of these overlap with the flags below.
+  enum PrefRegistrationFlags : uint32_t {
+    // No flags are specified.
+    NO_REGISTRATION_FLAGS = 0,
+
+    // The first 8 bits are reserved for subclasses of PrefRegistry to use.
+
+    // This marks the pref as "lossy". There is no strict time guarantee on when
+    // a lossy pref will be persisted to permanent storage when it is modified.
+    LOSSY_PREF = 1 << 8,
+
+    // Registering a pref as public allows other services to access it.
+    PUBLIC = 1 << 9,
+  };
+
+  typedef PrefValueMap::const_iterator const_iterator;
+  typedef std::unordered_map<std::string, uint32_t> PrefRegistrationFlagsMap;
+
+  PrefRegistry();
+
+  // Retrieve the set of registration flags for the given preference. The return
+  // value is a bitmask of PrefRegistrationFlags.
+  uint32_t GetRegistrationFlags(const std::string& pref_name) const;
+
+  // Gets the registered defaults.
+  scoped_refptr<PrefStore> defaults();
+
+  // Allows iteration over defaults.
+  const_iterator begin() const;
+  const_iterator end() const;
+
+  // Changes the default value for a preference.
+  //
+  // |pref_name| must be a previously registered preference.
+  void SetDefaultPrefValue(const std::string& pref_name, base::Value value);
+
+  // Registers a pref owned by another service for use with the current service.
+  // The owning service must register that pref with the |PUBLIC| flag.
+  void RegisterForeignPref(const std::string& path);
+
+  // Sets the default value and flags of a previously-registered foreign pref
+  // value.
+  void SetDefaultForeignPrefValue(const std::string& path,
+                                  base::Value default_value,
+                                  uint32_t flags);
+
+  const std::set<std::string>& foreign_pref_keys() const {
+    return foreign_pref_keys_;
+  }
+
+ protected:
+  friend class base::RefCounted<PrefRegistry>;
+  virtual ~PrefRegistry();
+
+  // Used by subclasses to register a default value and registration flags for
+  // a preference. |flags| is a bitmask of |PrefRegistrationFlags|.
+  void RegisterPreference(const std::string& path,
+                          base::Value default_value,
+                          uint32_t flags);
+
+  // Allows subclasses to hook into pref registration.
+  virtual void OnPrefRegistered(const std::string& path, uint32_t flags);
+
+  scoped_refptr<DefaultPrefStore> defaults_;
+
+  // A map of pref name to a bitmask of PrefRegistrationFlags.
+  PrefRegistrationFlagsMap registration_flags_;
+
+  std::set<std::string> foreign_pref_keys_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PrefRegistry);
+};
+
+#endif  // COMPONENTS_PREFS_PREF_REGISTRY_H_
diff --git a/src/components/prefs/pref_registry_simple.cc b/src/components/prefs/pref_registry_simple.cc
new file mode 100644
index 0000000..bab05dd
--- /dev/null
+++ b/src/components/prefs/pref_registry_simple.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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 "components/prefs/pref_registry_simple.h"
+
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+
+PrefRegistrySimple::PrefRegistrySimple() = default;
+PrefRegistrySimple::~PrefRegistrySimple() = default;
+
+void PrefRegistrySimple::RegisterBooleanPref(const std::string& path,
+                                             bool default_value,
+                                             uint32_t flags) {
+  RegisterPreference(path, base::Value(default_value), flags);
+}
+
+void PrefRegistrySimple::RegisterIntegerPref(const std::string& path,
+                                             int default_value,
+                                             uint32_t flags) {
+  RegisterPreference(path, base::Value(default_value), flags);
+}
+
+void PrefRegistrySimple::RegisterDoublePref(const std::string& path,
+                                            double default_value,
+                                            uint32_t flags) {
+  RegisterPreference(path, base::Value(default_value), flags);
+}
+
+void PrefRegistrySimple::RegisterStringPref(const std::string& path,
+                                            const std::string& default_value,
+                                            uint32_t flags) {
+  RegisterPreference(path, base::Value(default_value), flags);
+}
+
+void PrefRegistrySimple::RegisterFilePathPref(
+    const std::string& path,
+    const base::FilePath& default_value,
+    uint32_t flags) {
+  RegisterPreference(path, base::Value(default_value.value()), flags);
+}
+
+void PrefRegistrySimple::RegisterListPref(const std::string& path,
+                                          uint32_t flags) {
+  RegisterPreference(path, base::Value(base::Value::Type::LIST), flags);
+}
+
+void PrefRegistrySimple::RegisterListPref(const std::string& path,
+                                          base::Value default_value,
+                                          uint32_t flags) {
+  RegisterPreference(path, std::move(default_value), flags);
+}
+
+void PrefRegistrySimple::RegisterDictionaryPref(const std::string& path,
+                                                uint32_t flags) {
+  RegisterPreference(path, base::Value(base::Value::Type::DICTIONARY), flags);
+}
+
+void PrefRegistrySimple::RegisterDictionaryPref(const std::string& path,
+                                                base::Value default_value,
+                                                uint32_t flags) {
+  RegisterPreference(path, std::move(default_value), flags);
+}
+
+void PrefRegistrySimple::RegisterInt64Pref(const std::string& path,
+                                           int64_t default_value,
+                                           uint32_t flags) {
+  RegisterPreference(path, base::Value(base::NumberToString(default_value)),
+                     flags);
+}
+
+void PrefRegistrySimple::RegisterUint64Pref(const std::string& path,
+                                            uint64_t default_value,
+                                            uint32_t flags) {
+  RegisterPreference(path, base::Value(base::NumberToString(default_value)),
+                     flags);
+}
+
+void PrefRegistrySimple::RegisterTimePref(const std::string& path,
+                                          base::Time default_value,
+                                          uint32_t flags) {
+  RegisterInt64Pref(
+      path, default_value.ToDeltaSinceWindowsEpoch().InMicroseconds(), flags);
+}
+
+void PrefRegistrySimple::RegisterTimeDeltaPref(const std::string& path,
+                                               base::TimeDelta default_value,
+                                               uint32_t flags) {
+  RegisterInt64Pref(path, default_value.InMicroseconds(), flags);
+}
diff --git a/src/components/prefs/pref_registry_simple.h b/src/components/prefs/pref_registry_simple.h
new file mode 100644
index 0000000..358aa67
--- /dev/null
+++ b/src/components/prefs/pref_registry_simple.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_PREF_REGISTRY_SIMPLE_H_
+#define COMPONENTS_PREFS_PREF_REGISTRY_SIMPLE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "components/prefs/pref_registry.h"
+#include "components/prefs/prefs_export.h"
+
+namespace base {
+class Value;
+class FilePath;
+}  // namespace base
+
+// A simple implementation of PrefRegistry.
+class COMPONENTS_PREFS_EXPORT PrefRegistrySimple : public PrefRegistry {
+ public:
+  PrefRegistrySimple();
+
+  // For each of these registration methods, |flags| is an optional bitmask of
+  // PrefRegistrationFlags.
+  void RegisterBooleanPref(const std::string& path,
+                           bool default_value,
+                           uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterIntegerPref(const std::string& path,
+                           int default_value,
+                           uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterDoublePref(const std::string& path,
+                          double default_value,
+                          uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterStringPref(const std::string& path,
+                          const std::string& default_value,
+                          uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterFilePathPref(const std::string& path,
+                            const base::FilePath& default_value,
+                            uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterListPref(const std::string& path,
+                        uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterListPref(const std::string& path,
+                        base::Value default_value,
+                        uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterDictionaryPref(const std::string& path,
+                              uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterDictionaryPref(const std::string& path,
+                              base::Value default_value,
+                              uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterInt64Pref(const std::string& path,
+                         int64_t default_value,
+                         uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterUint64Pref(const std::string& path,
+                          uint64_t default_value,
+                          uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterTimePref(const std::string& path,
+                        base::Time default_value,
+                        uint32_t flags = NO_REGISTRATION_FLAGS);
+
+  void RegisterTimeDeltaPref(const std::string& path,
+                             base::TimeDelta default_value,
+                             uint32_t flags = NO_REGISTRATION_FLAGS);
+
+ protected:
+  ~PrefRegistrySimple() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PrefRegistrySimple);
+};
+
+#endif  // COMPONENTS_PREFS_PREF_REGISTRY_SIMPLE_H_
diff --git a/src/components/prefs/pref_service.cc b/src/components/prefs/pref_service.cc
new file mode 100644
index 0000000..624a78f
--- /dev/null
+++ b/src/components/prefs/pref_service.cc
@@ -0,0 +1,732 @@
+// Copyright (c) 2012 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 "components/prefs/pref_service.h"
+
+#include <algorithm>
+#include <map>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/debug/alias.h"
+#include "base/debug/dump_without_crashing.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/util/values/values_util.h"
+#include "base/value_conversions.h"
+#include "build/build_config.h"
+#include "components/prefs/default_pref_store.h"
+#include "components/prefs/pref_notifier_impl.h"
+#include "components/prefs/pref_registry.h"
+
+namespace {
+
+class ReadErrorHandler : public PersistentPrefStore::ReadErrorDelegate {
+ public:
+  using ErrorCallback =
+      base::RepeatingCallback<void(PersistentPrefStore::PrefReadError)>;
+  explicit ReadErrorHandler(ErrorCallback cb) : callback_(cb) {}
+
+  void OnError(PersistentPrefStore::PrefReadError error) override {
+    callback_.Run(error);
+  }
+
+ private:
+  ErrorCallback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReadErrorHandler);
+};
+
+// Returns the WriteablePrefStore::PrefWriteFlags for the pref with the given
+// |path|.
+uint32_t GetWriteFlags(const PrefService::Preference* pref) {
+  uint32_t write_flags = WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS;
+
+  if (!pref)
+    return write_flags;
+
+  if (pref->registration_flags() & PrefRegistry::LOSSY_PREF)
+    write_flags |= WriteablePrefStore::LOSSY_PREF_WRITE_FLAG;
+  return write_flags;
+}
+
+// For prefs names in |pref_store| that are not presented in |pref_changed_map|,
+// check if their values differ from those in pref_service->FindPreference() and
+// add the result into |pref_changed_map|.
+void CheckForNewPrefChangesInPrefStore(
+    std::map<std::string, bool>* pref_changed_map,
+    PrefStore* pref_store,
+    PrefService* pref_service) {
+  if (!pref_store)
+    return;
+  auto values = pref_store->GetValues();
+  for (const auto& item : values->DictItems()) {
+    // If the key already presents, skip it as a store with higher precedence
+    // already sets the entry.
+    if (pref_changed_map->find(item.first) != pref_changed_map->end())
+      continue;
+    const PrefService::Preference* pref =
+        pref_service->FindPreference(item.first);
+    if (!pref)
+      continue;
+    pref_changed_map->emplace(item.first, *(pref->GetValue()) != item.second);
+  }
+}
+
+}  // namespace
+
+PrefService::PrefService(
+    std::unique_ptr<PrefNotifierImpl> pref_notifier,
+    std::unique_ptr<PrefValueStore> pref_value_store,
+    scoped_refptr<PersistentPrefStore> user_prefs,
+    scoped_refptr<PrefRegistry> pref_registry,
+    base::RepeatingCallback<void(PersistentPrefStore::PrefReadError)>
+        read_error_callback,
+    bool async)
+    : pref_notifier_(std::move(pref_notifier)),
+      pref_value_store_(std::move(pref_value_store)),
+      user_pref_store_(std::move(user_prefs)),
+      read_error_callback_(std::move(read_error_callback)),
+      pref_registry_(std::move(pref_registry)) {
+  pref_notifier_->SetPrefService(this);
+
+  DCHECK(pref_registry_);
+  DCHECK(pref_value_store_);
+
+  InitFromStorage(async);
+}
+
+PrefService::~PrefService() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // TODO(crbug.com/942491, 946668, 945772) The following code collects
+  // augments stack dumps created by ~PrefNotifierImpl() with information
+  // whether the profile owning the PrefService is an incognito profile.
+  // Delete this, once the bugs are closed.
+  const bool is_incognito_profile = user_pref_store_->IsInMemoryPrefStore();
+  base::debug::Alias(&is_incognito_profile);
+  // Export value of is_incognito_profile to a string so that `grep`
+  // is a sufficient tool to analyze crashdumps.
+  char is_incognito_profile_string[32];
+  base::strlcpy(is_incognito_profile_string,
+                is_incognito_profile ? "is_incognito: yes" : "is_incognito: no",
+                sizeof(is_incognito_profile_string));
+  base::debug::Alias(&is_incognito_profile_string);
+}
+
+void PrefService::InitFromStorage(bool async) {
+  if (user_pref_store_->IsInitializationComplete()) {
+    read_error_callback_.Run(user_pref_store_->GetReadError());
+  } else if (!async) {
+    read_error_callback_.Run(user_pref_store_->ReadPrefs());
+  } else {
+    // Guarantee that initialization happens after this function returned.
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&PersistentPrefStore::ReadPrefsAsync, user_pref_store_,
+                       new ReadErrorHandler(read_error_callback_)));
+  }
+}
+
+void PrefService::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  user_pref_store_->CommitPendingWrite(std::move(reply_callback),
+                                       std::move(synchronous_done_callback));
+}
+
+void PrefService::SchedulePendingLossyWrites() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  user_pref_store_->SchedulePendingLossyWrites();
+}
+
+bool PrefService::GetBoolean(const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  bool result = false;
+
+  const base::Value* value = GetPreferenceValueChecked(path);
+  if (!value)
+    return result;
+  bool rv = value->GetAsBoolean(&result);
+  DCHECK(rv);
+  return result;
+}
+
+int PrefService::GetInteger(const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  int result = 0;
+
+  const base::Value* value = GetPreferenceValueChecked(path);
+  if (!value)
+    return result;
+  bool rv = value->GetAsInteger(&result);
+  DCHECK(rv);
+  return result;
+}
+
+double PrefService::GetDouble(const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  double result = 0.0;
+
+  const base::Value* value = GetPreferenceValueChecked(path);
+  if (!value)
+    return result;
+  bool rv = value->GetAsDouble(&result);
+  DCHECK(rv);
+  return result;
+}
+
+std::string PrefService::GetString(const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  std::string result;
+
+  const base::Value* value = GetPreferenceValueChecked(path);
+  if (!value)
+    return result;
+  bool rv = value->GetAsString(&result);
+  DCHECK(rv);
+  return result;
+}
+
+base::FilePath PrefService::GetFilePath(const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  base::FilePath result;
+
+  const base::Value* value = GetPreferenceValueChecked(path);
+  if (!value)
+    return base::FilePath(result);
+  bool rv = base::GetValueAsFilePath(*value, &result);
+  DCHECK(rv);
+  return result;
+}
+
+bool PrefService::HasPrefPath(const std::string& path) const {
+  const Preference* pref = FindPreference(path);
+  return pref && !pref->IsDefaultValue();
+}
+
+void PrefService::IteratePreferenceValues(
+    base::RepeatingCallback<void(const std::string& key,
+                                 const base::Value& value)> callback) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  for (const auto& it : *pref_registry_)
+    callback.Run(it.first, *GetPreferenceValue(it.first));
+}
+
+std::unique_ptr<base::DictionaryValue> PrefService::GetPreferenceValues(
+    IncludeDefaults include_defaults) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::unique_ptr<base::DictionaryValue> out(new base::DictionaryValue);
+  for (const auto& it : *pref_registry_) {
+    if (include_defaults == INCLUDE_DEFAULTS) {
+      out->Set(it.first, GetPreferenceValue(it.first)->CreateDeepCopy());
+    } else {
+      const Preference* pref = FindPreference(it.first);
+      if (pref->IsDefaultValue())
+        continue;
+      out->Set(it.first, pref->GetValue()->CreateDeepCopy());
+    }
+  }
+  return out;
+}
+
+const PrefService::Preference* PrefService::FindPreference(
+    const std::string& pref_name) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  auto it = prefs_map_.find(pref_name);
+  if (it != prefs_map_.end())
+    return &(it->second);
+  const base::Value* default_value = nullptr;
+  if (!pref_registry_->defaults()->GetValue(pref_name, &default_value))
+    return nullptr;
+  it = prefs_map_
+           .insert(std::make_pair(
+               pref_name, Preference(this, pref_name, default_value->type())))
+           .first;
+  return &(it->second);
+}
+
+bool PrefService::ReadOnly() const {
+  return user_pref_store_->ReadOnly();
+}
+
+PrefService::PrefInitializationStatus PrefService::GetInitializationStatus()
+    const {
+  if (!user_pref_store_->IsInitializationComplete())
+    return INITIALIZATION_STATUS_WAITING;
+
+  switch (user_pref_store_->GetReadError()) {
+    case PersistentPrefStore::PREF_READ_ERROR_NONE:
+      return INITIALIZATION_STATUS_SUCCESS;
+    case PersistentPrefStore::PREF_READ_ERROR_NO_FILE:
+      return INITIALIZATION_STATUS_CREATED_NEW_PREF_STORE;
+    default:
+      return INITIALIZATION_STATUS_ERROR;
+  }
+}
+
+PrefService::PrefInitializationStatus
+PrefService::GetAllPrefStoresInitializationStatus() const {
+  if (!pref_value_store_->IsInitializationComplete())
+    return INITIALIZATION_STATUS_WAITING;
+
+  return GetInitializationStatus();
+}
+
+bool PrefService::IsManagedPreference(const std::string& pref_name) const {
+  const Preference* pref = FindPreference(pref_name);
+  return pref && pref->IsManaged();
+}
+
+bool PrefService::IsPreferenceManagedByCustodian(
+    const std::string& pref_name) const {
+  const Preference* pref = FindPreference(pref_name);
+  return pref && pref->IsManagedByCustodian();
+}
+
+bool PrefService::IsUserModifiablePreference(
+    const std::string& pref_name) const {
+  const Preference* pref = FindPreference(pref_name);
+  return pref && pref->IsUserModifiable();
+}
+
+const base::Value* PrefService::Get(const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const base::Value* value = GetPreferenceValueChecked(path);
+  if (!value)
+    return nullptr;
+  return value;
+}
+
+const base::DictionaryValue* PrefService::GetDictionary(
+    const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const base::Value* value = GetPreferenceValueChecked(path);
+  if (!value)
+    return nullptr;
+  if (value->type() != base::Value::Type::DICTIONARY) {
+    NOTREACHED();
+    return nullptr;
+  }
+  return static_cast<const base::DictionaryValue*>(value);
+}
+
+const base::Value* PrefService::GetUserPrefValue(
+    const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const Preference* pref = FindPreference(path);
+  if (!pref) {
+    NOTREACHED() << "Trying to get an unregistered pref: " << path;
+    return nullptr;
+  }
+
+  // Look for an existing preference in the user store. If it doesn't
+  // exist, return NULL.
+  base::Value* value = nullptr;
+  if (!user_pref_store_->GetMutableValue(path, &value))
+    return nullptr;
+
+  if (value->type() != pref->GetType()) {
+    NOTREACHED() << "Pref value type doesn't match registered type.";
+    return nullptr;
+  }
+
+  return value;
+}
+
+void PrefService::SetDefaultPrefValue(const std::string& path,
+                                      base::Value value) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  pref_registry_->SetDefaultPrefValue(path, std::move(value));
+}
+
+const base::Value* PrefService::GetDefaultPrefValue(
+    const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Lookup the preference in the default store.
+  const base::Value* value = nullptr;
+  bool has_value = pref_registry_->defaults()->GetValue(path, &value);
+  DCHECK(has_value) << "Default value missing for pref: " << path;
+  return value;
+}
+
+const base::ListValue* PrefService::GetList(const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const base::Value* value = GetPreferenceValueChecked(path);
+  if (!value)
+    return nullptr;
+  if (value->type() != base::Value::Type::LIST) {
+    NOTREACHED();
+    return nullptr;
+  }
+  return static_cast<const base::ListValue*>(value);
+}
+
+void PrefService::AddPrefObserver(const std::string& path, PrefObserver* obs) {
+  pref_notifier_->AddPrefObserver(path, obs);
+}
+
+void PrefService::RemovePrefObserver(const std::string& path,
+                                     PrefObserver* obs) {
+  pref_notifier_->RemovePrefObserver(path, obs);
+}
+
+void PrefService::AddPrefInitObserver(base::OnceCallback<void(bool)> obs) {
+  pref_notifier_->AddInitObserver(std::move(obs));
+}
+
+PrefRegistry* PrefService::DeprecatedGetPrefRegistry() {
+  return pref_registry_.get();
+}
+
+void PrefService::ClearPref(const std::string& path) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const Preference* pref = FindPreference(path);
+  if (!pref) {
+    NOTREACHED() << "Trying to clear an unregistered pref: " << path;
+    return;
+  }
+  user_pref_store_->RemoveValue(path, GetWriteFlags(pref));
+}
+
+void PrefService::ClearMutableValues() {
+  user_pref_store_->ClearMutableValues();
+}
+
+void PrefService::OnStoreDeletionFromDisk() {
+  user_pref_store_->OnStoreDeletionFromDisk();
+}
+
+void PrefService::ChangePrefValueStore(
+    PrefStore* managed_prefs,
+    PrefStore* supervised_user_prefs,
+    PrefStore* extension_prefs,
+    PrefStore* recommended_prefs,
+    std::unique_ptr<PrefValueStore::Delegate> delegate) {
+  // Only adding new pref stores are supported.
+  DCHECK(!pref_value_store_->HasPrefStore(PrefValueStore::MANAGED_STORE) ||
+         !managed_prefs);
+  DCHECK(
+      !pref_value_store_->HasPrefStore(PrefValueStore::SUPERVISED_USER_STORE) ||
+      !supervised_user_prefs);
+  DCHECK(!pref_value_store_->HasPrefStore(PrefValueStore::EXTENSION_STORE) ||
+         !extension_prefs);
+  DCHECK(!pref_value_store_->HasPrefStore(PrefValueStore::RECOMMENDED_STORE) ||
+         !recommended_prefs);
+
+  // If some of the stores are already initialized, check for pref value changes
+  // according to store precedence.
+  std::map<std::string, bool> pref_changed_map;
+  CheckForNewPrefChangesInPrefStore(&pref_changed_map, managed_prefs, this);
+  CheckForNewPrefChangesInPrefStore(&pref_changed_map, supervised_user_prefs,
+                                    this);
+  CheckForNewPrefChangesInPrefStore(&pref_changed_map, extension_prefs, this);
+  CheckForNewPrefChangesInPrefStore(&pref_changed_map, recommended_prefs, this);
+
+  pref_value_store_ = pref_value_store_->CloneAndSpecialize(
+      managed_prefs, supervised_user_prefs, extension_prefs,
+      nullptr /* command_line_prefs */, nullptr /* user_prefs */,
+      recommended_prefs, nullptr /* default_prefs */, pref_notifier_.get(),
+      std::move(delegate));
+
+  // Notify |pref_notifier_| on all changed values.
+  for (const auto& kv : pref_changed_map) {
+    if (kv.second)
+      pref_notifier_.get()->OnPreferenceChanged(kv.first);
+  }
+}
+
+void PrefService::AddPrefObserverAllPrefs(PrefObserver* obs) {
+  pref_notifier_->AddPrefObserverAllPrefs(obs);
+}
+
+void PrefService::RemovePrefObserverAllPrefs(PrefObserver* obs) {
+  pref_notifier_->RemovePrefObserverAllPrefs(obs);
+}
+
+void PrefService::Set(const std::string& path, const base::Value& value) {
+  SetUserPrefValue(path, value.CreateDeepCopy());
+}
+
+void PrefService::SetBoolean(const std::string& path, bool value) {
+  SetUserPrefValue(path, std::make_unique<base::Value>(value));
+}
+
+void PrefService::SetInteger(const std::string& path, int value) {
+  SetUserPrefValue(path, std::make_unique<base::Value>(value));
+}
+
+void PrefService::SetDouble(const std::string& path, double value) {
+  SetUserPrefValue(path, std::make_unique<base::Value>(value));
+}
+
+void PrefService::SetString(const std::string& path, const std::string& value) {
+  SetUserPrefValue(path, std::make_unique<base::Value>(value));
+}
+
+void PrefService::SetFilePath(const std::string& path,
+                              const base::FilePath& value) {
+  SetUserPrefValue(
+      path, base::Value::ToUniquePtrValue(base::CreateFilePathValue(value)));
+}
+
+void PrefService::SetInt64(const std::string& path, int64_t value) {
+  SetUserPrefValue(path,
+                   base::Value::ToUniquePtrValue(util::Int64ToValue(value)));
+}
+
+int64_t PrefService::GetInt64(const std::string& path) const {
+  const base::Value* value = GetPreferenceValueChecked(path);
+  base::Optional<int64_t> integer = util::ValueToInt64(value);
+  DCHECK(integer);
+  return integer.value_or(0);
+}
+
+void PrefService::SetUint64(const std::string& path, uint64_t value) {
+  SetUserPrefValue(path,
+                   std::make_unique<base::Value>(base::NumberToString(value)));
+}
+
+uint64_t PrefService::GetUint64(const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const base::Value* value = GetPreferenceValueChecked(path);
+  if (!value)
+    return 0;
+  std::string result("0");
+  bool rv = value->GetAsString(&result);
+  DCHECK(rv);
+
+  uint64_t val;
+  base::StringToUint64(result, &val);
+  return val;
+}
+
+void PrefService::SetTime(const std::string& path, base::Time value) {
+  SetUserPrefValue(path,
+                   base::Value::ToUniquePtrValue(util::TimeToValue(value)));
+}
+
+base::Time PrefService::GetTime(const std::string& path) const {
+  const base::Value* value = GetPreferenceValueChecked(path);
+  base::Optional<base::Time> time = util::ValueToTime(value);
+  DCHECK(time);
+  return time.value_or(base::Time());
+}
+
+void PrefService::SetTimeDelta(const std::string& path, base::TimeDelta value) {
+  SetUserPrefValue(
+      path, base::Value::ToUniquePtrValue(util::TimeDeltaToValue(value)));
+}
+
+base::TimeDelta PrefService::GetTimeDelta(const std::string& path) const {
+  const base::Value* value = GetPreferenceValueChecked(path);
+  base::Optional<base::TimeDelta> time_delta = util::ValueToTimeDelta(value);
+  DCHECK(time_delta);
+  return time_delta.value_or(base::TimeDelta());
+}
+
+base::Value* PrefService::GetMutableUserPref(const std::string& path,
+                                             base::Value::Type type) {
+  CHECK(type == base::Value::Type::DICTIONARY ||
+        type == base::Value::Type::LIST);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const Preference* pref = FindPreference(path);
+  if (!pref) {
+    NOTREACHED() << "Trying to get an unregistered pref: " << path;
+    return nullptr;
+  }
+  if (pref->GetType() != type) {
+    NOTREACHED() << "Wrong type for GetMutableValue: " << path;
+    return nullptr;
+  }
+
+  // Look for an existing preference in the user store. Return it in case it
+  // exists and has the correct type.
+  base::Value* value = nullptr;
+  if (user_pref_store_->GetMutableValue(path, &value) &&
+      value->type() == type) {
+    return value;
+  }
+
+  // TODO(crbug.com/859477): Remove once root cause has been found.
+  if (value && value->type() != type) {
+    DEBUG_ALIAS_FOR_CSTR(path_copy, path.c_str(), 1024);
+    base::debug::DumpWithoutCrashing();
+  }
+
+  // If no user preference of the correct type exists, clone default value.
+  const base::Value* default_value = nullptr;
+  pref_registry_->defaults()->GetValue(path, &default_value);
+  // TODO(crbug.com/859477): Revert to DCHECK once root cause has been found.
+  if (default_value->type() != type) {
+    DEBUG_ALIAS_FOR_CSTR(path_copy, path.c_str(), 1024);
+    base::debug::DumpWithoutCrashing();
+  }
+  user_pref_store_->SetValueSilently(path, default_value->CreateDeepCopy(),
+                                     GetWriteFlags(pref));
+  user_pref_store_->GetMutableValue(path, &value);
+  return value;
+}
+
+void PrefService::ReportUserPrefChanged(const std::string& key) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  user_pref_store_->ReportValueChanged(key, GetWriteFlags(FindPreference(key)));
+}
+
+void PrefService::ReportUserPrefChanged(
+    const std::string& key,
+    std::set<std::vector<std::string>> path_components) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  user_pref_store_->ReportSubValuesChanged(key, std::move(path_components),
+                                           GetWriteFlags(FindPreference(key)));
+}
+
+void PrefService::SetUserPrefValue(const std::string& path,
+                                   std::unique_ptr<base::Value> new_value) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const Preference* pref = FindPreference(path);
+  if (!pref) {
+    NOTREACHED() << "Trying to write an unregistered pref: " << path;
+    return;
+  }
+  if (pref->GetType() != new_value->type()) {
+    NOTREACHED() << "Trying to set pref " << path << " of type "
+                 << pref->GetType() << " to value of type "
+                 << new_value->type();
+    return;
+  }
+
+  user_pref_store_->SetValue(path, std::move(new_value), GetWriteFlags(pref));
+}
+
+void PrefService::UpdateCommandLinePrefStore(PrefStore* command_line_store) {
+  pref_value_store_->UpdateCommandLinePrefStore(command_line_store);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// PrefService::Preference
+
+PrefService::Preference::Preference(const PrefService* service,
+                                    std::string name,
+                                    base::Value::Type type)
+    : name_(std::move(name)),
+      type_(type),
+      // Cache the registration flags at creation time to avoid multiple map
+      // lookups later.
+      registration_flags_(service->pref_registry_->GetRegistrationFlags(name_)),
+      pref_service_(service) {}
+
+const base::Value* PrefService::Preference::GetValue() const {
+  return pref_service_->GetPreferenceValueChecked(name_);
+}
+
+const base::Value* PrefService::Preference::GetRecommendedValue() const {
+  DCHECK(pref_service_->FindPreference(name_))
+      << "Must register pref before getting its value";
+
+  const base::Value* found_value = nullptr;
+  if (pref_value_store()->GetRecommendedValue(name_, type_, &found_value)) {
+    DCHECK(found_value->type() == type_);
+    return found_value;
+  }
+
+  // The pref has no recommended value.
+  return nullptr;
+}
+
+bool PrefService::Preference::IsManaged() const {
+  return pref_value_store()->PrefValueInManagedStore(name_);
+}
+
+bool PrefService::Preference::IsManagedByCustodian() const {
+  return pref_value_store()->PrefValueInSupervisedStore(name_);
+}
+
+bool PrefService::Preference::IsRecommended() const {
+  return pref_value_store()->PrefValueFromRecommendedStore(name_);
+}
+
+bool PrefService::Preference::HasExtensionSetting() const {
+  return pref_value_store()->PrefValueInExtensionStore(name_);
+}
+
+bool PrefService::Preference::HasUserSetting() const {
+  return pref_value_store()->PrefValueInUserStore(name_);
+}
+
+bool PrefService::Preference::IsExtensionControlled() const {
+  return pref_value_store()->PrefValueFromExtensionStore(name_);
+}
+
+bool PrefService::Preference::IsUserControlled() const {
+  return pref_value_store()->PrefValueFromUserStore(name_);
+}
+
+bool PrefService::Preference::IsDefaultValue() const {
+  return pref_value_store()->PrefValueFromDefaultStore(name_);
+}
+
+bool PrefService::Preference::IsUserModifiable() const {
+  return pref_value_store()->PrefValueUserModifiable(name_);
+}
+
+bool PrefService::Preference::IsExtensionModifiable() const {
+  return pref_value_store()->PrefValueExtensionModifiable(name_);
+}
+
+const base::Value* PrefService::GetPreferenceValue(
+    const std::string& path) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // TODO(battre): This is a check for crbug.com/435208. After analyzing some
+  // crash dumps it looks like the PrefService is accessed even though it has
+  // been cleared already.
+  CHECK(pref_registry_);
+  CHECK(pref_registry_->defaults());
+  CHECK(pref_value_store_);
+
+  const base::Value* default_value = nullptr;
+  if (pref_registry_->defaults()->GetValue(path, &default_value)) {
+    const base::Value* found_value = nullptr;
+    base::Value::Type default_type = default_value->type();
+    if (pref_value_store_->GetValue(path, default_type, &found_value)) {
+      DCHECK(found_value->type() == default_type);
+      return found_value;
+    } else {
+      // Every registered preference has at least a default value.
+      NOTREACHED() << "no valid value found for registered pref " << path;
+    }
+  }
+
+  return nullptr;
+}
+
+const base::Value* PrefService::GetPreferenceValueChecked(
+    const std::string& path) const {
+  const base::Value* value = GetPreferenceValue(path);
+  DCHECK(value) << "Trying to read an unregistered pref: " << path;
+  return value;
+}
diff --git a/src/components/prefs/pref_service.h b/src/components/prefs/pref_service.h
new file mode 100644
index 0000000..c6f6f14
--- /dev/null
+++ b/src/components/prefs/pref_service.h
@@ -0,0 +1,468 @@
+// Copyright (c) 2012 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 provides a way to access the application's current preferences.
+
+// Chromium settings and storage represent user-selected preferences and
+// information and MUST not be extracted, overwritten or modified except
+// through Chromium defined APIs.
+
+#ifndef COMPONENTS_PREFS_PREF_SERVICE_H_
+#define COMPONENTS_PREFS_PREF_SERVICE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_value_store.h"
+#include "components/prefs/prefs_export.h"
+
+class PrefNotifier;
+class PrefNotifierImpl;
+class PrefObserver;
+class PrefRegistry;
+class PrefStore;
+
+namespace base {
+class FilePath;
+}
+
+namespace prefs {
+class ScopedDictionaryPrefUpdate;
+}
+
+namespace subtle {
+class PrefMemberBase;
+class ScopedUserPrefUpdateBase;
+}  // namespace subtle
+
+// Base class for PrefServices. You can use the base class to read and
+// interact with preferences, but not to register new preferences; for
+// that see e.g. PrefRegistrySimple.
+//
+// Settings and storage accessed through this class represent
+// user-selected preferences and information and MUST not be
+// extracted, overwritten or modified except through the defined APIs.
+class COMPONENTS_PREFS_EXPORT PrefService {
+ public:
+  enum PrefInitializationStatus {
+    INITIALIZATION_STATUS_WAITING,
+    INITIALIZATION_STATUS_SUCCESS,
+    INITIALIZATION_STATUS_CREATED_NEW_PREF_STORE,
+    INITIALIZATION_STATUS_ERROR
+  };
+
+  enum IncludeDefaults {
+    INCLUDE_DEFAULTS,
+    EXCLUDE_DEFAULTS,
+  };
+
+  // A helper class to store all the information associated with a preference.
+  class COMPONENTS_PREFS_EXPORT Preference {
+   public:
+    // The type of the preference is determined by the type with which it is
+    // registered. This type needs to be a boolean, integer, double, string,
+    // dictionary (a branch), or list.  You shouldn't need to construct this on
+    // your own; use the PrefService::Register*Pref methods instead.
+    Preference(const PrefService* service,
+               std::string name,
+               base::Value::Type type);
+    ~Preference() {}
+
+    // Returns the name of the Preference (i.e., the key, e.g.,
+    // browser.window_placement).
+    std::string name() const { return name_; }
+
+    // Returns the registered type of the preference.
+    base::Value::Type GetType() const { return type_; }
+
+    // Returns the value of the Preference, falling back to the registered
+    // default value if no other has been set.
+    const base::Value* GetValue() const;
+
+    // Returns the value recommended by the admin, if any.
+    const base::Value* GetRecommendedValue() const;
+
+    // Returns true if the Preference is managed, i.e. set by an admin policy.
+    // Since managed prefs have the highest priority, this also indicates
+    // whether the pref is actually being controlled by the policy setting.
+    bool IsManaged() const;
+
+    // Returns true if the Preference is controlled by the custodian of the
+    // supervised user. Since a supervised user is not expected to have an admin
+    // policy, this is the controlling pref if set.
+    bool IsManagedByCustodian() const;
+
+    // Returns true if the Preference's current value is one recommended by
+    // admin policy. Note that this will be false if any other higher-priority
+    // source overrides the value (e.g., the user has set a value).
+    bool IsRecommended() const;
+
+    // Returns true if the Preference has a value set by an extension, even if
+    // that value is being overridden by a higher-priority source.
+    bool HasExtensionSetting() const;
+
+    // Returns true if the Preference has a user setting, even if that value is
+    // being overridden by a higher-priority source.
+    bool HasUserSetting() const;
+
+    // Returns true if the Preference value is currently being controlled by an
+    // extension, and not by any higher-priority source.
+    bool IsExtensionControlled() const;
+
+    // Returns true if the Preference value is currently being controlled by a
+    // user setting, and not by any higher-priority source.
+    bool IsUserControlled() const;
+
+    // Returns true if the Preference is currently using its default value,
+    // and has not been set by any higher-priority source (even with the same
+    // value).
+    bool IsDefaultValue() const;
+
+    // Returns true if the user can change the Preference value, which is the
+    // case if no higher-priority source than the user store controls the
+    // Preference.
+    bool IsUserModifiable() const;
+
+    // Returns true if an extension can change the Preference value, which is
+    // the case if no higher-priority source than the extension store controls
+    // the Preference.
+    bool IsExtensionModifiable() const;
+
+    // Return the registration flags for this pref as a bitmask of
+    // PrefRegistry::PrefRegistrationFlags.
+    uint32_t registration_flags() const { return registration_flags_; }
+
+   private:
+    friend class PrefService;
+
+    PrefValueStore* pref_value_store() const {
+      return pref_service_->pref_value_store_.get();
+    }
+
+    const std::string name_;
+
+    const base::Value::Type type_;
+
+    const uint32_t registration_flags_;
+
+    // Reference to the PrefService in which this pref was created.
+    const PrefService* const pref_service_;
+  };
+
+  // You may wish to use PrefServiceFactory or one of its subclasses
+  // for simplified construction.
+  PrefService(std::unique_ptr<PrefNotifierImpl> pref_notifier,
+              std::unique_ptr<PrefValueStore> pref_value_store,
+              scoped_refptr<PersistentPrefStore> user_prefs,
+              scoped_refptr<PrefRegistry> pref_registry,
+              base::RepeatingCallback<void(PersistentPrefStore::PrefReadError)>
+                  read_error_callback,
+              bool async);
+  virtual ~PrefService();
+
+  // Lands pending writes to disk. This should only be used if we need to save
+  // immediately (basically, during shutdown). |reply_callback| will be posted
+  // to the current sequence when changes have been written.
+  // |synchronous_done_callback| on the other hand will be invoked right away
+  // wherever the writes complete (could even be invoked synchronously if no
+  // writes need to occur); this is useful when the current thread cannot pump
+  // messages to observe the reply (e.g. nested loops banned on main thread
+  // during shutdown). |synchronous_done_callback| must be thread-safe.
+  void CommitPendingWrite(
+      base::OnceClosure reply_callback = base::OnceClosure(),
+      base::OnceClosure synchronous_done_callback = base::OnceClosure());
+
+  // Schedule a write if there is any lossy data pending. Unlike
+  // CommitPendingWrite() this does not immediately sync to disk, instead it
+  // triggers an eventual write if there is lossy data pending and if there
+  // isn't one scheduled already.
+  void SchedulePendingLossyWrites();
+
+  // Returns true if the preference for the given preference name is available
+  // and is managed.
+  bool IsManagedPreference(const std::string& pref_name) const;
+
+  // Returns true if the preference for the given preference name is available
+  // and is controlled by the parent/guardian of the child Account.
+  bool IsPreferenceManagedByCustodian(const std::string& pref_name) const;
+
+  // Returns |true| if a preference with the given name is available and its
+  // value can be changed by the user.
+  bool IsUserModifiablePreference(const std::string& pref_name) const;
+
+  // Look up a preference.  Returns NULL if the preference is not
+  // registered.
+  const PrefService::Preference* FindPreference(const std::string& path) const;
+
+  // If the path is valid and the value at the end of the path matches the type
+  // specified, it will return the specified value.  Otherwise, the default
+  // value (set when the pref was registered) will be returned.
+  bool GetBoolean(const std::string& path) const;
+  int GetInteger(const std::string& path) const;
+  double GetDouble(const std::string& path) const;
+  std::string GetString(const std::string& path) const;
+  base::FilePath GetFilePath(const std::string& path) const;
+
+  // Returns the branch if it exists, or the registered default value otherwise.
+  // Note that |path| must point to a registered preference. In that case, these
+  // functions will never return NULL.
+  const base::Value* Get(const std::string& path) const;
+  const base::DictionaryValue* GetDictionary(const std::string& path) const;
+  const base::ListValue* GetList(const std::string& path) const;
+
+  // Removes a user pref and restores the pref to its default value.
+  void ClearPref(const std::string& path);
+
+  // If the path is valid (i.e., registered), update the pref value in the user
+  // prefs.
+  // To set the value of dictionary or list values in the pref tree use
+  // Set(), but to modify the value of a dictionary or list use either
+  // ListPrefUpdate or DictionaryPrefUpdate from scoped_user_pref_update.h.
+  void Set(const std::string& path, const base::Value& value);
+  void SetBoolean(const std::string& path, bool value);
+  void SetInteger(const std::string& path, int value);
+  void SetDouble(const std::string& path, double value);
+  void SetString(const std::string& path, const std::string& value);
+  void SetFilePath(const std::string& path, const base::FilePath& value);
+
+  // Int64 helper methods that actually store the given value as a string.
+  // Note that if obtaining the named value via GetDictionary or GetList, the
+  // Value type will be Type::STRING.
+  void SetInt64(const std::string& path, int64_t value);
+  int64_t GetInt64(const std::string& path) const;
+
+  // As above, but for unsigned values.
+  void SetUint64(const std::string& path, uint64_t value);
+  uint64_t GetUint64(const std::string& path) const;
+
+  // Time helper methods that actually store the given value as a string, which
+  // represents the number of microseconds elapsed (absolute for TimeDelta and
+  // relative to Windows epoch for Time variants). Note that if obtaining the
+  // named value via GetDictionary or GetList, the Value type will be
+  // Type::STRING.
+  void SetTime(const std::string& path, base::Time value);
+  base::Time GetTime(const std::string& path) const;
+  void SetTimeDelta(const std::string& path, base::TimeDelta value);
+  base::TimeDelta GetTimeDelta(const std::string& path) const;
+
+  // Returns the value of the given preference, from the user pref store. If
+  // the preference is not set in the user pref store, returns NULL.
+  const base::Value* GetUserPrefValue(const std::string& path) const;
+
+  // Changes the default value for a preference.
+  //
+  // Will cause a pref change notification to be fired if this causes
+  // the effective value to change.
+  void SetDefaultPrefValue(const std::string& path, base::Value value);
+
+  // Returns the default value of the given preference. |path| must point to a
+  // registered preference. In that case, will never return nullptr, so callers
+  // do not need to check this.
+  const base::Value* GetDefaultPrefValue(const std::string& path) const;
+
+  // Returns true if a value has been set for the specified path.
+  // NOTE: this is NOT the same as FindPreference. In particular
+  // FindPreference returns whether RegisterXXX has been invoked, where as
+  // this checks if a value exists for the path.
+  bool HasPrefPath(const std::string& path) const;
+
+  // Issues a callback for every preference value. The preferences must not be
+  // mutated during iteration.
+  void IteratePreferenceValues(
+      base::RepeatingCallback<void(const std::string& key,
+                                   const base::Value& value)> callback) const;
+
+  // Returns a dictionary with effective preference values. This is an expensive
+  // operation which does a deep copy. Use only if you really need the results
+  // in a base::Value (for example, for JSON serialization). Otherwise use
+  // IteratePreferenceValues above to avoid the copies.
+  //
+  // If INCLUDE_DEFAULTS is requested, preferences set to their default values
+  // will be included. Otherwise, these will be omitted from the returned
+  // dictionary.
+  std::unique_ptr<base::DictionaryValue> GetPreferenceValues(
+      IncludeDefaults include_defaults) const;
+
+  bool ReadOnly() const;
+
+  // Returns the initialization state, taking only user prefs into account.
+  PrefInitializationStatus GetInitializationStatus() const;
+
+  // Returns the initialization state, taking all pref stores into account.
+  PrefInitializationStatus GetAllPrefStoresInitializationStatus() const;
+
+  // Tell our PrefValueStore to update itself to |command_line_store|.
+  // Takes ownership of the store.
+  virtual void UpdateCommandLinePrefStore(PrefStore* command_line_store);
+
+  // We run the callback once, when initialization completes. The bool
+  // parameter will be set to true for successful initialization,
+  // false for unsuccessful.
+  void AddPrefInitObserver(base::OnceCallback<void(bool)> callback);
+
+  // Returns the PrefRegistry object for this service. You should not
+  // use this; the intent is for no registrations to take place after
+  // PrefService has been constructed.
+  //
+  // Instead of using this method, the recommended approach is to
+  // register all preferences for a class Xyz up front in a static
+  // Xyz::RegisterPrefs function, which gets invoked early in the
+  // application's start-up, before a PrefService is created.
+  //
+  // As an example, prefs registration in Chrome is triggered by the
+  // functions chrome::RegisterPrefs (for global preferences) and
+  // chrome::RegisterProfilePrefs (for user-specific preferences)
+  // implemented in chrome/browser/prefs/browser_prefs.cc.
+  PrefRegistry* DeprecatedGetPrefRegistry();
+
+  // Clears mutable values.
+  void ClearMutableValues();
+
+  // Invoked when the store is deleted from disk. Allows this PrefService
+  // to tangentially cleanup data it may have saved outside the store.
+  void OnStoreDeletionFromDisk();
+
+  // Add new pref stores to the existing PrefValueStore. Only adding new
+  // stores are allowed. If a corresponding store already exists, calling this
+  // will cause DCHECK failures. If the newly added stores already contain
+  // values, PrefNotifier associated with this object will be notified with
+  // these values. |delegate| can be passed to observe events of the new
+  // PrefValueStore.
+  // TODO(qinmin): packaging all the input params in a struct, and do the same
+  // for the constructor.
+  void ChangePrefValueStore(
+      PrefStore* managed_prefs,
+      PrefStore* supervised_user_prefs,
+      PrefStore* extension_prefs,
+      PrefStore* recommended_prefs,
+      std::unique_ptr<PrefValueStore::Delegate> delegate = nullptr);
+
+  // A low level function for registering an observer for every single
+  // preference changed notification. The caller must ensure that the observer
+  // remains valid as long as it is registered. Pointer ownership is not
+  // transferred.
+  //
+  // Almost all calling code should use a PrefChangeRegistrar instead.
+  //
+  // AVOID ADDING THESE. These are low-level observer notifications that are
+  // called for every pref change. This can lead to inefficiency, and the lack
+  // of a "registrar" model makes it easy to forget to undregister. It is
+  // really designed for integrating other notification systems, not for normal
+  // observation.
+  void AddPrefObserverAllPrefs(PrefObserver* obs);
+  void RemovePrefObserverAllPrefs(PrefObserver* obs);
+
+ protected:
+  // The PrefNotifier handles registering and notifying preference observers.
+  // It is created and owned by this PrefService. Subclasses may access it for
+  // unit testing.
+  const std::unique_ptr<PrefNotifierImpl> pref_notifier_;
+
+  // The PrefValueStore provides prioritized preference values. It is owned by
+  // this PrefService. Subclasses may access it for unit testing.
+  std::unique_ptr<PrefValueStore> pref_value_store_;
+
+  // Pref Stores and profile that we passed to the PrefValueStore.
+  const scoped_refptr<PersistentPrefStore> user_pref_store_;
+
+  // Callback to call when a read error occurs. Always invoked on the sequence
+  // this PrefService was created own.
+  const base::RepeatingCallback<void(PersistentPrefStore::PrefReadError)>
+      read_error_callback_;
+
+ private:
+  // Hash map expected to be fastest here since it minimises expensive
+  // string comparisons. Order is unimportant, and deletions are rare.
+  // Confirmed on Android where this speeded Chrome startup by roughly 50ms
+  // vs. std::map, and by roughly 180ms vs. std::set of Preference pointers.
+  typedef std::unordered_map<std::string, Preference> PreferenceMap;
+
+  // Give access to ReportUserPrefChanged() and GetMutableUserPref().
+  friend class subtle::ScopedUserPrefUpdateBase;
+  friend class PrefServiceTest_WriteablePrefStoreFlags_Test;
+  friend class prefs::ScopedDictionaryPrefUpdate;
+
+  // Registration of pref change observers must be done using the
+  // PrefChangeRegistrar, which is declared as a friend here to grant it
+  // access to the otherwise protected members Add/RemovePrefObserver.
+  // PrefMember registers for preferences changes notification directly to
+  // avoid the storage overhead of the registrar, so its base class must be
+  // declared as a friend, too.
+  friend class PrefChangeRegistrar;
+  friend class subtle::PrefMemberBase;
+
+  // These are protected so they can only be accessed by the friend
+  // classes listed above.
+  //
+  // If the pref at the given path changes, we call the observer's
+  // OnPreferenceChanged method. Note that observers should not call
+  // these methods directly but rather use a PrefChangeRegistrar to
+  // make sure the observer gets cleaned up properly.
+  //
+  // Virtual for testing.
+  virtual void AddPrefObserver(const std::string& path, PrefObserver* obs);
+  virtual void RemovePrefObserver(const std::string& path, PrefObserver* obs);
+
+  // Sends notification of a changed preference. This needs to be called by
+  // a ScopedUserPrefUpdate or ScopedDictionaryPrefUpdate if a DictionaryValue
+  // or ListValue is changed.
+  void ReportUserPrefChanged(const std::string& key);
+  void ReportUserPrefChanged(
+      const std::string& key,
+      std::set<std::vector<std::string>> path_components);
+
+  // Sets the value for this pref path in the user pref store and informs the
+  // PrefNotifier of the change.
+  void SetUserPrefValue(const std::string& path,
+                        std::unique_ptr<base::Value> new_value);
+
+  // Load preferences from storage, attempting to diagnose and handle errors.
+  // This should only be called from the constructor.
+  void InitFromStorage(bool async);
+
+  // Used to set the value of dictionary or list values in the user pref store.
+  // This will create a dictionary or list if one does not exist in the user
+  // pref store. This method returns NULL only if you're requesting an
+  // unregistered pref or a non-dict/non-list pref.
+  // |type| may only be Values::Type::DICTIONARY or Values::Type::LIST and
+  // |path| must point to a registered preference of type |type|.
+  // Ownership of the returned value remains at the user pref store.
+  base::Value* GetMutableUserPref(const std::string& path,
+                                  base::Value::Type type);
+
+  // GetPreferenceValue is the equivalent of FindPreference(path)->GetValue(),
+  // it has been added for performance. It is faster because it does
+  // not need to find or create a Preference object to get the
+  // value (GetValue() calls back though the preference service to
+  // actually get the value.).
+  const base::Value* GetPreferenceValue(const std::string& path) const;
+  const base::Value* GetPreferenceValueChecked(const std::string& path) const;
+
+  const scoped_refptr<PrefRegistry> pref_registry_;
+
+  // Local cache of registered Preference objects. The pref_registry_
+  // is authoritative with respect to what the types and default values
+  // of registered preferences are.
+  mutable PreferenceMap prefs_map_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(PrefService);
+};
+
+#endif  // COMPONENTS_PREFS_PREF_SERVICE_H_
diff --git a/src/components/prefs/pref_service_factory.cc b/src/components/prefs/pref_service_factory.cc
new file mode 100644
index 0000000..cc8e52d
--- /dev/null
+++ b/src/components/prefs/pref_service_factory.cc
@@ -0,0 +1,51 @@
+// Copyright 2013 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 "components/prefs/pref_service_factory.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/sequenced_task_runner.h"
+#include "components/prefs/default_pref_store.h"
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/pref_filter.h"
+#include "components/prefs/pref_notifier_impl.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/pref_value_store.h"
+
+PrefServiceFactory::PrefServiceFactory()
+    : read_error_callback_(base::DoNothing()), async_(false) {}
+
+PrefServiceFactory::~PrefServiceFactory() {}
+
+void PrefServiceFactory::SetUserPrefsFile(
+    const base::FilePath& prefs_file,
+    base::SequencedTaskRunner* task_runner) {
+  user_prefs_ =
+      base::MakeRefCounted<JsonPrefStore>(prefs_file, nullptr, task_runner);
+}
+
+std::unique_ptr<PrefService> PrefServiceFactory::Create(
+    scoped_refptr<PrefRegistry> pref_registry,
+    std::unique_ptr<PrefValueStore::Delegate> delegate) {
+  auto pref_notifier = std::make_unique<PrefNotifierImpl>();
+  auto pref_value_store = std::make_unique<PrefValueStore>(
+      managed_prefs_.get(), supervised_user_prefs_.get(),
+      extension_prefs_.get(), command_line_prefs_.get(), user_prefs_.get(),
+      recommended_prefs_.get(), pref_registry->defaults().get(),
+      pref_notifier.get(), std::move(delegate));
+  return std::make_unique<PrefService>(
+      std::move(pref_notifier), std::move(pref_value_store), user_prefs_.get(),
+      std::move(pref_registry), read_error_callback_, async_);
+}
+
+void PrefServiceFactory::ChangePrefValueStore(
+    PrefService* pref_service,
+    std::unique_ptr<PrefValueStore::Delegate> delegate) {
+  pref_service->ChangePrefValueStore(
+      managed_prefs_.get(), supervised_user_prefs_.get(),
+      extension_prefs_.get(), recommended_prefs_.get(), std::move(delegate));
+}
diff --git a/src/components/prefs/pref_service_factory.h b/src/components/prefs/pref_service_factory.h
new file mode 100644
index 0000000..56699c0
--- /dev/null
+++ b/src/components/prefs/pref_service_factory.h
@@ -0,0 +1,101 @@
+// Copyright 2013 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 COMPONENTS_PREFS_PREF_SERVICE_FACTORY_H_
+#define COMPONENTS_PREFS_PREF_SERVICE_FACTORY_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_registry.h"
+#include "components/prefs/pref_store.h"
+#include "components/prefs/pref_value_store.h"
+#include "components/prefs/prefs_export.h"
+
+class PrefService;
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+}  // namespace base
+
+// A class that allows convenient building of PrefService.
+class COMPONENTS_PREFS_EXPORT PrefServiceFactory {
+ public:
+  PrefServiceFactory();
+  virtual ~PrefServiceFactory();
+
+  // Functions for setting the various parameters of the PrefService to build.
+  void set_managed_prefs(scoped_refptr<PrefStore> prefs) {
+    managed_prefs_.swap(prefs);
+  }
+
+  void set_supervised_user_prefs(scoped_refptr<PrefStore> prefs) {
+    supervised_user_prefs_.swap(prefs);
+  }
+
+  void set_extension_prefs(scoped_refptr<PrefStore> prefs) {
+    extension_prefs_.swap(prefs);
+  }
+
+  void set_command_line_prefs(scoped_refptr<PrefStore> prefs) {
+    command_line_prefs_.swap(prefs);
+  }
+
+  void set_user_prefs(scoped_refptr<PersistentPrefStore> prefs) {
+    user_prefs_.swap(prefs);
+  }
+
+  void set_recommended_prefs(scoped_refptr<PrefStore> prefs) {
+    recommended_prefs_.swap(prefs);
+  }
+
+  // Sets up error callback for the PrefService.  A do-nothing default is
+  // provided if this is not called. This callback is always invoked (async or
+  // not) on the sequence on which Create is invoked.
+  void set_read_error_callback(
+      base::RepeatingCallback<void(PersistentPrefStore::PrefReadError)>
+          read_error_callback) {
+    read_error_callback_ = std::move(read_error_callback);
+  }
+
+  // Specifies to use an actual file-backed user pref store.
+  void SetUserPrefsFile(const base::FilePath& prefs_file,
+                        base::SequencedTaskRunner* task_runner);
+
+  void set_async(bool async) {
+    async_ = async;
+  }
+
+  // Creates a PrefService object initialized with the parameters from
+  // this factory.
+  std::unique_ptr<PrefService> Create(
+      scoped_refptr<PrefRegistry> pref_registry,
+      std::unique_ptr<PrefValueStore::Delegate> delegate = nullptr);
+
+  // Add pref stores from this object to the |pref_service|.
+  void ChangePrefValueStore(
+      PrefService* pref_service,
+      std::unique_ptr<PrefValueStore::Delegate> delegate = nullptr);
+
+ protected:
+  scoped_refptr<PrefStore> managed_prefs_;
+  scoped_refptr<PrefStore> supervised_user_prefs_;
+  scoped_refptr<PrefStore> extension_prefs_;
+  scoped_refptr<PrefStore> command_line_prefs_;
+  scoped_refptr<PersistentPrefStore> user_prefs_;
+  scoped_refptr<PrefStore> recommended_prefs_;
+
+  base::RepeatingCallback<void(PersistentPrefStore::PrefReadError)>
+      read_error_callback_;
+
+  // Defaults to false.
+  bool async_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PrefServiceFactory);
+};
+
+#endif  // COMPONENTS_PREFS_PREF_SERVICE_FACTORY_H_
diff --git a/src/components/prefs/pref_service_unittest.cc b/src/components/prefs/pref_service_unittest.cc
new file mode 100644
index 0000000..e63cc86
--- /dev/null
+++ b/src/components/prefs/pref_service_unittest.cc
@@ -0,0 +1,620 @@
+// Copyright (c) 2012 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 <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/bind_helpers.h"
+#include "base/stl_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/mock_pref_change_callback.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_notifier_impl.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service_factory.h"
+#include "components/prefs/pref_value_store.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/prefs/testing_pref_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Mock;
+
+namespace {
+
+const char kPrefName[] = "pref.name";
+const char kManagedPref[] = "managed_pref";
+const char kRecommendedPref[] = "recommended_pref";
+const char kSupervisedPref[] = "supervised_pref";
+
+}  // namespace
+
+TEST(PrefServiceTest, NoObserverFire) {
+  TestingPrefServiceSimple prefs;
+
+  const char pref_name[] = "homepage";
+  prefs.registry()->RegisterStringPref(pref_name, std::string());
+
+  const char new_pref_value[] = "http://www.google.com/";
+  MockPrefChangeCallback obs(&prefs);
+  PrefChangeRegistrar registrar;
+  registrar.Init(&prefs);
+  registrar.Add(pref_name, obs.GetCallback());
+
+  // This should fire the checks in MockPrefChangeCallback::OnPreferenceChanged.
+  const base::Value expected_value(new_pref_value);
+  obs.Expect(pref_name, &expected_value);
+  prefs.SetString(pref_name, new_pref_value);
+  Mock::VerifyAndClearExpectations(&obs);
+
+  // Setting the pref to the same value should not set the pref value a second
+  // time.
+  EXPECT_CALL(obs, OnPreferenceChanged(_)).Times(0);
+  prefs.SetString(pref_name, new_pref_value);
+  Mock::VerifyAndClearExpectations(&obs);
+
+  // Clearing the pref should cause the pref to fire.
+  const base::Value expected_default_value((std::string()));
+  obs.Expect(pref_name, &expected_default_value);
+  prefs.ClearPref(pref_name);
+  Mock::VerifyAndClearExpectations(&obs);
+
+  // Clearing the pref again should not cause the pref to fire.
+  EXPECT_CALL(obs, OnPreferenceChanged(_)).Times(0);
+  prefs.ClearPref(pref_name);
+  Mock::VerifyAndClearExpectations(&obs);
+}
+
+TEST(PrefServiceTest, HasPrefPath) {
+  TestingPrefServiceSimple prefs;
+
+  const char path[] = "fake.path";
+
+  // Shouldn't initially have a path.
+  EXPECT_FALSE(prefs.HasPrefPath(path));
+
+  // Register the path. This doesn't set a value, so the path still shouldn't
+  // exist.
+  prefs.registry()->RegisterStringPref(path, std::string());
+  EXPECT_FALSE(prefs.HasPrefPath(path));
+
+  // Set a value and make sure we have a path.
+  prefs.SetString(path, "blah");
+  EXPECT_TRUE(prefs.HasPrefPath(path));
+}
+
+TEST(PrefServiceTest, Observers) {
+  const char pref_name[] = "homepage";
+
+  TestingPrefServiceSimple prefs;
+  prefs.SetUserPref(pref_name,
+                    std::make_unique<base::Value>("http://www.cnn.com"));
+  prefs.registry()->RegisterStringPref(pref_name, std::string());
+
+  const char new_pref_value[] = "http://www.google.com/";
+  const base::Value expected_new_pref_value(new_pref_value);
+  MockPrefChangeCallback obs(&prefs);
+  PrefChangeRegistrar registrar;
+  registrar.Init(&prefs);
+  registrar.Add(pref_name, obs.GetCallback());
+
+  PrefChangeRegistrar registrar_two;
+  registrar_two.Init(&prefs);
+
+  // This should fire the checks in MockPrefChangeCallback::OnPreferenceChanged.
+  obs.Expect(pref_name, &expected_new_pref_value);
+  prefs.SetString(pref_name, new_pref_value);
+  Mock::VerifyAndClearExpectations(&obs);
+
+  // Now try adding a second pref observer.
+  const char new_pref_value2[] = "http://www.youtube.com/";
+  const base::Value expected_new_pref_value2(new_pref_value2);
+  MockPrefChangeCallback obs2(&prefs);
+  obs.Expect(pref_name, &expected_new_pref_value2);
+  obs2.Expect(pref_name, &expected_new_pref_value2);
+  registrar_two.Add(pref_name, obs2.GetCallback());
+  // This should fire the checks in obs and obs2.
+  prefs.SetString(pref_name, new_pref_value2);
+  Mock::VerifyAndClearExpectations(&obs);
+  Mock::VerifyAndClearExpectations(&obs2);
+
+  // Set a recommended value.
+  const base::Value recommended_pref_value("http://www.gmail.com/");
+  obs.Expect(pref_name, &expected_new_pref_value2);
+  obs2.Expect(pref_name, &expected_new_pref_value2);
+  // This should fire the checks in obs and obs2 but with an unchanged value
+  // as the recommended value is being overridden by the user-set value.
+  prefs.SetRecommendedPref(pref_name, recommended_pref_value.CreateDeepCopy());
+  Mock::VerifyAndClearExpectations(&obs);
+  Mock::VerifyAndClearExpectations(&obs2);
+
+  // Make sure obs2 still works after removing obs.
+  registrar.Remove(pref_name);
+  EXPECT_CALL(obs, OnPreferenceChanged(_)).Times(0);
+  obs2.Expect(pref_name, &expected_new_pref_value);
+  // This should only fire the observer in obs2.
+  prefs.SetString(pref_name, new_pref_value);
+  Mock::VerifyAndClearExpectations(&obs);
+  Mock::VerifyAndClearExpectations(&obs2);
+}
+
+// Make sure that if a preference changes type, so the wrong type is stored in
+// the user pref file, it uses the correct fallback value instead.
+TEST(PrefServiceTest, GetValueChangedType) {
+  const int kTestValue = 10;
+  TestingPrefServiceSimple prefs;
+  prefs.registry()->RegisterIntegerPref(kPrefName, kTestValue);
+
+  // Check falling back to a recommended value.
+  prefs.SetUserPref(kPrefName, std::make_unique<base::Value>("not an integer"));
+  const PrefService::Preference* pref = prefs.FindPreference(kPrefName);
+  ASSERT_TRUE(pref);
+  const base::Value* value = pref->GetValue();
+  ASSERT_TRUE(value);
+  EXPECT_EQ(base::Value::Type::INTEGER, value->type());
+  int actual_int_value = -1;
+  EXPECT_TRUE(value->GetAsInteger(&actual_int_value));
+  EXPECT_EQ(kTestValue, actual_int_value);
+}
+
+TEST(PrefServiceTest, GetValueAndGetRecommendedValue) {
+  const int kDefaultValue = 5;
+  const int kUserValue = 10;
+  const int kRecommendedValue = 15;
+  TestingPrefServiceSimple prefs;
+  prefs.registry()->RegisterIntegerPref(kPrefName, kDefaultValue);
+
+  // Create pref with a default value only.
+  const PrefService::Preference* pref = prefs.FindPreference(kPrefName);
+  ASSERT_TRUE(pref);
+
+  // Check that GetValue() returns the default value.
+  const base::Value* value = pref->GetValue();
+  ASSERT_TRUE(value);
+  EXPECT_EQ(base::Value::Type::INTEGER, value->type());
+  int actual_int_value = -1;
+  EXPECT_TRUE(value->GetAsInteger(&actual_int_value));
+  EXPECT_EQ(kDefaultValue, actual_int_value);
+
+  // Check that GetRecommendedValue() returns no value.
+  value = pref->GetRecommendedValue();
+  ASSERT_FALSE(value);
+
+  // Set a user-set value.
+  prefs.SetUserPref(kPrefName, std::make_unique<base::Value>(kUserValue));
+
+  // Check that GetValue() returns the user-set value.
+  value = pref->GetValue();
+  ASSERT_TRUE(value);
+  EXPECT_EQ(base::Value::Type::INTEGER, value->type());
+  actual_int_value = -1;
+  EXPECT_TRUE(value->GetAsInteger(&actual_int_value));
+  EXPECT_EQ(kUserValue, actual_int_value);
+
+  // Check that GetRecommendedValue() returns no value.
+  value = pref->GetRecommendedValue();
+  ASSERT_FALSE(value);
+
+  // Set a recommended value.
+  prefs.SetRecommendedPref(kPrefName,
+                           std::make_unique<base::Value>(kRecommendedValue));
+
+  // Check that GetValue() returns the user-set value.
+  value = pref->GetValue();
+  ASSERT_TRUE(value);
+  EXPECT_EQ(base::Value::Type::INTEGER, value->type());
+  actual_int_value = -1;
+  EXPECT_TRUE(value->GetAsInteger(&actual_int_value));
+  EXPECT_EQ(kUserValue, actual_int_value);
+
+  // Check that GetRecommendedValue() returns the recommended value.
+  value = pref->GetRecommendedValue();
+  ASSERT_TRUE(value);
+  EXPECT_EQ(base::Value::Type::INTEGER, value->type());
+  actual_int_value = -1;
+  EXPECT_TRUE(value->GetAsInteger(&actual_int_value));
+  EXPECT_EQ(kRecommendedValue, actual_int_value);
+
+  // Remove the user-set value.
+  prefs.RemoveUserPref(kPrefName);
+
+  // Check that GetValue() returns the recommended value.
+  value = pref->GetValue();
+  ASSERT_TRUE(value);
+  EXPECT_EQ(base::Value::Type::INTEGER, value->type());
+  actual_int_value = -1;
+  EXPECT_TRUE(value->GetAsInteger(&actual_int_value));
+  EXPECT_EQ(kRecommendedValue, actual_int_value);
+
+  // Check that GetRecommendedValue() returns the recommended value.
+  value = pref->GetRecommendedValue();
+  ASSERT_TRUE(value);
+  EXPECT_EQ(base::Value::Type::INTEGER, value->type());
+  actual_int_value = -1;
+  EXPECT_TRUE(value->GetAsInteger(&actual_int_value));
+  EXPECT_EQ(kRecommendedValue, actual_int_value);
+}
+
+TEST(PrefServiceTest, SetTimeValue_RegularTime) {
+  TestingPrefServiceSimple prefs;
+
+  // Register a null time as the default.
+  prefs.registry()->RegisterTimePref(kPrefName, base::Time());
+  EXPECT_TRUE(prefs.GetTime(kPrefName).is_null());
+
+  // Set a time and make sure that we can read it without any loss of precision.
+  const base::Time time = base::Time::Now();
+  prefs.SetTime(kPrefName, time);
+  EXPECT_EQ(time, prefs.GetTime(kPrefName));
+}
+
+TEST(PrefServiceTest, SetTimeValue_NullTime) {
+  TestingPrefServiceSimple prefs;
+
+  // Register a non-null time as the default.
+  const base::Time default_time = base::Time::FromDeltaSinceWindowsEpoch(
+      base::TimeDelta::FromMicroseconds(12345));
+  prefs.registry()->RegisterTimePref(kPrefName, default_time);
+  EXPECT_FALSE(prefs.GetTime(kPrefName).is_null());
+
+  // Set a null time and make sure that it remains null upon deserialization.
+  prefs.SetTime(kPrefName, base::Time());
+  EXPECT_TRUE(prefs.GetTime(kPrefName).is_null());
+}
+
+TEST(PrefServiceTest, SetTimeDeltaValue_RegularTimeDelta) {
+  TestingPrefServiceSimple prefs;
+
+  // Register a zero time delta as the default.
+  prefs.registry()->RegisterTimeDeltaPref(kPrefName, base::TimeDelta());
+  EXPECT_TRUE(prefs.GetTimeDelta(kPrefName).is_zero());
+
+  // Set a time delta and make sure that we can read it without any loss of
+  // precision.
+  const base::TimeDelta delta = base::Time::Now() - base::Time();
+  prefs.SetTimeDelta(kPrefName, delta);
+  EXPECT_EQ(delta, prefs.GetTimeDelta(kPrefName));
+}
+
+TEST(PrefServiceTest, SetTimeDeltaValue_ZeroTimeDelta) {
+  TestingPrefServiceSimple prefs;
+
+  // Register a non-zero time delta as the default.
+  const base::TimeDelta default_delta =
+      base::TimeDelta::FromMicroseconds(12345);
+  prefs.registry()->RegisterTimeDeltaPref(kPrefName, default_delta);
+  EXPECT_FALSE(prefs.GetTimeDelta(kPrefName).is_zero());
+
+  // Set a zero time delta and make sure that it remains zero upon
+  // deserialization.
+  prefs.SetTimeDelta(kPrefName, base::TimeDelta());
+  EXPECT_TRUE(prefs.GetTimeDelta(kPrefName).is_zero());
+}
+
+// A PrefStore which just stores the last write flags that were used to write
+// values to it.
+class WriteFlagChecker : public TestingPrefStore {
+ public:
+  WriteFlagChecker() {}
+
+  void ReportValueChanged(const std::string& key, uint32_t flags) override {
+    SetLastWriteFlags(flags);
+  }
+
+  void SetValue(const std::string& key,
+                std::unique_ptr<base::Value> value,
+                uint32_t flags) override {
+    SetLastWriteFlags(flags);
+  }
+
+  void SetValueSilently(const std::string& key,
+                        std::unique_ptr<base::Value> value,
+                        uint32_t flags) override {
+    SetLastWriteFlags(flags);
+  }
+
+  void RemoveValue(const std::string& key, uint32_t flags) override {
+    SetLastWriteFlags(flags);
+  }
+
+  uint32_t GetLastFlagsAndClear() {
+    CHECK(last_write_flags_set_);
+    uint32_t result = last_write_flags_;
+    last_write_flags_set_ = false;
+    last_write_flags_ = WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS;
+    return result;
+  }
+
+  bool last_write_flags_set() { return last_write_flags_set_; }
+
+ private:
+  ~WriteFlagChecker() override {}
+
+  void SetLastWriteFlags(uint32_t flags) {
+    CHECK(!last_write_flags_set_);
+    last_write_flags_set_ = true;
+    last_write_flags_ = flags;
+  }
+
+  bool last_write_flags_set_ = false;
+  uint32_t last_write_flags_ = WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS;
+};
+
+TEST(PrefServiceTest, WriteablePrefStoreFlags) {
+  scoped_refptr<WriteFlagChecker> flag_checker(new WriteFlagChecker);
+  scoped_refptr<PrefRegistrySimple> registry(new PrefRegistrySimple);
+  PrefServiceFactory factory;
+  factory.set_user_prefs(flag_checker);
+  std::unique_ptr<PrefService> prefs(factory.Create(registry.get()));
+
+  // The first 8 bits of write flags are reserved for subclasses. Create a
+  // custom flag in this range
+  uint32_t kCustomRegistrationFlag = 1 << 2;
+
+  // A map of the registration flags that will be tested and the write flags
+  // they are expected to convert to.
+  struct RegistrationToWriteFlags {
+    const char* pref_name;
+    uint32_t registration_flags;
+    uint32_t write_flags;
+  };
+  const RegistrationToWriteFlags kRegistrationToWriteFlags[] = {
+      {"none", PrefRegistry::NO_REGISTRATION_FLAGS,
+       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS},
+      {"lossy", PrefRegistry::LOSSY_PREF,
+       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG},
+      {"custom", kCustomRegistrationFlag,
+       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS},
+      {"lossyandcustom", PrefRegistry::LOSSY_PREF | kCustomRegistrationFlag,
+       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG}};
+
+  for (size_t i = 0; i < base::size(kRegistrationToWriteFlags); ++i) {
+    RegistrationToWriteFlags entry = kRegistrationToWriteFlags[i];
+    registry->RegisterDictionaryPref(entry.pref_name,
+                                     entry.registration_flags);
+
+    SCOPED_TRACE("Currently testing pref with name: " +
+                 std::string(entry.pref_name));
+
+    prefs->GetMutableUserPref(entry.pref_name, base::Value::Type::DICTIONARY);
+    EXPECT_TRUE(flag_checker->last_write_flags_set());
+    EXPECT_EQ(entry.write_flags, flag_checker->GetLastFlagsAndClear());
+
+    prefs->ReportUserPrefChanged(entry.pref_name);
+    EXPECT_TRUE(flag_checker->last_write_flags_set());
+    EXPECT_EQ(entry.write_flags, flag_checker->GetLastFlagsAndClear());
+
+    prefs->ClearPref(entry.pref_name);
+    EXPECT_TRUE(flag_checker->last_write_flags_set());
+    EXPECT_EQ(entry.write_flags, flag_checker->GetLastFlagsAndClear());
+
+    prefs->SetUserPrefValue(entry.pref_name,
+                            std::make_unique<base::DictionaryValue>());
+    EXPECT_TRUE(flag_checker->last_write_flags_set());
+    EXPECT_EQ(entry.write_flags, flag_checker->GetLastFlagsAndClear());
+  }
+}
+
+class PrefServiceSetValueTest : public testing::Test {
+ protected:
+  static const char kName[];
+  static const char kValue[];
+
+  PrefServiceSetValueTest() : observer_(&prefs_) {}
+
+  TestingPrefServiceSimple prefs_;
+  MockPrefChangeCallback observer_;
+};
+
+const char PrefServiceSetValueTest::kName[] = "name";
+const char PrefServiceSetValueTest::kValue[] = "value";
+
+TEST_F(PrefServiceSetValueTest, SetStringValue) {
+  const char default_string[] = "default";
+  const base::Value default_value(default_string);
+  prefs_.registry()->RegisterStringPref(kName, default_string);
+
+  PrefChangeRegistrar registrar;
+  registrar.Init(&prefs_);
+  registrar.Add(kName, observer_.GetCallback());
+
+  // Changing the controlling store from default to user triggers notification.
+  observer_.Expect(kName, &default_value);
+  prefs_.Set(kName, default_value);
+  Mock::VerifyAndClearExpectations(&observer_);
+
+  EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0);
+  prefs_.Set(kName, default_value);
+  Mock::VerifyAndClearExpectations(&observer_);
+
+  base::Value new_value(kValue);
+  observer_.Expect(kName, &new_value);
+  prefs_.Set(kName, new_value);
+  Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(PrefServiceSetValueTest, SetDictionaryValue) {
+  prefs_.registry()->RegisterDictionaryPref(kName);
+  PrefChangeRegistrar registrar;
+  registrar.Init(&prefs_);
+  registrar.Add(kName, observer_.GetCallback());
+
+  EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0);
+  prefs_.RemoveUserPref(kName);
+  Mock::VerifyAndClearExpectations(&observer_);
+
+  base::DictionaryValue new_value;
+  new_value.SetString(kName, kValue);
+  observer_.Expect(kName, &new_value);
+  prefs_.Set(kName, new_value);
+  Mock::VerifyAndClearExpectations(&observer_);
+
+  EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0);
+  prefs_.Set(kName, new_value);
+  Mock::VerifyAndClearExpectations(&observer_);
+
+  base::DictionaryValue empty;
+  observer_.Expect(kName, &empty);
+  prefs_.Set(kName, empty);
+  Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(PrefServiceSetValueTest, SetListValue) {
+  prefs_.registry()->RegisterListPref(kName);
+  PrefChangeRegistrar registrar;
+  registrar.Init(&prefs_);
+  registrar.Add(kName, observer_.GetCallback());
+
+  EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0);
+  prefs_.RemoveUserPref(kName);
+  Mock::VerifyAndClearExpectations(&observer_);
+
+  base::ListValue new_value;
+  new_value.AppendString(kValue);
+  observer_.Expect(kName, &new_value);
+  prefs_.Set(kName, new_value);
+  Mock::VerifyAndClearExpectations(&observer_);
+
+  EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0);
+  prefs_.Set(kName, new_value);
+  Mock::VerifyAndClearExpectations(&observer_);
+
+  base::ListValue empty;
+  observer_.Expect(kName, &empty);
+  prefs_.Set(kName, empty);
+  Mock::VerifyAndClearExpectations(&observer_);
+}
+
+class PrefValueStoreChangeTest : public testing::Test {
+ protected:
+  PrefValueStoreChangeTest()
+      : user_pref_store_(base::MakeRefCounted<TestingPrefStore>()),
+        pref_registry_(base::MakeRefCounted<PrefRegistrySimple>()) {}
+
+  ~PrefValueStoreChangeTest() override = default;
+
+  void SetUp() override {
+    auto pref_notifier = std::make_unique<PrefNotifierImpl>();
+    auto pref_value_store = std::make_unique<PrefValueStore>(
+        nullptr /* managed_prefs */, nullptr /* supervised_user_prefs */,
+        nullptr /* extension_prefs */, new TestingPrefStore(),
+        user_pref_store_.get(), nullptr /* recommended_prefs */,
+        pref_registry_->defaults().get(), pref_notifier.get());
+    pref_service_ = std::make_unique<PrefService>(
+        std::move(pref_notifier), std::move(pref_value_store), user_pref_store_,
+        pref_registry_, base::DoNothing(), false);
+    pref_registry_->RegisterIntegerPref(kManagedPref, 1);
+    pref_registry_->RegisterIntegerPref(kRecommendedPref, 2);
+    pref_registry_->RegisterIntegerPref(kSupervisedPref, 3);
+  }
+
+  std::unique_ptr<PrefService> pref_service_;
+  scoped_refptr<TestingPrefStore> user_pref_store_;
+  scoped_refptr<PrefRegistrySimple> pref_registry_;
+};
+
+// Check that value from the new PrefValueStore will be correctly retrieved.
+TEST_F(PrefValueStoreChangeTest, ChangePrefValueStore) {
+  const PrefService::Preference* preference =
+      pref_service_->FindPreference(kManagedPref);
+  EXPECT_TRUE(preference->IsDefaultValue());
+  EXPECT_EQ(base::Value(1), *(preference->GetValue()));
+  const PrefService::Preference* supervised =
+      pref_service_->FindPreference(kSupervisedPref);
+  EXPECT_TRUE(supervised->IsDefaultValue());
+  EXPECT_EQ(base::Value(3), *(supervised->GetValue()));
+  const PrefService::Preference* recommended =
+      pref_service_->FindPreference(kRecommendedPref);
+  EXPECT_TRUE(recommended->IsDefaultValue());
+  EXPECT_EQ(base::Value(2), *(recommended->GetValue()));
+
+  user_pref_store_->SetInteger(kManagedPref, 10);
+  EXPECT_TRUE(preference->IsUserControlled());
+  ASSERT_EQ(base::Value(10), *(preference->GetValue()));
+
+  scoped_refptr<TestingPrefStore> managed_pref_store =
+      base::MakeRefCounted<TestingPrefStore>();
+  pref_service_->ChangePrefValueStore(
+      managed_pref_store.get(), nullptr /* supervised_user_prefs */,
+      nullptr /* extension_prefs */, nullptr /* recommended_prefs */);
+  EXPECT_TRUE(preference->IsUserControlled());
+  ASSERT_EQ(base::Value(10), *(preference->GetValue()));
+
+  // Test setting a managed pref after overriding the managed PrefStore.
+  managed_pref_store->SetInteger(kManagedPref, 20);
+  EXPECT_TRUE(preference->IsManaged());
+  ASSERT_EQ(base::Value(20), *(preference->GetValue()));
+
+  // Test overriding the supervised and recommended PrefStore with already set
+  // prefs.
+  scoped_refptr<TestingPrefStore> supervised_pref_store =
+      base::MakeRefCounted<TestingPrefStore>();
+  scoped_refptr<TestingPrefStore> recommened_pref_store =
+      base::MakeRefCounted<TestingPrefStore>();
+  supervised_pref_store->SetInteger(kManagedPref, 30);
+  supervised_pref_store->SetInteger(kSupervisedPref, 31);
+  recommened_pref_store->SetInteger(kManagedPref, 40);
+  recommened_pref_store->SetInteger(kRecommendedPref, 41);
+  pref_service_->ChangePrefValueStore(
+      nullptr /* managed_prefs */, supervised_pref_store.get(),
+      nullptr /* extension_prefs */, recommened_pref_store.get());
+  EXPECT_TRUE(preference->IsManaged());
+  ASSERT_EQ(base::Value(20), *(preference->GetValue()));
+  EXPECT_TRUE(supervised->IsManagedByCustodian());
+  EXPECT_EQ(base::Value(31), *(supervised->GetValue()));
+  EXPECT_TRUE(recommended->IsRecommended());
+  EXPECT_EQ(base::Value(41), *(recommended->GetValue()));
+}
+
+// Tests that PrefChangeRegistrar works after PrefValueStore is changed.
+TEST_F(PrefValueStoreChangeTest, PrefChangeRegistrar) {
+  MockPrefChangeCallback obs(pref_service_.get());
+  PrefChangeRegistrar registrar;
+  registrar.Init(pref_service_.get());
+  registrar.Add(kManagedPref, obs.GetCallback());
+  registrar.Add(kSupervisedPref, obs.GetCallback());
+  registrar.Add(kRecommendedPref, obs.GetCallback());
+
+  base::Value expected_value(10);
+  obs.Expect(kManagedPref, &expected_value);
+  user_pref_store_->SetInteger(kManagedPref, 10);
+  Mock::VerifyAndClearExpectations(&obs);
+  expected_value = base::Value(11);
+  obs.Expect(kRecommendedPref, &expected_value);
+  user_pref_store_->SetInteger(kRecommendedPref, 11);
+  Mock::VerifyAndClearExpectations(&obs);
+
+  // Test overriding the managed and supervised PrefStore with already set
+  // prefs.
+  scoped_refptr<TestingPrefStore> managed_pref_store =
+      base::MakeRefCounted<TestingPrefStore>();
+  scoped_refptr<TestingPrefStore> supervised_pref_store =
+      base::MakeRefCounted<TestingPrefStore>();
+  // Update |kManagedPref| before changing the PrefValueStore, the
+  // PrefChangeRegistrar should get notified on |kManagedPref| as its value
+  // changes.
+  managed_pref_store->SetInteger(kManagedPref, 20);
+  // Due to store precedence, the value of |kRecommendedPref| will not be
+  // changed so PrefChangeRegistrar will not be notified.
+  managed_pref_store->SetInteger(kRecommendedPref, 11);
+  supervised_pref_store->SetInteger(kManagedPref, 30);
+  supervised_pref_store->SetInteger(kRecommendedPref, 21);
+  expected_value = base::Value(20);
+  obs.Expect(kManagedPref, &expected_value);
+  pref_service_->ChangePrefValueStore(
+      managed_pref_store.get(), supervised_pref_store.get(),
+      nullptr /* extension_prefs */, nullptr /* recommended_prefs */);
+  Mock::VerifyAndClearExpectations(&obs);
+
+  // Update a pref value after PrefValueStore change, it should also work.
+  expected_value = base::Value(31);
+  obs.Expect(kSupervisedPref, &expected_value);
+  supervised_pref_store->SetInteger(kSupervisedPref, 31);
+  Mock::VerifyAndClearExpectations(&obs);
+}
diff --git a/src/components/prefs/pref_store.cc b/src/components/prefs/pref_store.cc
new file mode 100644
index 0000000..2ae0afc
--- /dev/null
+++ b/src/components/prefs/pref_store.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2012 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 "components/prefs/pref_store.h"
+
+bool PrefStore::HasObservers() const {
+  return false;
+}
+
+bool PrefStore::IsInitializationComplete() const {
+  return true;
+}
diff --git a/src/components/prefs/pref_store.h b/src/components/prefs/pref_store.h
new file mode 100644
index 0000000..22d263c
--- /dev/null
+++ b/src/components/prefs/pref_store.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_PREF_STORE_H_
+#define COMPONENTS_PREFS_PREF_STORE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/prefs/prefs_export.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}  // namespace base
+
+// This is an abstract interface for reading and writing from/to a persistent
+// preference store, used by PrefService. An implementation using a JSON file
+// can be found in JsonPrefStore, while an implementation without any backing
+// store for testing can be found in TestingPrefStore. Furthermore, there is
+// CommandLinePrefStore, which bridges command line options to preferences and
+// ConfigurationPolicyPrefStore, which is used for hooking up configuration
+// policy with the preference subsystem.
+class COMPONENTS_PREFS_EXPORT PrefStore : public base::RefCounted<PrefStore> {
+ public:
+  // Observer interface for monitoring PrefStore.
+  class COMPONENTS_PREFS_EXPORT Observer {
+   public:
+    // Called when the value for the given |key| in the store changes.
+    virtual void OnPrefValueChanged(const std::string& key) = 0;
+    // Notification about the PrefStore being fully initialized.
+    virtual void OnInitializationCompleted(bool succeeded) = 0;
+
+   protected:
+    virtual ~Observer() {}
+  };
+
+  PrefStore() {}
+
+  // Add and remove observers.
+  virtual void AddObserver(Observer* observer) {}
+  virtual void RemoveObserver(Observer* observer) {}
+  virtual bool HasObservers() const;
+
+  // Whether the store has completed all asynchronous initialization.
+  virtual bool IsInitializationComplete() const;
+
+  // Get the value for a given preference |key| and stores it in |*result|.
+  // |*result| is only modified if the return value is true and if |result|
+  // is not NULL. Ownership of the |*result| value remains with the PrefStore.
+  virtual bool GetValue(const std::string& key,
+                        const base::Value** result) const = 0;
+
+  // Get all the values. Never returns a null pointer.
+  virtual std::unique_ptr<base::DictionaryValue> GetValues() const = 0;
+
+ protected:
+  friend class base::RefCounted<PrefStore>;
+  virtual ~PrefStore() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PrefStore);
+};
+
+#endif  // COMPONENTS_PREFS_PREF_STORE_H_
diff --git a/src/components/prefs/pref_store_observer_mock.cc b/src/components/prefs/pref_store_observer_mock.cc
new file mode 100644
index 0000000..a03c858
--- /dev/null
+++ b/src/components/prefs/pref_store_observer_mock.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/prefs/pref_store_observer_mock.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+PrefStoreObserverMock::PrefStoreObserverMock()
+    : initialized(false), initialization_success(false) {}
+
+PrefStoreObserverMock::~PrefStoreObserverMock() {}
+
+void PrefStoreObserverMock::VerifyAndResetChangedKey(
+    const std::string& expected) {
+  EXPECT_EQ(1u, changed_keys.size());
+  if (changed_keys.size() >= 1)
+    EXPECT_EQ(expected, changed_keys.front());
+  changed_keys.clear();
+}
+
+void PrefStoreObserverMock::OnPrefValueChanged(const std::string& key) {
+  changed_keys.push_back(key);
+}
+
+void PrefStoreObserverMock::OnInitializationCompleted(bool success) {
+  initialized = true;
+  initialization_success = success;
+}
diff --git a/src/components/prefs/pref_store_observer_mock.h b/src/components/prefs/pref_store_observer_mock.h
new file mode 100644
index 0000000..01bd6a6
--- /dev/null
+++ b/src/components/prefs/pref_store_observer_mock.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PREFS_PREF_STORE_OBSERVER_MOCK_H_
+#define COMPONENTS_PREFS_PREF_STORE_OBSERVER_MOCK_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "components/prefs/pref_store.h"
+
+// A mock implementation of PrefStore::Observer.
+class PrefStoreObserverMock : public PrefStore::Observer {
+ public:
+  PrefStoreObserverMock();
+  ~PrefStoreObserverMock() override;
+
+  void VerifyAndResetChangedKey(const std::string& expected);
+
+  // PrefStore::Observer implementation
+  void OnPrefValueChanged(const std::string& key) override;
+  void OnInitializationCompleted(bool success) override;
+
+  std::vector<std::string> changed_keys;
+  bool initialized;
+  bool initialization_success;  // Only valid if |initialized|.
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PrefStoreObserverMock);
+};
+
+#endif  // COMPONENTS_PREFS_PREF_STORE_OBSERVER_MOCK_H_
diff --git a/src/components/prefs/pref_value_map.cc b/src/components/prefs/pref_value_map.cc
new file mode 100644
index 0000000..939646f
--- /dev/null
+++ b/src/components/prefs/pref_value_map.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/prefs/pref_value_map.h"
+
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/values.h"
+
+PrefValueMap::PrefValueMap() {}
+
+PrefValueMap::~PrefValueMap() {}
+
+bool PrefValueMap::GetValue(const std::string& key,
+                            const base::Value** value) const {
+  auto it = prefs_.find(key);
+  if (it == prefs_.end())
+    return false;
+
+  if (value)
+    *value = &it->second;
+
+  return true;
+}
+
+bool PrefValueMap::GetValue(const std::string& key, base::Value** value) {
+  auto it = prefs_.find(key);
+  if (it == prefs_.end())
+    return false;
+
+  if (value)
+    *value = &it->second;
+
+  return true;
+}
+
+bool PrefValueMap::SetValue(const std::string& key, base::Value value) {
+  base::Value& existing_value = prefs_[key];
+  if (value == existing_value)
+    return false;
+
+  existing_value = std::move(value);
+  return true;
+}
+
+bool PrefValueMap::RemoveValue(const std::string& key) {
+  return prefs_.erase(key) != 0;
+}
+
+void PrefValueMap::Clear() {
+  prefs_.clear();
+}
+
+void PrefValueMap::Swap(PrefValueMap* other) {
+  prefs_.swap(other->prefs_);
+}
+
+PrefValueMap::iterator PrefValueMap::begin() {
+  return prefs_.begin();
+}
+
+PrefValueMap::iterator PrefValueMap::end() {
+  return prefs_.end();
+}
+
+PrefValueMap::const_iterator PrefValueMap::begin() const {
+  return prefs_.begin();
+}
+
+PrefValueMap::const_iterator PrefValueMap::end() const {
+  return prefs_.end();
+}
+
+bool PrefValueMap::empty() const {
+  return prefs_.empty();
+}
+
+bool PrefValueMap::GetBoolean(const std::string& key, bool* value) const {
+  const base::Value* stored_value = nullptr;
+  return GetValue(key, &stored_value) && stored_value->GetAsBoolean(value);
+}
+
+void PrefValueMap::SetBoolean(const std::string& key, bool value) {
+  SetValue(key, base::Value(value));
+}
+
+bool PrefValueMap::GetString(const std::string& key, std::string* value) const {
+  const base::Value* stored_value = nullptr;
+  return GetValue(key, &stored_value) && stored_value->GetAsString(value);
+}
+
+void PrefValueMap::SetString(const std::string& key, const std::string& value) {
+  SetValue(key, base::Value(value));
+}
+
+bool PrefValueMap::GetInteger(const std::string& key, int* value) const {
+  const base::Value* stored_value = nullptr;
+  return GetValue(key, &stored_value) && stored_value->GetAsInteger(value);
+}
+
+void PrefValueMap::SetInteger(const std::string& key, const int value) {
+  SetValue(key, base::Value(value));
+}
+
+void PrefValueMap::SetDouble(const std::string& key, const double value) {
+  SetValue(key, base::Value(value));
+}
+
+void PrefValueMap::GetDifferingKeys(
+    const PrefValueMap* other,
+    std::vector<std::string>* differing_keys) const {
+  differing_keys->clear();
+
+  // Put everything into ordered maps.
+  std::map<std::string, const base::Value*> this_prefs;
+  std::map<std::string, const base::Value*> other_prefs;
+  for (const auto& pair : prefs_)
+    this_prefs.emplace(pair.first, &pair.second);
+  for (const auto& pair : other->prefs_)
+    other_prefs.emplace(pair.first, &pair.second);
+
+  // Walk over the maps in lockstep, adding everything that is different.
+  auto this_pref = this_prefs.begin();
+  auto other_pref = other_prefs.begin();
+  while (this_pref != this_prefs.end() && other_pref != other_prefs.end()) {
+    const int diff = this_pref->first.compare(other_pref->first);
+    if (diff == 0) {
+      if (!this_pref->second->Equals(other_pref->second))
+        differing_keys->push_back(this_pref->first);
+      ++this_pref;
+      ++other_pref;
+    } else if (diff < 0) {
+      differing_keys->push_back(this_pref->first);
+      ++this_pref;
+    } else if (diff > 0) {
+      differing_keys->push_back(other_pref->first);
+      ++other_pref;
+    }
+  }
+
+  // Add the remaining entries.
+  for (; this_pref != this_prefs.end(); ++this_pref)
+    differing_keys->push_back(this_pref->first);
+  for (; other_pref != other_prefs.end(); ++other_pref)
+    differing_keys->push_back(other_pref->first);
+}
+
+std::unique_ptr<base::DictionaryValue> PrefValueMap::AsDictionaryValue() const {
+  auto dictionary = std::make_unique<base::DictionaryValue>();
+  for (const auto& value : prefs_)
+    dictionary->Set(value.first, value.second.CreateDeepCopy());
+
+  return dictionary;
+}
diff --git a/src/components/prefs/pref_value_map.h b/src/components/prefs/pref_value_map.h
new file mode 100644
index 0000000..300c74f
--- /dev/null
+++ b/src/components/prefs/pref_value_map.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PREFS_PREF_VALUE_MAP_H_
+#define COMPONENTS_PREFS_PREF_VALUE_MAP_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "components/prefs/prefs_export.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}  // namespace base
+
+// A generic string to value map used by the PrefStore implementations.
+class COMPONENTS_PREFS_EXPORT PrefValueMap {
+ public:
+  using Map = std::map<std::string, base::Value>;
+  using iterator = Map::iterator;
+  using const_iterator = Map::const_iterator;
+
+  PrefValueMap();
+  virtual ~PrefValueMap();
+
+  // Gets the value for |key| and stores it in |value|. Ownership remains with
+  // the map. Returns true if a value is present. If not, |value| is not
+  // touched.
+  bool GetValue(const std::string& key, const base::Value** value) const;
+  bool GetValue(const std::string& key, base::Value** value);
+
+  // Sets a new |value| for |key|. Returns true if the value changed.
+  bool SetValue(const std::string& key, base::Value value);
+
+  // Removes the value for |key| from the map. Returns true if a value was
+  // removed.
+  bool RemoveValue(const std::string& key);
+
+  // Clears the map.
+  void Clear();
+
+  // Swaps the contents of two maps.
+  void Swap(PrefValueMap* other);
+
+  iterator begin();
+  iterator end();
+  const_iterator begin() const;
+  const_iterator end() const;
+  bool empty() const;
+
+  // Gets a boolean value for |key| and stores it in |value|. Returns true if
+  // the value was found and of the proper type.
+  bool GetBoolean(const std::string& key, bool* value) const;
+
+  // Sets the value for |key| to the boolean |value|.
+  void SetBoolean(const std::string& key, bool value);
+
+  // Gets a string value for |key| and stores it in |value|. Returns true if
+  // the value was found and of the proper type.
+  bool GetString(const std::string& key, std::string* value) const;
+
+  // Sets the value for |key| to the string |value|.
+  void SetString(const std::string& key, const std::string& value);
+
+  // Gets an int value for |key| and stores it in |value|. Returns true if
+  // the value was found and of the proper type.
+  bool GetInteger(const std::string& key, int* value) const;
+
+  // Sets the value for |key| to the int |value|.
+  void SetInteger(const std::string& key, const int value);
+
+  // Sets the value for |key| to the double |value|.
+  void SetDouble(const std::string& key, const double value);
+
+  // Compares this value map against |other| and stores all key names that have
+  // different values in |differing_keys|. This includes keys that are present
+  // only in one of the maps.
+  void GetDifferingKeys(const PrefValueMap* other,
+                        std::vector<std::string>* differing_keys) const;
+
+  // Copies the map into a dictionary value.
+  std::unique_ptr<base::DictionaryValue> AsDictionaryValue() const;
+
+ private:
+  Map prefs_;
+
+  DISALLOW_COPY_AND_ASSIGN(PrefValueMap);
+};
+
+#endif  // COMPONENTS_PREFS_PREF_VALUE_MAP_H_
diff --git a/src/components/prefs/pref_value_map_unittest.cc b/src/components/prefs/pref_value_map_unittest.cc
new file mode 100644
index 0000000..7d2f05c
--- /dev/null
+++ b/src/components/prefs/pref_value_map_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/prefs/pref_value_map.h"
+
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+TEST(PrefValueMapTest, SetValue) {
+  PrefValueMap map;
+  const Value* result = nullptr;
+  EXPECT_FALSE(map.GetValue("key", &result));
+  EXPECT_FALSE(result);
+
+  EXPECT_TRUE(map.SetValue("key", Value("test")));
+  EXPECT_FALSE(map.SetValue("key", Value("test")));
+  EXPECT_TRUE(map.SetValue("key", Value("hi mom!")));
+
+  EXPECT_TRUE(map.GetValue("key", &result));
+  EXPECT_TRUE(Value("hi mom!").Equals(result));
+}
+
+TEST(PrefValueMapTest, GetAndSetIntegerValue) {
+  PrefValueMap map;
+  ASSERT_TRUE(map.SetValue("key", Value(5)));
+
+  int int_value = 0;
+  EXPECT_TRUE(map.GetInteger("key", &int_value));
+  EXPECT_EQ(5, int_value);
+
+  map.SetInteger("key", -14);
+  EXPECT_TRUE(map.GetInteger("key", &int_value));
+  EXPECT_EQ(-14, int_value);
+}
+
+TEST(PrefValueMapTest, SetDoubleValue) {
+  PrefValueMap map;
+  ASSERT_TRUE(map.SetValue("key", Value(5.5)));
+
+  const Value* result = nullptr;
+  ASSERT_TRUE(map.GetValue("key", &result));
+  double double_value = 0.;
+  EXPECT_TRUE(result->GetAsDouble(&double_value));
+  EXPECT_DOUBLE_EQ(5.5, double_value);
+}
+
+TEST(PrefValueMapTest, RemoveValue) {
+  PrefValueMap map;
+  EXPECT_FALSE(map.RemoveValue("key"));
+
+  EXPECT_TRUE(map.SetValue("key", Value("test")));
+  EXPECT_TRUE(map.GetValue("key", nullptr));
+
+  EXPECT_TRUE(map.RemoveValue("key"));
+  EXPECT_FALSE(map.GetValue("key", nullptr));
+
+  EXPECT_FALSE(map.RemoveValue("key"));
+}
+
+TEST(PrefValueMapTest, Clear) {
+  PrefValueMap map;
+  EXPECT_TRUE(map.SetValue("key", Value("test")));
+  EXPECT_TRUE(map.GetValue("key", nullptr));
+
+  map.Clear();
+
+  EXPECT_FALSE(map.GetValue("key", nullptr));
+}
+
+TEST(PrefValueMapTest, GetDifferingKeys) {
+  PrefValueMap reference;
+  EXPECT_TRUE(reference.SetValue("b", Value("test")));
+  EXPECT_TRUE(reference.SetValue("c", Value("test")));
+  EXPECT_TRUE(reference.SetValue("e", Value("test")));
+
+  PrefValueMap check;
+  std::vector<std::string> differing_paths;
+  std::vector<std::string> expected_differing_paths;
+
+  reference.GetDifferingKeys(&check, &differing_paths);
+  expected_differing_paths.push_back("b");
+  expected_differing_paths.push_back("c");
+  expected_differing_paths.push_back("e");
+  EXPECT_EQ(expected_differing_paths, differing_paths);
+
+  EXPECT_TRUE(check.SetValue("a", Value("test")));
+  EXPECT_TRUE(check.SetValue("c", Value("test")));
+  EXPECT_TRUE(check.SetValue("d", Value("test")));
+
+  reference.GetDifferingKeys(&check, &differing_paths);
+  expected_differing_paths.clear();
+  expected_differing_paths.push_back("a");
+  expected_differing_paths.push_back("b");
+  expected_differing_paths.push_back("d");
+  expected_differing_paths.push_back("e");
+  EXPECT_EQ(expected_differing_paths, differing_paths);
+}
+
+TEST(PrefValueMapTest, SwapTwoMaps) {
+  PrefValueMap first_map;
+  EXPECT_TRUE(first_map.SetValue("a", Value("test")));
+  EXPECT_TRUE(first_map.SetValue("b", Value("test")));
+  EXPECT_TRUE(first_map.SetValue("c", Value("test")));
+
+  PrefValueMap second_map;
+  EXPECT_TRUE(second_map.SetValue("d", Value("test")));
+  EXPECT_TRUE(second_map.SetValue("e", Value("test")));
+  EXPECT_TRUE(second_map.SetValue("f", Value("test")));
+
+  first_map.Swap(&second_map);
+
+  EXPECT_TRUE(first_map.GetValue("d", nullptr));
+  EXPECT_TRUE(first_map.GetValue("e", nullptr));
+  EXPECT_TRUE(first_map.GetValue("f", nullptr));
+
+  EXPECT_TRUE(second_map.GetValue("a", nullptr));
+  EXPECT_TRUE(second_map.GetValue("b", nullptr));
+  EXPECT_TRUE(second_map.GetValue("c", nullptr));
+}
+
+}  // namespace
+}  // namespace base
diff --git a/src/components/prefs/pref_value_store.cc b/src/components/prefs/pref_value_store.cc
new file mode 100644
index 0000000..aa43aba
--- /dev/null
+++ b/src/components/prefs/pref_value_store.cc
@@ -0,0 +1,311 @@
+// Copyright (c) 2012 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 "components/prefs/pref_value_store.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "components/prefs/pref_notifier.h"
+#include "components/prefs/pref_observer.h"
+
+PrefValueStore::PrefStoreKeeper::PrefStoreKeeper()
+    : pref_value_store_(nullptr), type_(PrefValueStore::INVALID_STORE) {}
+
+PrefValueStore::PrefStoreKeeper::~PrefStoreKeeper() {
+  if (pref_store_) {
+    pref_store_->RemoveObserver(this);
+    pref_store_ = nullptr;
+  }
+  pref_value_store_ = nullptr;
+}
+
+void PrefValueStore::PrefStoreKeeper::Initialize(
+    PrefValueStore* store,
+    PrefStore* pref_store,
+    PrefValueStore::PrefStoreType type) {
+  if (pref_store_) {
+    pref_store_->RemoveObserver(this);
+    DCHECK(!pref_store_->HasObservers());
+  }
+  type_ = type;
+  pref_value_store_ = store;
+  pref_store_ = pref_store;
+  if (pref_store_)
+    pref_store_->AddObserver(this);
+}
+
+void PrefValueStore::PrefStoreKeeper::OnPrefValueChanged(
+    const std::string& key) {
+  pref_value_store_->OnPrefValueChanged(type_, key);
+}
+
+void PrefValueStore::PrefStoreKeeper::OnInitializationCompleted(
+    bool succeeded) {
+  pref_value_store_->OnInitializationCompleted(type_, succeeded);
+}
+
+PrefValueStore::PrefValueStore(PrefStore* managed_prefs,
+                               PrefStore* supervised_user_prefs,
+                               PrefStore* extension_prefs,
+                               PrefStore* command_line_prefs,
+                               PrefStore* user_prefs,
+                               PrefStore* recommended_prefs,
+                               PrefStore* default_prefs,
+                               PrefNotifier* pref_notifier,
+                               std::unique_ptr<Delegate> delegate)
+    : pref_notifier_(pref_notifier),
+      initialization_failed_(false),
+      delegate_(std::move(delegate)) {
+  InitPrefStore(MANAGED_STORE, managed_prefs);
+  InitPrefStore(SUPERVISED_USER_STORE, supervised_user_prefs);
+  InitPrefStore(EXTENSION_STORE, extension_prefs);
+  InitPrefStore(COMMAND_LINE_STORE, command_line_prefs);
+  InitPrefStore(USER_STORE, user_prefs);
+  InitPrefStore(RECOMMENDED_STORE, recommended_prefs);
+  InitPrefStore(DEFAULT_STORE, default_prefs);
+
+  CheckInitializationCompleted();
+  if (delegate_) {
+    delegate_->Init(managed_prefs, supervised_user_prefs, extension_prefs,
+                    command_line_prefs, user_prefs, recommended_prefs,
+                    default_prefs, pref_notifier);
+  }
+}
+
+PrefValueStore::~PrefValueStore() {}
+
+std::unique_ptr<PrefValueStore> PrefValueStore::CloneAndSpecialize(
+    PrefStore* managed_prefs,
+    PrefStore* supervised_user_prefs,
+    PrefStore* extension_prefs,
+    PrefStore* command_line_prefs,
+    PrefStore* user_prefs,
+    PrefStore* recommended_prefs,
+    PrefStore* default_prefs,
+    PrefNotifier* pref_notifier,
+    std::unique_ptr<Delegate> delegate) {
+  DCHECK(pref_notifier);
+  if (!managed_prefs)
+    managed_prefs = GetPrefStore(MANAGED_STORE);
+  if (!supervised_user_prefs)
+    supervised_user_prefs = GetPrefStore(SUPERVISED_USER_STORE);
+  if (!extension_prefs)
+    extension_prefs = GetPrefStore(EXTENSION_STORE);
+  if (!command_line_prefs)
+    command_line_prefs = GetPrefStore(COMMAND_LINE_STORE);
+  if (!user_prefs)
+    user_prefs = GetPrefStore(USER_STORE);
+  if (!recommended_prefs)
+    recommended_prefs = GetPrefStore(RECOMMENDED_STORE);
+  if (!default_prefs)
+    default_prefs = GetPrefStore(DEFAULT_STORE);
+
+  return std::make_unique<PrefValueStore>(
+      managed_prefs, supervised_user_prefs, extension_prefs, command_line_prefs,
+      user_prefs, recommended_prefs, default_prefs, pref_notifier,
+      std::move(delegate));
+}
+
+void PrefValueStore::set_callback(const PrefChangedCallback& callback) {
+  pref_changed_callback_ = callback;
+}
+
+bool PrefValueStore::GetValue(const std::string& name,
+                              base::Value::Type type,
+                              const base::Value** out_value) const {
+  // Check the |PrefStore|s in order of their priority from highest to lowest,
+  // looking for the first preference value with the given |name| and |type|.
+  for (size_t i = 0; i <= PREF_STORE_TYPE_MAX; ++i) {
+    if (GetValueFromStoreWithType(name, type, static_cast<PrefStoreType>(i),
+                                  out_value))
+      return true;
+  }
+  return false;
+}
+
+bool PrefValueStore::GetRecommendedValue(const std::string& name,
+                                         base::Value::Type type,
+                                         const base::Value** out_value) const {
+  return GetValueFromStoreWithType(name, type, RECOMMENDED_STORE, out_value);
+}
+
+void PrefValueStore::NotifyPrefChanged(
+    const std::string& path,
+    PrefValueStore::PrefStoreType new_store) {
+  DCHECK(new_store != INVALID_STORE);
+  // A notification is sent when the pref value in any store changes. If this
+  // store is currently being overridden by a higher-priority store, the
+  // effective value of the pref will not have changed.
+  pref_notifier_->OnPreferenceChanged(path);
+  if (!pref_changed_callback_.is_null())
+    pref_changed_callback_.Run(path);
+}
+
+bool PrefValueStore::PrefValueInManagedStore(const std::string& name) const {
+  return PrefValueInStore(name, MANAGED_STORE);
+}
+
+bool PrefValueStore::PrefValueInSupervisedStore(const std::string& name) const {
+  return PrefValueInStore(name, SUPERVISED_USER_STORE);
+}
+
+bool PrefValueStore::PrefValueInExtensionStore(const std::string& name) const {
+  return PrefValueInStore(name, EXTENSION_STORE);
+}
+
+bool PrefValueStore::PrefValueInUserStore(const std::string& name) const {
+  return PrefValueInStore(name, USER_STORE);
+}
+
+bool PrefValueStore::PrefValueFromExtensionStore(
+    const std::string& name) const {
+  return ControllingPrefStoreForPref(name) == EXTENSION_STORE;
+}
+
+bool PrefValueStore::PrefValueFromUserStore(const std::string& name) const {
+  return ControllingPrefStoreForPref(name) == USER_STORE;
+}
+
+bool PrefValueStore::PrefValueFromRecommendedStore(
+    const std::string& name) const {
+  return ControllingPrefStoreForPref(name) == RECOMMENDED_STORE;
+}
+
+bool PrefValueStore::PrefValueFromDefaultStore(const std::string& name) const {
+  return ControllingPrefStoreForPref(name) == DEFAULT_STORE;
+}
+
+bool PrefValueStore::PrefValueUserModifiable(const std::string& name) const {
+  PrefStoreType effective_store = ControllingPrefStoreForPref(name);
+  return effective_store >= USER_STORE || effective_store == INVALID_STORE;
+}
+
+bool PrefValueStore::PrefValueExtensionModifiable(
+    const std::string& name) const {
+  PrefStoreType effective_store = ControllingPrefStoreForPref(name);
+  return effective_store >= EXTENSION_STORE || effective_store == INVALID_STORE;
+}
+
+void PrefValueStore::UpdateCommandLinePrefStore(PrefStore* command_line_prefs) {
+  InitPrefStore(COMMAND_LINE_STORE, command_line_prefs);
+  if (delegate_)
+    delegate_->UpdateCommandLinePrefStore(command_line_prefs);
+}
+
+bool PrefValueStore::IsInitializationComplete() const {
+  for (size_t i = 0; i <= PREF_STORE_TYPE_MAX; ++i) {
+    const PrefStore* pref_store = GetPrefStore(static_cast<PrefStoreType>(i));
+    if (pref_store && !pref_store->IsInitializationComplete())
+      return false;
+  }
+  return true;
+}
+
+bool PrefValueStore::HasPrefStore(PrefStoreType type) const {
+  return GetPrefStore(type);
+}
+
+bool PrefValueStore::PrefValueInStore(
+    const std::string& name,
+    PrefValueStore::PrefStoreType store) const {
+  // Declare a temp Value* and call GetValueFromStore,
+  // ignoring the output value.
+  const base::Value* tmp_value = nullptr;
+  return GetValueFromStore(name, store, &tmp_value);
+}
+
+bool PrefValueStore::PrefValueInStoreRange(
+    const std::string& name,
+    PrefValueStore::PrefStoreType first_checked_store,
+    PrefValueStore::PrefStoreType last_checked_store) const {
+  if (first_checked_store > last_checked_store) {
+    NOTREACHED();
+    return false;
+  }
+
+  for (size_t i = first_checked_store;
+       i <= static_cast<size_t>(last_checked_store); ++i) {
+    if (PrefValueInStore(name, static_cast<PrefStoreType>(i)))
+      return true;
+  }
+  return false;
+}
+
+PrefValueStore::PrefStoreType PrefValueStore::ControllingPrefStoreForPref(
+    const std::string& name) const {
+  for (size_t i = 0; i <= PREF_STORE_TYPE_MAX; ++i) {
+    if (PrefValueInStore(name, static_cast<PrefStoreType>(i)))
+      return static_cast<PrefStoreType>(i);
+  }
+  return INVALID_STORE;
+}
+
+bool PrefValueStore::GetValueFromStore(const std::string& name,
+                                       PrefValueStore::PrefStoreType store_type,
+                                       const base::Value** out_value) const {
+  // Only return true if we find a value and it is the correct type, so stale
+  // values with the incorrect type will be ignored.
+  const PrefStore* store = GetPrefStore(static_cast<PrefStoreType>(store_type));
+  if (store && store->GetValue(name, out_value))
+    return true;
+
+  // No valid value found for the given preference name: set the return value
+  // to false.
+  *out_value = nullptr;
+  return false;
+}
+
+bool PrefValueStore::GetValueFromStoreWithType(
+    const std::string& name,
+    base::Value::Type type,
+    PrefStoreType store,
+    const base::Value** out_value) const {
+  if (GetValueFromStore(name, store, out_value)) {
+    if ((*out_value)->type() == type)
+      return true;
+
+    LOG(WARNING) << "Expected type for " << name << " is " << type
+                 << " but got " << (*out_value)->type() << " in store "
+                 << store;
+  }
+
+  *out_value = nullptr;
+  return false;
+}
+
+void PrefValueStore::OnPrefValueChanged(PrefValueStore::PrefStoreType type,
+                                        const std::string& key) {
+  NotifyPrefChanged(key, type);
+}
+
+void PrefValueStore::OnInitializationCompleted(
+    PrefValueStore::PrefStoreType type,
+    bool succeeded) {
+  if (initialization_failed_)
+    return;
+  if (!succeeded) {
+    initialization_failed_ = true;
+    pref_notifier_->OnInitializationCompleted(false);
+    return;
+  }
+  CheckInitializationCompleted();
+}
+
+void PrefValueStore::InitPrefStore(PrefValueStore::PrefStoreType type,
+                                   PrefStore* pref_store) {
+  pref_stores_[type].Initialize(this, pref_store, type);
+}
+
+void PrefValueStore::CheckInitializationCompleted() {
+  if (initialization_failed_)
+    return;
+  for (size_t i = 0; i <= PREF_STORE_TYPE_MAX; ++i) {
+    scoped_refptr<PrefStore> store =
+        GetPrefStore(static_cast<PrefStoreType>(i));
+    if (store.get() && !store->IsInitializationComplete())
+      return;
+  }
+  pref_notifier_->OnInitializationCompleted(true);
+}
diff --git a/src/components/prefs/pref_value_store.h b/src/components/prefs/pref_value_store.h
new file mode 100644
index 0000000..70a92bc
--- /dev/null
+++ b/src/components/prefs/pref_value_store.h
@@ -0,0 +1,318 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_PREF_VALUE_STORE_H_
+#define COMPONENTS_PREFS_PREF_VALUE_STORE_H_
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "components/prefs/pref_store.h"
+#include "components/prefs/prefs_export.h"
+
+class PersistentPrefStore;
+class PrefNotifier;
+class PrefRegistry;
+class PrefStore;
+
+// The PrefValueStore manages various sources of values for Preferences
+// (e.g., configuration policies, extensions, and user settings). It returns
+// the value of a Preference from the source with the highest priority, and
+// allows setting user-defined values for preferences that are not managed.
+//
+// Unless otherwise explicitly noted, all of the methods of this class must
+// be called on the UI thread.
+class COMPONENTS_PREFS_EXPORT PrefValueStore {
+ public:
+  typedef base::Callback<void(const std::string&)> PrefChangedCallback;
+
+  // Delegate used to observe certain events in the |PrefValueStore|'s lifetime.
+  class Delegate {
+   public:
+    virtual ~Delegate() {}
+
+    // Called by the PrefValueStore constructor with the PrefStores passed to
+    // it.
+    virtual void Init(PrefStore* managed_prefs,
+                      PrefStore* supervised_user_prefs,
+                      PrefStore* extension_prefs,
+                      PrefStore* command_line_prefs,
+                      PrefStore* user_prefs,
+                      PrefStore* recommended_prefs,
+                      PrefStore* default_prefs,
+                      PrefNotifier* pref_notifier) = 0;
+
+    virtual void InitIncognitoUserPrefs(
+        scoped_refptr<PersistentPrefStore> incognito_user_prefs_overlay,
+        scoped_refptr<PersistentPrefStore> incognito_user_prefs_underlay,
+        const std::vector<const char*>& overlay_pref_names) = 0;
+
+    virtual void InitPrefRegistry(PrefRegistry* pref_registry) = 0;
+
+    // Called whenever PrefValueStore::UpdateCommandLinePrefStore is called,
+    // with the same argument.
+    virtual void UpdateCommandLinePrefStore(PrefStore* command_line_prefs) = 0;
+  };
+
+  // PrefStores must be listed here in order from highest to lowest priority.
+  //   MANAGED contains all managed preference values that are provided by
+  //      mandatory policies (e.g. Windows Group Policy or cloud policy).
+  //   SUPERVISED_USER contains preferences that are valid for supervised users.
+  //   EXTENSION contains preference values set by extensions.
+  //   COMMAND_LINE contains preference values set by command-line switches.
+  //   USER contains all user-set preference values.
+  //   RECOMMENDED contains all preferences that are provided by recommended
+  //      policies.
+  //   DEFAULT contains all application default preference values.
+  enum PrefStoreType {
+    // INVALID_STORE is not associated with an actual PrefStore but used as
+    // an invalid marker, e.g. as a return value.
+    INVALID_STORE = -1,
+    MANAGED_STORE = 0,
+    SUPERVISED_USER_STORE,
+    EXTENSION_STORE,
+    COMMAND_LINE_STORE,
+    USER_STORE,
+    RECOMMENDED_STORE,
+    DEFAULT_STORE,
+    PREF_STORE_TYPE_MAX = DEFAULT_STORE
+  };
+
+  // In decreasing order of precedence:
+  //   |managed_prefs| contains all preferences from mandatory policies.
+  //   |supervised_user_prefs| contains all preferences from supervised user
+  //        settings, i.e. settings configured for a supervised user by their
+  //        custodian.
+  //   |extension_prefs| contains preference values set by extensions.
+  //   |command_line_prefs| contains preference values set by command-line
+  //        switches.
+  //   |user_prefs| contains all user-set preference values.
+  //   |recommended_prefs| contains all preferences from recommended policies.
+  //   |default_prefs| contains application-default preference values. It must
+  //        be non-null if any preferences are to be registered.
+  //
+  // |pref_notifier| facilitates broadcasting preference change notifications
+  // to the world.
+  PrefValueStore(PrefStore* managed_prefs,
+                 PrefStore* supervised_user_prefs,
+                 PrefStore* extension_prefs,
+                 PrefStore* command_line_prefs,
+                 PrefStore* user_prefs,
+                 PrefStore* recommended_prefs,
+                 PrefStore* default_prefs,
+                 PrefNotifier* pref_notifier,
+                 std::unique_ptr<Delegate> delegate = nullptr);
+  virtual ~PrefValueStore();
+
+  // Creates a clone of this PrefValueStore with PrefStores overwritten
+  // by the parameters passed, if unequal NULL.
+  //
+  // The new PrefValueStore is passed the |delegate| in its constructor.
+  std::unique_ptr<PrefValueStore> CloneAndSpecialize(
+      PrefStore* managed_prefs,
+      PrefStore* supervised_user_prefs,
+      PrefStore* extension_prefs,
+      PrefStore* command_line_prefs,
+      PrefStore* user_prefs,
+      PrefStore* recommended_prefs,
+      PrefStore* default_prefs,
+      PrefNotifier* pref_notifier,
+      std::unique_ptr<Delegate> delegate = nullptr);
+
+  // A PrefValueStore can have exactly one callback that is directly
+  // notified of preferences changing in the store. This does not
+  // filter through the PrefNotifier mechanism, which may not forward
+  // certain changes (e.g. unregistered prefs).
+  void set_callback(const PrefChangedCallback& callback);
+
+  // Gets the value for the given preference name that has the specified value
+  // type. Values stored in a PrefStore that have the matching |name| but
+  // a non-matching |type| are silently skipped. Returns true if a valid value
+  // was found in any of the available PrefStores. Most callers should use
+  // Preference::GetValue() instead of calling this method directly.
+  bool GetValue(const std::string& name,
+                base::Value::Type type,
+                const base::Value** out_value) const;
+
+  // Gets the recommended value for the given preference name that has the
+  // specified value type. A value stored in the recommended PrefStore that has
+  // the matching |name| but a non-matching |type| is silently ignored. Returns
+  // true if a valid value was found. Most callers should use
+  // Preference::GetRecommendedValue() instead of calling this method directly.
+  bool GetRecommendedValue(const std::string& name,
+                           base::Value::Type type,
+                           const base::Value** out_value) const;
+
+  // These methods return true if a preference with the given name is in the
+  // indicated pref store, even if that value is currently being overridden by
+  // a higher-priority source.
+  bool PrefValueInManagedStore(const std::string& name) const;
+  bool PrefValueInSupervisedStore(const std::string& name) const;
+  bool PrefValueInExtensionStore(const std::string& name) const;
+  bool PrefValueInUserStore(const std::string& name) const;
+
+  // These methods return true if a preference with the given name is actually
+  // being controlled by the indicated pref store and not being overridden by
+  // a higher-priority source.
+  bool PrefValueFromExtensionStore(const std::string& name) const;
+  bool PrefValueFromUserStore(const std::string& name) const;
+  bool PrefValueFromRecommendedStore(const std::string& name) const;
+  bool PrefValueFromDefaultStore(const std::string& name) const;
+
+  // Check whether a Preference value is modifiable by the user, i.e. whether
+  // there is no higher-priority source controlling it.
+  bool PrefValueUserModifiable(const std::string& name) const;
+
+  // Check whether a Preference value is modifiable by an extension, i.e.
+  // whether there is no higher-priority source controlling it.
+  bool PrefValueExtensionModifiable(const std::string& name) const;
+
+  // Update the command line PrefStore with |command_line_prefs|.
+  void UpdateCommandLinePrefStore(PrefStore* command_line_prefs);
+
+  bool IsInitializationComplete() const;
+
+  // Check whether a particular type of PrefStore exists.
+  bool HasPrefStore(PrefStoreType type) const;
+
+ private:
+  // Keeps a PrefStore reference on behalf of the PrefValueStore and monitors
+  // the PrefStore for changes, forwarding notifications to PrefValueStore. This
+  // indirection is here for the sake of disambiguating notifications from the
+  // individual PrefStores.
+  class PrefStoreKeeper : public PrefStore::Observer {
+   public:
+    PrefStoreKeeper();
+    ~PrefStoreKeeper() override;
+
+    // Takes ownership of |pref_store|.
+    void Initialize(PrefValueStore* store,
+                    PrefStore* pref_store,
+                    PrefStoreType type);
+
+    PrefStore* store() { return pref_store_.get(); }
+    const PrefStore* store() const { return pref_store_.get(); }
+
+   private:
+    // PrefStore::Observer implementation.
+    void OnPrefValueChanged(const std::string& key) override;
+    void OnInitializationCompleted(bool succeeded) override;
+
+    // PrefValueStore this keeper is part of.
+    PrefValueStore* pref_value_store_;
+
+    // The PrefStore managed by this keeper.
+    scoped_refptr<PrefStore> pref_store_;
+
+    // Type of the pref store.
+    PrefStoreType type_;
+
+    DISALLOW_COPY_AND_ASSIGN(PrefStoreKeeper);
+  };
+
+  typedef std::map<std::string, base::Value::Type> PrefTypeMap;
+
+  // Returns true if the preference with the given name has a value in the
+  // given PrefStoreType, of the same value type as the preference was
+  // registered with.
+  bool PrefValueInStore(const std::string& name, PrefStoreType store) const;
+
+  // Returns true if a preference has an explicit value in any of the
+  // stores in the range specified by |first_checked_store| and
+  // |last_checked_store|, even if that value is currently being
+  // overridden by a higher-priority store.
+  bool PrefValueInStoreRange(const std::string& name,
+                             PrefStoreType first_checked_store,
+                             PrefStoreType last_checked_store) const;
+
+  // Returns the pref store type identifying the source that controls the
+  // Preference identified by |name|. If none of the sources has a value,
+  // INVALID_STORE is returned. In practice, the default PrefStore
+  // should always have a value for any registered preferencem, so INVALID_STORE
+  // indicates an error.
+  PrefStoreType ControllingPrefStoreForPref(const std::string& name) const;
+
+  // Get a value from the specified |store|.
+  bool GetValueFromStore(const std::string& name,
+                         PrefStoreType store,
+                         const base::Value** out_value) const;
+
+  // Get a value from the specified |store| if its |type| matches.
+  bool GetValueFromStoreWithType(const std::string& name,
+                                 base::Value::Type type,
+                                 PrefStoreType store,
+                                 const base::Value** out_value) const;
+
+  // Called upon changes in individual pref stores in order to determine whether
+  // the user-visible pref value has changed. Triggers the change notification
+  // if the effective value of the preference has changed, or if the store
+  // controlling the pref has changed.
+  void NotifyPrefChanged(const std::string& path, PrefStoreType new_store);
+
+  // Called from the PrefStoreKeeper implementation when a pref value for |key|
+  // changed in the pref store for |type|.
+  void OnPrefValueChanged(PrefStoreType type, const std::string& key);
+
+  // Handle the event that the store for |type| has completed initialization.
+  void OnInitializationCompleted(PrefStoreType type, bool succeeded);
+
+  // Initializes a pref store keeper. Sets up a PrefStoreKeeper that will take
+  // ownership of the passed |pref_store|.
+  void InitPrefStore(PrefStoreType type, PrefStore* pref_store);
+
+  // Checks whether initialization is completed and tells the notifier if that
+  // is the case.
+  void CheckInitializationCompleted();
+
+  // Get the PrefStore pointer for the given type. May return NULL if there is
+  // no PrefStore for that type.
+  PrefStore* GetPrefStore(PrefStoreType type) {
+    return pref_stores_[type].store();
+  }
+  const PrefStore* GetPrefStore(PrefStoreType type) const {
+    return pref_stores_[type].store();
+  }
+
+  // Keeps the PrefStore references in order of precedence.
+  PrefStoreKeeper pref_stores_[PREF_STORE_TYPE_MAX + 1];
+
+  PrefChangedCallback pref_changed_callback_;
+
+  // Used for generating notifications. This is a weak reference,
+  // since the notifier is owned by the corresponding PrefService.
+  PrefNotifier* pref_notifier_;
+
+  // A mapping of preference names to their registered types.
+  PrefTypeMap pref_types_;
+
+  // True if not all of the PrefStores were initialized successfully.
+  bool initialization_failed_;
+
+  // Might be null.
+  std::unique_ptr<Delegate> delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(PrefValueStore);
+};
+
+namespace std {
+
+template <>
+struct hash<PrefValueStore::PrefStoreType> {
+  size_t operator()(PrefValueStore::PrefStoreType type) const {
+    return std::hash<
+        std::underlying_type<PrefValueStore::PrefStoreType>::type>()(type);
+  }
+};
+
+}  // namespace std
+
+#endif  // COMPONENTS_PREFS_PREF_VALUE_STORE_H_
diff --git a/src/components/prefs/pref_value_store_unittest.cc b/src/components/prefs/pref_value_store_unittest.cc
new file mode 100644
index 0000000..dff37f7
--- /dev/null
+++ b/src/components/prefs/pref_value_store_unittest.cc
@@ -0,0 +1,607 @@
+// Copyright (c) 2012 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 "components/prefs/pref_value_store.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "components/prefs/pref_notifier.h"
+#include "components/prefs/testing_pref_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Mock;
+using testing::_;
+
+namespace {
+
+// Allows to capture pref notifications through gmock.
+class MockPrefNotifier : public PrefNotifier {
+ public:
+  MOCK_METHOD1(OnPreferenceChanged, void(const std::string&));
+  MOCK_METHOD1(OnInitializationCompleted, void(bool));
+};
+
+// Allows to capture sync model associator interaction.
+class MockPrefModelAssociator {
+ public:
+  MOCK_METHOD1(ProcessPrefChange, void(const std::string&));
+};
+
+}  // namespace
+
+// Names of the preferences used in this test.
+namespace prefs {
+const char kManagedPref[] = "this.pref.managed";
+const char kSupervisedUserPref[] = "this.pref.supervised_user";
+const char kCommandLinePref[] = "this.pref.command_line";
+const char kExtensionPref[] = "this.pref.extension";
+const char kUserPref[] = "this.pref.user";
+const char kRecommendedPref[] = "this.pref.recommended";
+const char kDefaultPref[] = "this.pref.default";
+const char kMissingPref[] = "this.pref.does_not_exist";
+}  // namespace prefs
+
+// Potentially expected values of all preferences used in this test program.
+namespace managed_pref {
+const char kManagedValue[] = "managed:managed";
+}
+
+namespace supervised_user_pref {
+const char kManagedValue[] = "supervised_user:managed";
+const char kSupervisedUserValue[] = "supervised_user:supervised_user";
+}  // namespace supervised_user_pref
+
+namespace extension_pref {
+const char kManagedValue[] = "extension:managed";
+const char kSupervisedUserValue[] = "extension:supervised_user";
+const char kExtensionValue[] = "extension:extension";
+}  // namespace extension_pref
+
+namespace command_line_pref {
+const char kManagedValue[] = "command_line:managed";
+const char kSupervisedUserValue[] = "command_line:supervised_user";
+const char kExtensionValue[] = "command_line:extension";
+const char kCommandLineValue[] = "command_line:command_line";
+}  // namespace command_line_pref
+
+namespace user_pref {
+const char kManagedValue[] = "user:managed";
+const char kSupervisedUserValue[] = "supervised_user:supervised_user";
+const char kExtensionValue[] = "user:extension";
+const char kCommandLineValue[] = "user:command_line";
+const char kUserValue[] = "user:user";
+}  // namespace user_pref
+
+namespace recommended_pref {
+const char kManagedValue[] = "recommended:managed";
+const char kSupervisedUserValue[] = "recommended:supervised_user";
+const char kExtensionValue[] = "recommended:extension";
+const char kCommandLineValue[] = "recommended:command_line";
+const char kUserValue[] = "recommended:user";
+const char kRecommendedValue[] = "recommended:recommended";
+}  // namespace recommended_pref
+
+namespace default_pref {
+const char kManagedValue[] = "default:managed";
+const char kSupervisedUserValue[] = "default:supervised_user";
+const char kExtensionValue[] = "default:extension";
+const char kCommandLineValue[] = "default:command_line";
+const char kUserValue[] = "default:user";
+const char kRecommendedValue[] = "default:recommended";
+const char kDefaultValue[] = "default:default";
+}  // namespace default_pref
+
+class PrefValueStoreTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    // Create TestingPrefStores.
+    CreateManagedPrefs();
+    CreateSupervisedUserPrefs();
+    CreateExtensionPrefs();
+    CreateCommandLinePrefs();
+    CreateUserPrefs();
+    CreateRecommendedPrefs();
+    CreateDefaultPrefs();
+    sync_associator_.reset(new MockPrefModelAssociator());
+
+    // Create a fresh PrefValueStore.
+    pref_value_store_.reset(new PrefValueStore(
+        managed_pref_store_.get(), supervised_user_pref_store_.get(),
+        extension_pref_store_.get(), command_line_pref_store_.get(),
+        user_pref_store_.get(), recommended_pref_store_.get(),
+        default_pref_store_.get(), &pref_notifier_));
+
+    pref_value_store_->set_callback(
+        base::Bind(&MockPrefModelAssociator::ProcessPrefChange,
+                   base::Unretained(sync_associator_.get())));
+  }
+
+  void CreateManagedPrefs() {
+    managed_pref_store_ = new TestingPrefStore;
+    managed_pref_store_->SetString(prefs::kManagedPref,
+                                   managed_pref::kManagedValue);
+  }
+
+  void CreateSupervisedUserPrefs() {
+    supervised_user_pref_store_ = new TestingPrefStore;
+    supervised_user_pref_store_->SetString(prefs::kManagedPref,
+                                           supervised_user_pref::kManagedValue);
+    supervised_user_pref_store_->SetString(
+        prefs::kSupervisedUserPref, supervised_user_pref::kSupervisedUserValue);
+  }
+
+  void CreateExtensionPrefs() {
+    extension_pref_store_ = new TestingPrefStore;
+    extension_pref_store_->SetString(prefs::kManagedPref,
+                                     extension_pref::kManagedValue);
+    extension_pref_store_->SetString(prefs::kSupervisedUserPref,
+                                     extension_pref::kSupervisedUserValue);
+    extension_pref_store_->SetString(prefs::kExtensionPref,
+                                     extension_pref::kExtensionValue);
+  }
+
+  void CreateCommandLinePrefs() {
+    command_line_pref_store_ = new TestingPrefStore;
+
+    command_line_pref_store_->SetString(prefs::kManagedPref,
+                                        command_line_pref::kManagedValue);
+    command_line_pref_store_->SetString(
+        prefs::kSupervisedUserPref, command_line_pref::kSupervisedUserValue);
+    command_line_pref_store_->SetString(prefs::kExtensionPref,
+                                        command_line_pref::kExtensionValue);
+    command_line_pref_store_->SetString(prefs::kCommandLinePref,
+                                        command_line_pref::kCommandLineValue);
+  }
+
+  void CreateUserPrefs() {
+    user_pref_store_ = new TestingPrefStore;
+    user_pref_store_->SetString(prefs::kManagedPref, user_pref::kManagedValue);
+    user_pref_store_->SetString(prefs::kSupervisedUserPref,
+                                user_pref::kSupervisedUserValue);
+    user_pref_store_->SetString(prefs::kCommandLinePref,
+                                user_pref::kCommandLineValue);
+    user_pref_store_->SetString(prefs::kExtensionPref,
+                                user_pref::kExtensionValue);
+    user_pref_store_->SetString(prefs::kUserPref, user_pref::kUserValue);
+  }
+
+  void CreateRecommendedPrefs() {
+    recommended_pref_store_ = new TestingPrefStore;
+    recommended_pref_store_->SetString(prefs::kManagedPref,
+                                       recommended_pref::kManagedValue);
+    recommended_pref_store_->SetString(prefs::kSupervisedUserPref,
+                                       recommended_pref::kSupervisedUserValue);
+    recommended_pref_store_->SetString(prefs::kCommandLinePref,
+                                       recommended_pref::kCommandLineValue);
+    recommended_pref_store_->SetString(prefs::kExtensionPref,
+                                       recommended_pref::kExtensionValue);
+    recommended_pref_store_->SetString(prefs::kUserPref,
+                                       recommended_pref::kUserValue);
+    recommended_pref_store_->SetString(prefs::kRecommendedPref,
+                                       recommended_pref::kRecommendedValue);
+  }
+
+  void CreateDefaultPrefs() {
+    default_pref_store_ = new TestingPrefStore;
+    default_pref_store_->SetString(prefs::kSupervisedUserPref,
+                                   default_pref::kSupervisedUserValue);
+    default_pref_store_->SetString(prefs::kManagedPref,
+                                   default_pref::kManagedValue);
+    default_pref_store_->SetString(prefs::kCommandLinePref,
+                                   default_pref::kCommandLineValue);
+    default_pref_store_->SetString(prefs::kExtensionPref,
+                                   default_pref::kExtensionValue);
+    default_pref_store_->SetString(prefs::kUserPref, default_pref::kUserValue);
+    default_pref_store_->SetString(prefs::kRecommendedPref,
+                                   default_pref::kRecommendedValue);
+    default_pref_store_->SetString(prefs::kDefaultPref,
+                                   default_pref::kDefaultValue);
+  }
+
+  void ExpectValueChangeNotifications(const std::string& name) {
+    EXPECT_CALL(pref_notifier_, OnPreferenceChanged(name));
+    EXPECT_CALL(*sync_associator_, ProcessPrefChange(name));
+  }
+
+  void CheckAndClearValueChangeNotifications() {
+    Mock::VerifyAndClearExpectations(&pref_notifier_);
+    Mock::VerifyAndClearExpectations(sync_associator_.get());
+  }
+
+  MockPrefNotifier pref_notifier_;
+  std::unique_ptr<MockPrefModelAssociator> sync_associator_;
+  std::unique_ptr<PrefValueStore> pref_value_store_;
+
+  scoped_refptr<TestingPrefStore> managed_pref_store_;
+  scoped_refptr<TestingPrefStore> supervised_user_pref_store_;
+  scoped_refptr<TestingPrefStore> extension_pref_store_;
+  scoped_refptr<TestingPrefStore> command_line_pref_store_;
+  scoped_refptr<TestingPrefStore> user_pref_store_;
+  scoped_refptr<TestingPrefStore> recommended_pref_store_;
+  scoped_refptr<TestingPrefStore> default_pref_store_;
+};
+
+TEST_F(PrefValueStoreTest, GetValue) {
+  const base::Value* value;
+
+  // The following tests read a value from the PrefService. The preferences are
+  // set in a way such that all lower-priority stores have a value and we can
+  // test whether overrides work correctly.
+
+  // Test getting a managed value.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetValue(prefs::kManagedPref,
+                                          base::Value::Type::STRING, &value));
+  std::string actual_str_value;
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(managed_pref::kManagedValue, actual_str_value);
+
+  // Test getting a supervised user value.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetValue(prefs::kSupervisedUserPref,
+                                          base::Value::Type::STRING, &value));
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(supervised_user_pref::kSupervisedUserValue, actual_str_value);
+
+  // Test getting an extension value.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetValue(prefs::kExtensionPref,
+                                          base::Value::Type::STRING, &value));
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(extension_pref::kExtensionValue, actual_str_value);
+
+  // Test getting a command-line value.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetValue(prefs::kCommandLinePref,
+                                          base::Value::Type::STRING, &value));
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(command_line_pref::kCommandLineValue, actual_str_value);
+
+  // Test getting a user-set value.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetValue(prefs::kUserPref,
+                                          base::Value::Type::STRING, &value));
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(user_pref::kUserValue, actual_str_value);
+
+  // Test getting a user set value overwriting a recommended value.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetValue(prefs::kRecommendedPref,
+                                          base::Value::Type::STRING, &value));
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(recommended_pref::kRecommendedValue, actual_str_value);
+
+  // Test getting a default value.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetValue(prefs::kDefaultPref,
+                                          base::Value::Type::STRING, &value));
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(default_pref::kDefaultValue, actual_str_value);
+
+  // Test getting a preference value that the |PrefValueStore|
+  // does not contain.
+  base::Value tmp_dummy_value(true);
+  value = &tmp_dummy_value;
+  ASSERT_FALSE(pref_value_store_->GetValue(prefs::kMissingPref,
+                                           base::Value::Type::STRING, &value));
+  ASSERT_FALSE(value);
+}
+
+TEST_F(PrefValueStoreTest, GetRecommendedValue) {
+  const base::Value* value;
+
+  // The following tests read a value from the PrefService. The preferences are
+  // set in a way such that all lower-priority stores have a value and we can
+  // test whether overrides do not clutter the recommended value.
+
+  // Test getting recommended value when a managed value is present.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetRecommendedValue(
+      prefs::kManagedPref, base::Value::Type::STRING, &value));
+  std::string actual_str_value;
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(recommended_pref::kManagedValue, actual_str_value);
+
+  // Test getting recommended value when a supervised user value is present.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetRecommendedValue(
+      prefs::kSupervisedUserPref, base::Value::Type::STRING, &value));
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(recommended_pref::kSupervisedUserValue, actual_str_value);
+
+  // Test getting recommended value when an extension value is present.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetRecommendedValue(
+      prefs::kExtensionPref, base::Value::Type::STRING, &value));
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(recommended_pref::kExtensionValue, actual_str_value);
+
+  // Test getting recommended value when a command-line value is present.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetRecommendedValue(
+      prefs::kCommandLinePref, base::Value::Type::STRING, &value));
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(recommended_pref::kCommandLineValue, actual_str_value);
+
+  // Test getting recommended value when a user-set value is present.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetRecommendedValue(
+      prefs::kUserPref, base::Value::Type::STRING, &value));
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(recommended_pref::kUserValue, actual_str_value);
+
+  // Test getting recommended value when no higher-priority value is present.
+  value = nullptr;
+  ASSERT_TRUE(pref_value_store_->GetRecommendedValue(
+      prefs::kRecommendedPref, base::Value::Type::STRING, &value));
+  EXPECT_TRUE(value->GetAsString(&actual_str_value));
+  EXPECT_EQ(recommended_pref::kRecommendedValue, actual_str_value);
+
+  // Test getting recommended value when no recommended value is present.
+  base::Value tmp_dummy_value(true);
+  value = &tmp_dummy_value;
+  ASSERT_FALSE(pref_value_store_->GetRecommendedValue(
+      prefs::kDefaultPref, base::Value::Type::STRING, &value));
+  ASSERT_FALSE(value);
+
+  // Test getting a preference value that the |PrefValueStore|
+  // does not contain.
+  value = &tmp_dummy_value;
+  ASSERT_FALSE(pref_value_store_->GetRecommendedValue(
+      prefs::kMissingPref, base::Value::Type::STRING, &value));
+  ASSERT_FALSE(value);
+}
+
+TEST_F(PrefValueStoreTest, PrefChanges) {
+  // Check pref controlled by highest-priority store.
+  ExpectValueChangeNotifications(prefs::kManagedPref);
+  managed_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kManagedPref);
+  supervised_user_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kManagedPref);
+  extension_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kManagedPref);
+  command_line_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kManagedPref);
+  user_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kManagedPref);
+  recommended_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kManagedPref);
+  default_pref_store_->NotifyPrefValueChanged(prefs::kManagedPref);
+  CheckAndClearValueChangeNotifications();
+
+  // Check pref controlled by user store.
+  ExpectValueChangeNotifications(prefs::kUserPref);
+  managed_pref_store_->NotifyPrefValueChanged(prefs::kUserPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kUserPref);
+  extension_pref_store_->NotifyPrefValueChanged(prefs::kUserPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kUserPref);
+  command_line_pref_store_->NotifyPrefValueChanged(prefs::kUserPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kUserPref);
+  user_pref_store_->NotifyPrefValueChanged(prefs::kUserPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kUserPref);
+  recommended_pref_store_->NotifyPrefValueChanged(prefs::kUserPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kUserPref);
+  default_pref_store_->NotifyPrefValueChanged(prefs::kUserPref);
+  CheckAndClearValueChangeNotifications();
+
+  // Check pref controlled by default-pref store.
+  ExpectValueChangeNotifications(prefs::kDefaultPref);
+  managed_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kDefaultPref);
+  extension_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kDefaultPref);
+  command_line_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kDefaultPref);
+  user_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kDefaultPref);
+  recommended_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref);
+  CheckAndClearValueChangeNotifications();
+
+  ExpectValueChangeNotifications(prefs::kDefaultPref);
+  default_pref_store_->NotifyPrefValueChanged(prefs::kDefaultPref);
+  CheckAndClearValueChangeNotifications();
+}
+
+TEST_F(PrefValueStoreTest, OnInitializationCompleted) {
+  EXPECT_CALL(pref_notifier_, OnInitializationCompleted(true)).Times(0);
+  managed_pref_store_->SetInitializationCompleted();
+  supervised_user_pref_store_->SetInitializationCompleted();
+  extension_pref_store_->SetInitializationCompleted();
+  command_line_pref_store_->SetInitializationCompleted();
+  recommended_pref_store_->SetInitializationCompleted();
+  default_pref_store_->SetInitializationCompleted();
+  Mock::VerifyAndClearExpectations(&pref_notifier_);
+
+  // The notification should only be triggered after the last store is done.
+  EXPECT_CALL(pref_notifier_, OnInitializationCompleted(true)).Times(1);
+  user_pref_store_->SetInitializationCompleted();
+  Mock::VerifyAndClearExpectations(&pref_notifier_);
+}
+
+TEST_F(PrefValueStoreTest, PrefValueInManagedStore) {
+  EXPECT_TRUE(pref_value_store_->PrefValueInManagedStore(prefs::kManagedPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueInManagedStore(prefs::kSupervisedUserPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueInManagedStore(prefs::kExtensionPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueInManagedStore(prefs::kCommandLinePref));
+  EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore(prefs::kUserPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueInManagedStore(prefs::kRecommendedPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore(prefs::kDefaultPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore(prefs::kMissingPref));
+}
+
+TEST_F(PrefValueStoreTest, PrefValueInExtensionStore) {
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueInExtensionStore(prefs::kManagedPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueInExtensionStore(prefs::kSupervisedUserPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueInExtensionStore(prefs::kExtensionPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueInExtensionStore(prefs::kCommandLinePref));
+  EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore(prefs::kUserPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueInExtensionStore(prefs::kRecommendedPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueInExtensionStore(prefs::kDefaultPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueInExtensionStore(prefs::kMissingPref));
+}
+
+TEST_F(PrefValueStoreTest, PrefValueInUserStore) {
+  EXPECT_TRUE(pref_value_store_->PrefValueInUserStore(prefs::kManagedPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueInUserStore(prefs::kSupervisedUserPref));
+  EXPECT_TRUE(pref_value_store_->PrefValueInUserStore(prefs::kExtensionPref));
+  EXPECT_TRUE(pref_value_store_->PrefValueInUserStore(prefs::kCommandLinePref));
+  EXPECT_TRUE(pref_value_store_->PrefValueInUserStore(prefs::kUserPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueInUserStore(prefs::kRecommendedPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueInUserStore(prefs::kDefaultPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueInUserStore(prefs::kMissingPref));
+}
+
+TEST_F(PrefValueStoreTest, PrefValueFromExtensionStore) {
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromExtensionStore(prefs::kManagedPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore(
+      prefs::kSupervisedUserPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueFromExtensionStore(prefs::kExtensionPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromExtensionStore(prefs::kCommandLinePref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromExtensionStore(prefs::kUserPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromExtensionStore(prefs::kRecommendedPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromExtensionStore(prefs::kDefaultPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromExtensionStore(prefs::kMissingPref));
+}
+
+TEST_F(PrefValueStoreTest, PrefValueFromUserStore) {
+  EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore(prefs::kManagedPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromUserStore(prefs::kSupervisedUserPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromUserStore(prefs::kExtensionPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromUserStore(prefs::kCommandLinePref));
+  EXPECT_TRUE(pref_value_store_->PrefValueFromUserStore(prefs::kUserPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromUserStore(prefs::kRecommendedPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore(prefs::kDefaultPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore(prefs::kMissingPref));
+}
+
+TEST_F(PrefValueStoreTest, PrefValueFromRecommendedStore) {
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromRecommendedStore(prefs::kManagedPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore(
+      prefs::kSupervisedUserPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromRecommendedStore(prefs::kExtensionPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore(
+      prefs::kCommandLinePref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromRecommendedStore(prefs::kUserPref));
+  EXPECT_TRUE(pref_value_store_->PrefValueFromRecommendedStore(
+      prefs::kRecommendedPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromRecommendedStore(prefs::kDefaultPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromRecommendedStore(prefs::kMissingPref));
+}
+
+TEST_F(PrefValueStoreTest, PrefValueFromDefaultStore) {
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromDefaultStore(prefs::kManagedPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromDefaultStore(prefs::kSupervisedUserPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromDefaultStore(prefs::kExtensionPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromDefaultStore(prefs::kCommandLinePref));
+  EXPECT_FALSE(pref_value_store_->PrefValueFromDefaultStore(prefs::kUserPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromDefaultStore(prefs::kRecommendedPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueFromDefaultStore(prefs::kDefaultPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromDefaultStore(prefs::kMissingPref));
+}
+
+TEST_F(PrefValueStoreTest, PrefValueUserModifiable) {
+  EXPECT_FALSE(pref_value_store_->PrefValueUserModifiable(prefs::kManagedPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueUserModifiable(prefs::kSupervisedUserPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueUserModifiable(prefs::kExtensionPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueUserModifiable(prefs::kCommandLinePref));
+  EXPECT_TRUE(pref_value_store_->PrefValueUserModifiable(prefs::kUserPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueUserModifiable(prefs::kRecommendedPref));
+  EXPECT_TRUE(pref_value_store_->PrefValueUserModifiable(prefs::kDefaultPref));
+  EXPECT_TRUE(pref_value_store_->PrefValueUserModifiable(prefs::kMissingPref));
+}
+
+TEST_F(PrefValueStoreTest, PrefValueExtensionModifiable) {
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueExtensionModifiable(prefs::kManagedPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueExtensionModifiable(
+      prefs::kSupervisedUserPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueExtensionModifiable(prefs::kExtensionPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueExtensionModifiable(prefs::kCommandLinePref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueExtensionModifiable(prefs::kUserPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueExtensionModifiable(prefs::kRecommendedPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueExtensionModifiable(prefs::kDefaultPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueExtensionModifiable(prefs::kMissingPref));
+}
diff --git a/src/components/prefs/prefs.gyp b/src/components/prefs/prefs.gyp
new file mode 100644
index 0000000..c17c711
--- /dev/null
+++ b/src/components/prefs/prefs.gyp
@@ -0,0 +1,73 @@
+# Copyright 2019 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'targets': [
+    {
+      'target_name': 'prefs',
+      'type': 'static_library',
+      'sources': [
+        'command_line_pref_store.cc',
+        'command_line_pref_store.h',
+        'default_pref_store.cc',
+        'default_pref_store.h',
+        'in_memory_pref_store.cc',
+        'in_memory_pref_store.h',
+        'json_pref_store.cc',
+        'json_pref_store.h',
+        'overlay_user_pref_store.cc',
+        'overlay_user_pref_store.h',
+        'persistent_pref_store.cc',
+        'persistent_pref_store.h',
+        'pref_change_registrar.cc',
+        'pref_change_registrar.h',
+        'pref_filter.h',
+        'pref_member.cc',
+        'pref_member.h',
+        'pref_notifier.h',
+        'pref_notifier_impl.cc',
+        'pref_notifier_impl.h',
+        'pref_observer.h',
+        'pref_registry.cc',
+        'pref_registry.h',
+        'pref_registry_simple.cc',
+        'pref_registry_simple.h',
+        'pref_service.cc',
+        'pref_service.h',
+        'pref_service_factory.cc',
+        'pref_service_factory.h',
+        'pref_store.cc',
+        'pref_store.h',
+        'pref_value_map.cc',
+        'pref_value_map.h',
+        'pref_value_store.cc',
+        'pref_value_store.h',
+        'prefs_export.h',
+        'scoped_user_pref_update.cc',
+        'scoped_user_pref_update.h',
+        'value_map_pref_store.cc',
+        'value_map_pref_store.h',
+        'writeable_pref_store.cc',
+        'writeable_pref_store.h',
+      ],
+     'dependencies': [
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/base/util/values/values_util.gyp:values_util',
+      ],
+     'defines': [
+       'COMPONENTS_PREFS_IMPLEMENTATION',
+     ],
+    }
+  ]
+}
diff --git a/src/components/prefs/prefs_export.h b/src/components/prefs/prefs_export.h
new file mode 100644
index 0000000..b5c8366
--- /dev/null
+++ b/src/components/prefs/prefs_export.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_PREFS_EXPORT_H_
+#define COMPONENTS_PREFS_PREFS_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(COMPONENTS_PREFS_IMPLEMENTATION)
+#define COMPONENTS_PREFS_EXPORT __declspec(dllexport)
+#else
+#define COMPONENTS_PREFS_EXPORT __declspec(dllimport)
+#endif  // defined(COMPONENTS_PREFS_IMPLEMENTATION)
+
+#else  // defined(WIN32)
+#if defined(COMPONENTS_PREFS_IMPLEMENTATION)
+#define COMPONENTS_PREFS_EXPORT __attribute__((visibility("default")))
+#else
+#define COMPONENTS_PREFS_EXPORT
+#endif
+#endif
+
+#else  // defined(COMPONENT_BUILD)
+#define COMPONENTS_PREFS_EXPORT
+#endif
+
+#endif  // COMPONENTS_PREFS_PREFS_EXPORT_H_
diff --git a/src/components/prefs/scoped_user_pref_update.cc b/src/components/prefs/scoped_user_pref_update.cc
new file mode 100644
index 0000000..27d56af
--- /dev/null
+++ b/src/components/prefs/scoped_user_pref_update.cc
@@ -0,0 +1,44 @@
+// Copyright 2013 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 "components/prefs/scoped_user_pref_update.h"
+
+#include "base/logging.h"
+#include "components/prefs/pref_notifier.h"
+#include "components/prefs/pref_service.h"
+
+namespace subtle {
+
+ScopedUserPrefUpdateBase::ScopedUserPrefUpdateBase(PrefService* service,
+                                                   const std::string& path)
+    : service_(service), path_(path), value_(nullptr) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(service_->sequence_checker_);
+}
+
+ScopedUserPrefUpdateBase::~ScopedUserPrefUpdateBase() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  Notify();
+}
+
+base::Value* ScopedUserPrefUpdateBase::GetValueOfType(base::Value::Type type) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!value_)
+    value_ = service_->GetMutableUserPref(path_, type);
+
+  // |value_| might be downcast to base::DictionaryValue or base::ListValue,
+  // side-stepping CHECKs built into base::Value. Thus we need to be certain
+  // that the type matches.
+  if (value_)
+    CHECK_EQ(value_->type(), type);
+  return value_;
+}
+
+void ScopedUserPrefUpdateBase::Notify() {
+  if (value_) {
+    service_->ReportUserPrefChanged(path_);
+    value_ = nullptr;
+  }
+}
+
+}  // namespace subtle
diff --git a/src/components/prefs/scoped_user_pref_update.h b/src/components/prefs/scoped_user_pref_update.h
new file mode 100644
index 0000000..c90f91d
--- /dev/null
+++ b/src/components/prefs/scoped_user_pref_update.h
@@ -0,0 +1,104 @@
+// Copyright 2013 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.
+//
+// A helper class that assists preferences in firing notifications when lists
+// or dictionaries are changed.
+
+#ifndef COMPONENTS_PREFS_SCOPED_USER_PREF_UPDATE_H_
+#define COMPONENTS_PREFS_SCOPED_USER_PREF_UPDATE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/sequence_checker.h"
+#include "base/values.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/prefs_export.h"
+
+class PrefService;
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}  // namespace base
+
+namespace subtle {
+
+// Base class for ScopedUserPrefUpdateTemplate that contains the parts
+// that do not depend on ScopedUserPrefUpdateTemplate's template parameter.
+//
+// We need this base class mostly for making it a friend of PrefService
+// and getting access to PrefService::GetMutableUserPref and
+// PrefService::ReportUserPrefChanged.
+class COMPONENTS_PREFS_EXPORT ScopedUserPrefUpdateBase {
+ protected:
+  ScopedUserPrefUpdateBase(PrefService* service, const std::string& path);
+
+  // Calls Notify().
+  ~ScopedUserPrefUpdateBase();
+
+  // Sets |value_| to |service_|->GetMutableUserPref and returns it.
+  base::Value* GetValueOfType(base::Value::Type type);
+
+ private:
+  // If |value_| is not null, triggers a notification of PrefObservers and
+  // resets |value_|.
+  void Notify();
+
+  // Weak pointer.
+  PrefService* service_;
+  // Path of the preference being updated.
+  std::string path_;
+  // Cache of value from user pref store (set between Get() and Notify() calls).
+  base::Value* value_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedUserPrefUpdateBase);
+};
+
+}  // namespace subtle
+
+// Class to support modifications to DictionaryValues and ListValues while
+// guaranteeing that PrefObservers are notified of changed values.
+//
+// This class may only be used on the UI thread as it requires access to the
+// PrefService.
+template <typename T, base::Value::Type type_enum_value>
+class ScopedUserPrefUpdate : public subtle::ScopedUserPrefUpdateBase {
+ public:
+  ScopedUserPrefUpdate(PrefService* service, const std::string& path)
+      : ScopedUserPrefUpdateBase(service, path) {}
+
+  // Triggers an update notification if Get() was called.
+  virtual ~ScopedUserPrefUpdate() {}
+
+  // Returns a mutable |T| instance that
+  // - is already in the user pref store, or
+  // - is (silently) created and written to the user pref store if none existed
+  //   before.
+  //
+  // Calling Get() implies that an update notification is necessary at
+  // destruction time.
+  //
+  // The ownership of the return value remains with the user pref store.
+  // Virtual so it can be overriden in subclasses that transform the value
+  // before returning it (for example to return a subelement of a dictionary).
+  virtual T* Get() { return static_cast<T*>(GetValueOfType(type_enum_value)); }
+
+  T& operator*() { return *Get(); }
+
+  T* operator->() { return Get(); }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedUserPrefUpdate);
+};
+
+typedef ScopedUserPrefUpdate<base::DictionaryValue,
+                             base::Value::Type::DICTIONARY>
+    DictionaryPrefUpdate;
+typedef ScopedUserPrefUpdate<base::ListValue, base::Value::Type::LIST>
+    ListPrefUpdate;
+
+#endif  // COMPONENTS_PREFS_SCOPED_USER_PREF_UPDATE_H_
diff --git a/src/components/prefs/scoped_user_pref_update_unittest.cc b/src/components/prefs/scoped_user_pref_update_unittest.cc
new file mode 100644
index 0000000..edf1eb4
--- /dev/null
+++ b/src/components/prefs/scoped_user_pref_update_unittest.cc
@@ -0,0 +1,108 @@
+// Copyright 2013 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 "components/prefs/scoped_user_pref_update.h"
+#include "components/prefs/mock_pref_change_callback.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Mock;
+
+class ScopedUserPrefUpdateTest : public testing::Test {
+ public:
+  ScopedUserPrefUpdateTest() : observer_(&prefs_) {}
+  ~ScopedUserPrefUpdateTest() override {}
+
+ protected:
+  void SetUp() override {
+    prefs_.registry()->RegisterDictionaryPref(kPref);
+    registrar_.Init(&prefs_);
+    registrar_.Add(kPref, observer_.GetCallback());
+  }
+
+  static const char kPref[];
+  static const char kKey[];
+  static const char kValue[];
+
+  TestingPrefServiceSimple prefs_;
+  MockPrefChangeCallback observer_;
+  PrefChangeRegistrar registrar_;
+};
+
+const char ScopedUserPrefUpdateTest::kPref[] = "name";
+const char ScopedUserPrefUpdateTest::kKey[] = "key";
+const char ScopedUserPrefUpdateTest::kValue[] = "value";
+
+TEST_F(ScopedUserPrefUpdateTest, RegularUse) {
+  // Dictionary that will be expected to be set at the end.
+  base::DictionaryValue expected_dictionary;
+  expected_dictionary.SetString(kKey, kValue);
+
+  {
+    EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0);
+    DictionaryPrefUpdate update(&prefs_, kPref);
+    base::DictionaryValue* value = update.Get();
+    ASSERT_TRUE(value);
+    value->SetString(kKey, kValue);
+
+    // The dictionary was created for us but the creation should have happened
+    // silently without notifications.
+    Mock::VerifyAndClearExpectations(&observer_);
+
+    // Modifications happen online and are instantly visible, though.
+    const base::DictionaryValue* current_value = prefs_.GetDictionary(kPref);
+    ASSERT_TRUE(current_value);
+    EXPECT_TRUE(expected_dictionary.Equals(current_value));
+
+    // Now we are leaving the scope of the update so we should be notified.
+    observer_.Expect(kPref, &expected_dictionary);
+  }
+  Mock::VerifyAndClearExpectations(&observer_);
+
+  const base::DictionaryValue* current_value = prefs_.GetDictionary(kPref);
+  ASSERT_TRUE(current_value);
+  EXPECT_TRUE(expected_dictionary.Equals(current_value));
+}
+
+TEST_F(ScopedUserPrefUpdateTest, NeverTouchAnything) {
+  const base::DictionaryValue* old_value = prefs_.GetDictionary(kPref);
+  EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0);
+  { DictionaryPrefUpdate update(&prefs_, kPref); }
+  const base::DictionaryValue* new_value = prefs_.GetDictionary(kPref);
+  EXPECT_EQ(old_value, new_value);
+  Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(ScopedUserPrefUpdateTest, UpdatingListPrefWithDefaults) {
+  base::Value::ListStorage defaults;
+  defaults.emplace_back("firstvalue");
+  defaults.emplace_back("secondvalue");
+
+  std::string pref_name = "mypref";
+  prefs_.registry()->RegisterListPref(pref_name,
+                                      base::Value(std::move(defaults)));
+  EXPECT_EQ(2u, prefs_.GetList(pref_name)->GetList().size());
+
+  ListPrefUpdate update(&prefs_, pref_name);
+  update->AppendString("thirdvalue");
+  EXPECT_EQ(3u, prefs_.GetList(pref_name)->GetList().size());
+}
+
+TEST_F(ScopedUserPrefUpdateTest, UpdatingDictionaryPrefWithDefaults) {
+  base::Value defaults(base::Value::Type::DICTIONARY);
+  defaults.SetKey("firstkey", base::Value("value"));
+  defaults.SetKey("secondkey", base::Value("value"));
+
+  std::string pref_name = "mypref";
+  prefs_.registry()->RegisterDictionaryPref(pref_name, std::move(defaults));
+  EXPECT_EQ(2u, prefs_.GetDictionary(pref_name)->size());
+
+  DictionaryPrefUpdate update(&prefs_, pref_name);
+  update->SetKey("thirdkey", base::Value("value"));
+  EXPECT_EQ(3u, prefs_.GetDictionary(pref_name)->size());
+}
diff --git a/src/components/prefs/testing_pref_service.cc b/src/components/prefs/testing_pref_service.cc
new file mode 100644
index 0000000..82c0cf7
--- /dev/null
+++ b/src/components/prefs/testing_pref_service.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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 "components/prefs/testing_pref_service.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "components/prefs/default_pref_store.h"
+#include "components/prefs/pref_notifier_impl.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_value_store.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+template <>
+TestingPrefServiceBase<PrefService, PrefRegistry>::TestingPrefServiceBase(
+    TestingPrefStore* managed_prefs,
+    TestingPrefStore* extension_prefs,
+    TestingPrefStore* user_prefs,
+    TestingPrefStore* recommended_prefs,
+    PrefRegistry* pref_registry,
+    PrefNotifierImpl* pref_notifier)
+    : PrefService(
+          std::unique_ptr<PrefNotifierImpl>(pref_notifier),
+          std::make_unique<PrefValueStore>(managed_prefs,
+                                           nullptr,
+                                           extension_prefs,
+                                           nullptr,
+                                           user_prefs,
+                                           recommended_prefs,
+                                           pref_registry->defaults().get(),
+                                           pref_notifier),
+          user_prefs,
+          pref_registry,
+          base::Bind(&TestingPrefServiceBase<PrefService,
+                                             PrefRegistry>::HandleReadError),
+          false),
+      managed_prefs_(managed_prefs),
+      extension_prefs_(extension_prefs),
+      user_prefs_(user_prefs),
+      recommended_prefs_(recommended_prefs) {}
+
+TestingPrefServiceSimple::TestingPrefServiceSimple()
+    : TestingPrefServiceBase<PrefService, PrefRegistry>(
+          new TestingPrefStore(),
+          new TestingPrefStore(),
+          new TestingPrefStore(),
+          new TestingPrefStore(),
+          new PrefRegistrySimple(),
+          new PrefNotifierImpl()) {}
+
+TestingPrefServiceSimple::~TestingPrefServiceSimple() {}
+
+PrefRegistrySimple* TestingPrefServiceSimple::registry() {
+  return static_cast<PrefRegistrySimple*>(DeprecatedGetPrefRegistry());
+}
diff --git a/src/components/prefs/testing_pref_service.h b/src/components/prefs/testing_pref_service.h
new file mode 100644
index 0000000..a33a5d1
--- /dev/null
+++ b/src/components/prefs/testing_pref_service.h
@@ -0,0 +1,232 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_TESTING_PREF_SERVICE_H_
+#define COMPONENTS_PREFS_TESTING_PREF_SERVICE_H_
+
+#include <memory>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/prefs/pref_registry.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/testing_pref_store.h"
+
+class PrefNotifierImpl;
+class PrefRegistrySimple;
+class TestingPrefStore;
+
+// A PrefService subclass for testing. It operates totally in memory and
+// provides additional API for manipulating preferences at the different levels
+// (managed, extension, user) conveniently.
+//
+// Use this via its specializations, e.g. TestingPrefServiceSimple.
+template <class SuperPrefService, class ConstructionPrefRegistry>
+class TestingPrefServiceBase : public SuperPrefService {
+ public:
+  virtual ~TestingPrefServiceBase();
+
+  // Reads the value of a preference from the managed layer. Returns NULL if the
+  // preference is not defined at the managed layer.
+  const base::Value* GetManagedPref(const std::string& path) const;
+
+  // Sets a preference on the managed layer and fires observers if the
+  // preference changed.
+  void SetManagedPref(const std::string& path,
+                      std::unique_ptr<base::Value> value);
+
+  // Clears the preference on the managed layer and fire observers if the
+  // preference has been defined previously.
+  void RemoveManagedPref(const std::string& path);
+
+  // Similar to the above, but for extension preferences.
+  // Does not really know about extensions and their order of installation.
+  // Useful in tests that only check that a preference is overridden by an
+  // extension.
+  const base::Value* GetExtensionPref(const std::string& path) const;
+  void SetExtensionPref(const std::string& path,
+                        std::unique_ptr<base::Value> value);
+  void RemoveExtensionPref(const std::string& path);
+
+  // Similar to the above, but for user preferences.
+  const base::Value* GetUserPref(const std::string& path) const;
+  void SetUserPref(const std::string& path, std::unique_ptr<base::Value> value);
+  void RemoveUserPref(const std::string& path);
+
+  // Similar to the above, but for recommended policy preferences.
+  const base::Value* GetRecommendedPref(const std::string& path) const;
+  void SetRecommendedPref(const std::string& path,
+                          std::unique_ptr<base::Value> value);
+  void RemoveRecommendedPref(const std::string& path);
+
+  // Do-nothing implementation for TestingPrefService.
+  static void HandleReadError(PersistentPrefStore::PrefReadError error) {}
+
+ protected:
+  TestingPrefServiceBase(TestingPrefStore* managed_prefs,
+                         TestingPrefStore* extension_prefs,
+                         TestingPrefStore* user_prefs,
+                         TestingPrefStore* recommended_prefs,
+                         ConstructionPrefRegistry* pref_registry,
+                         PrefNotifierImpl* pref_notifier);
+
+ private:
+  // Reads the value of the preference indicated by |path| from |pref_store|.
+  // Returns NULL if the preference was not found.
+  const base::Value* GetPref(TestingPrefStore* pref_store,
+                             const std::string& path) const;
+
+  // Sets the value for |path| in |pref_store|.
+  void SetPref(TestingPrefStore* pref_store,
+               const std::string& path,
+               std::unique_ptr<base::Value> value);
+
+  // Removes the preference identified by |path| from |pref_store|.
+  void RemovePref(TestingPrefStore* pref_store, const std::string& path);
+
+  // Pointers to the pref stores our value store uses.
+  scoped_refptr<TestingPrefStore> managed_prefs_;
+  scoped_refptr<TestingPrefStore> extension_prefs_;
+  scoped_refptr<TestingPrefStore> user_prefs_;
+  scoped_refptr<TestingPrefStore> recommended_prefs_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestingPrefServiceBase);
+};
+
+// Test version of PrefService.
+class TestingPrefServiceSimple
+    : public TestingPrefServiceBase<PrefService, PrefRegistry> {
+ public:
+  TestingPrefServiceSimple();
+  ~TestingPrefServiceSimple() override;
+
+  // This is provided as a convenience for registering preferences on
+  // an existing TestingPrefServiceSimple instance. On a production
+  // PrefService you would do all registrations before constructing
+  // it, passing it a PrefRegistry via its constructor (or via
+  // e.g. PrefServiceFactory).
+  PrefRegistrySimple* registry();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestingPrefServiceSimple);
+};
+
+template <>
+TestingPrefServiceBase<PrefService, PrefRegistry>::TestingPrefServiceBase(
+    TestingPrefStore* managed_prefs,
+    TestingPrefStore* extension_prefs,
+    TestingPrefStore* user_prefs,
+    TestingPrefStore* recommended_prefs,
+    PrefRegistry* pref_registry,
+    PrefNotifierImpl* pref_notifier);
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+TestingPrefServiceBase<SuperPrefService,
+                       ConstructionPrefRegistry>::~TestingPrefServiceBase() {}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+const base::Value* TestingPrefServiceBase<
+    SuperPrefService,
+    ConstructionPrefRegistry>::GetManagedPref(const std::string& path) const {
+  return GetPref(managed_prefs_.get(), path);
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+void TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::
+    SetManagedPref(const std::string& path,
+                   std::unique_ptr<base::Value> value) {
+  SetPref(managed_prefs_.get(), path, std::move(value));
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+void TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::
+    RemoveManagedPref(const std::string& path) {
+  RemovePref(managed_prefs_.get(), path);
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+const base::Value* TestingPrefServiceBase<
+    SuperPrefService,
+    ConstructionPrefRegistry>::GetExtensionPref(const std::string& path) const {
+  return GetPref(extension_prefs_.get(), path);
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+void TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::
+    SetExtensionPref(const std::string& path,
+                     std::unique_ptr<base::Value> value) {
+  SetPref(extension_prefs_.get(), path, std::move(value));
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+void TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::
+    RemoveExtensionPref(const std::string& path) {
+  RemovePref(extension_prefs_.get(), path);
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+const base::Value*
+TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::GetUserPref(
+    const std::string& path) const {
+  return GetPref(user_prefs_.get(), path);
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+void TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::
+    SetUserPref(const std::string& path, std::unique_ptr<base::Value> value) {
+  SetPref(user_prefs_.get(), path, std::move(value));
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+void TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::
+    RemoveUserPref(const std::string& path) {
+  RemovePref(user_prefs_.get(), path);
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+const base::Value*
+TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::
+    GetRecommendedPref(const std::string& path) const {
+  return GetPref(recommended_prefs_, path);
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+void TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::
+    SetRecommendedPref(const std::string& path,
+                       std::unique_ptr<base::Value> value) {
+  SetPref(recommended_prefs_.get(), path, std::move(value));
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+void TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::
+    RemoveRecommendedPref(const std::string& path) {
+  RemovePref(recommended_prefs_.get(), path);
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+const base::Value*
+TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::GetPref(
+    TestingPrefStore* pref_store,
+    const std::string& path) const {
+  const base::Value* res;
+  return pref_store->GetValue(path, &res) ? res : NULL;
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+void TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::
+    SetPref(TestingPrefStore* pref_store,
+            const std::string& path,
+            std::unique_ptr<base::Value> value) {
+  pref_store->SetValue(path, std::move(value),
+                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+}
+
+template <class SuperPrefService, class ConstructionPrefRegistry>
+void TestingPrefServiceBase<SuperPrefService, ConstructionPrefRegistry>::
+    RemovePref(TestingPrefStore* pref_store, const std::string& path) {
+  pref_store->RemoveValue(path, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+}
+
+#endif  // COMPONENTS_PREFS_TESTING_PREF_SERVICE_H_
diff --git a/src/components/prefs/testing_pref_store.cc b/src/components/prefs/testing_pref_store.cc
new file mode 100644
index 0000000..4380407
--- /dev/null
+++ b/src/components/prefs/testing_pref_store.cc
@@ -0,0 +1,216 @@
+// Copyright (c) 2012 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 "components/prefs/testing_pref_store.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/json/json_writer.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TestingPrefStore::TestingPrefStore()
+    : read_only_(true),
+      read_success_(true),
+      read_error_(PersistentPrefStore::PREF_READ_ERROR_NONE),
+      block_async_read_(false),
+      pending_async_read_(false),
+      init_complete_(false),
+      committed_(true) {}
+
+bool TestingPrefStore::GetValue(const std::string& key,
+                                const base::Value** value) const {
+  return prefs_.GetValue(key, value);
+}
+
+std::unique_ptr<base::DictionaryValue> TestingPrefStore::GetValues() const {
+  return prefs_.AsDictionaryValue();
+}
+
+bool TestingPrefStore::GetMutableValue(const std::string& key,
+                                       base::Value** value) {
+  return prefs_.GetValue(key, value);
+}
+
+void TestingPrefStore::AddObserver(PrefStore::Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void TestingPrefStore::RemoveObserver(PrefStore::Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+bool TestingPrefStore::HasObservers() const {
+  return observers_.might_have_observers();
+}
+
+bool TestingPrefStore::IsInitializationComplete() const {
+  return init_complete_;
+}
+
+void TestingPrefStore::SetValue(const std::string& key,
+                                std::unique_ptr<base::Value> value,
+                                uint32_t flags) {
+  DCHECK(value);
+  if (prefs_.SetValue(key, base::Value::FromUniquePtrValue(std::move(value)))) {
+    committed_ = false;
+    NotifyPrefValueChanged(key);
+  }
+}
+
+void TestingPrefStore::SetValueSilently(const std::string& key,
+                                        std::unique_ptr<base::Value> value,
+                                        uint32_t flags) {
+  DCHECK(value);
+  CheckPrefIsSerializable(key, *value);
+  if (prefs_.SetValue(key, base::Value::FromUniquePtrValue(std::move(value))))
+    committed_ = false;
+}
+
+void TestingPrefStore::RemoveValue(const std::string& key, uint32_t flags) {
+  if (prefs_.RemoveValue(key)) {
+    committed_ = false;
+    NotifyPrefValueChanged(key);
+  }
+}
+
+bool TestingPrefStore::ReadOnly() const {
+  return read_only_;
+}
+
+PersistentPrefStore::PrefReadError TestingPrefStore::GetReadError() const {
+  return read_error_;
+}
+
+PersistentPrefStore::PrefReadError TestingPrefStore::ReadPrefs() {
+  NotifyInitializationCompleted();
+  return read_error_;
+}
+
+void TestingPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
+  DCHECK(!pending_async_read_);
+  error_delegate_.reset(error_delegate);
+  if (block_async_read_)
+    pending_async_read_ = true;
+  else
+    NotifyInitializationCompleted();
+}
+
+void TestingPrefStore::CommitPendingWrite(
+    base::OnceClosure reply_callback,
+    base::OnceClosure synchronous_done_callback) {
+  committed_ = true;
+  PersistentPrefStore::CommitPendingWrite(std::move(reply_callback),
+                                          std::move(synchronous_done_callback));
+}
+
+void TestingPrefStore::SchedulePendingLossyWrites() {}
+
+void TestingPrefStore::SetInitializationCompleted() {
+  NotifyInitializationCompleted();
+}
+
+void TestingPrefStore::NotifyPrefValueChanged(const std::string& key) {
+  for (Observer& observer : observers_)
+    observer.OnPrefValueChanged(key);
+}
+
+void TestingPrefStore::NotifyInitializationCompleted() {
+  DCHECK(!init_complete_);
+  init_complete_ = true;
+  if (read_success_ && read_error_ != PREF_READ_ERROR_NONE && error_delegate_)
+    error_delegate_->OnError(read_error_);
+  for (Observer& observer : observers_)
+    observer.OnInitializationCompleted(read_success_);
+}
+
+void TestingPrefStore::ReportValueChanged(const std::string& key,
+                                          uint32_t flags) {
+  const base::Value* value = nullptr;
+  if (prefs_.GetValue(key, &value))
+    CheckPrefIsSerializable(key, *value);
+
+  for (Observer& observer : observers_)
+    observer.OnPrefValueChanged(key);
+}
+
+void TestingPrefStore::SetString(const std::string& key,
+                                 const std::string& value) {
+  SetValue(key, std::make_unique<base::Value>(value), DEFAULT_PREF_WRITE_FLAGS);
+}
+
+void TestingPrefStore::SetInteger(const std::string& key, int value) {
+  SetValue(key, std::make_unique<base::Value>(value), DEFAULT_PREF_WRITE_FLAGS);
+}
+
+void TestingPrefStore::SetBoolean(const std::string& key, bool value) {
+  SetValue(key, std::make_unique<base::Value>(value), DEFAULT_PREF_WRITE_FLAGS);
+}
+
+bool TestingPrefStore::GetString(const std::string& key,
+                                 std::string* value) const {
+  const base::Value* stored_value;
+  if (!prefs_.GetValue(key, &stored_value) || !stored_value)
+    return false;
+
+  return stored_value->GetAsString(value);
+}
+
+bool TestingPrefStore::GetInteger(const std::string& key, int* value) const {
+  const base::Value* stored_value;
+  if (!prefs_.GetValue(key, &stored_value) || !stored_value)
+    return false;
+
+  return stored_value->GetAsInteger(value);
+}
+
+bool TestingPrefStore::GetBoolean(const std::string& key, bool* value) const {
+  const base::Value* stored_value;
+  if (!prefs_.GetValue(key, &stored_value) || !stored_value)
+    return false;
+
+  return stored_value->GetAsBoolean(value);
+}
+
+void TestingPrefStore::SetBlockAsyncRead(bool block_async_read) {
+  DCHECK(!init_complete_);
+  block_async_read_ = block_async_read;
+  if (pending_async_read_ && !block_async_read_)
+    NotifyInitializationCompleted();
+}
+
+void TestingPrefStore::ClearMutableValues() {
+  NOTIMPLEMENTED();
+}
+
+void TestingPrefStore::OnStoreDeletionFromDisk() {}
+
+void TestingPrefStore::set_read_only(bool read_only) {
+  read_only_ = read_only;
+}
+
+void TestingPrefStore::set_read_success(bool read_success) {
+  DCHECK(!init_complete_);
+  read_success_ = read_success;
+}
+
+void TestingPrefStore::set_read_error(
+    PersistentPrefStore::PrefReadError read_error) {
+  DCHECK(!init_complete_);
+  read_error_ = read_error;
+}
+
+TestingPrefStore::~TestingPrefStore() {
+  for (auto& pref : prefs_)
+    CheckPrefIsSerializable(pref.first, pref.second);
+}
+
+void TestingPrefStore::CheckPrefIsSerializable(const std::string& key,
+                                               const base::Value& value) {
+  std::string json;
+  EXPECT_TRUE(base::JSONWriter::Write(value, &json))
+      << "Pref \"" << key << "\" is not serializable as JSON.";
+}
diff --git a/src/components/prefs/testing_pref_store.h b/src/components/prefs/testing_pref_store.h
new file mode 100644
index 0000000..499b094
--- /dev/null
+++ b/src/components/prefs/testing_pref_store.h
@@ -0,0 +1,122 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_TESTING_PREF_STORE_H_
+#define COMPONENTS_PREFS_TESTING_PREF_STORE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_value_map.h"
+
+// |TestingPrefStore| is a preference store implementation that allows tests to
+// explicitly manipulate the contents of the store, triggering notifications
+// where appropriate.
+class TestingPrefStore : public PersistentPrefStore {
+ public:
+  TestingPrefStore();
+
+  // Overriden from PrefStore.
+  bool GetValue(const std::string& key,
+                const base::Value** result) const override;
+  std::unique_ptr<base::DictionaryValue> GetValues() const override;
+  void AddObserver(PrefStore::Observer* observer) override;
+  void RemoveObserver(PrefStore::Observer* observer) override;
+  bool HasObservers() const override;
+  bool IsInitializationComplete() const override;
+
+  // PersistentPrefStore overrides:
+  bool GetMutableValue(const std::string& key, base::Value** result) override;
+  void ReportValueChanged(const std::string& key, uint32_t flags) override;
+  void SetValue(const std::string& key,
+                std::unique_ptr<base::Value> value,
+                uint32_t flags) override;
+  void SetValueSilently(const std::string& key,
+                        std::unique_ptr<base::Value> value,
+                        uint32_t flags) override;
+  void RemoveValue(const std::string& key, uint32_t flags) override;
+  bool ReadOnly() const override;
+  PrefReadError GetReadError() const override;
+  PersistentPrefStore::PrefReadError ReadPrefs() override;
+  void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
+  void CommitPendingWrite(base::OnceClosure reply_callback,
+                          base::OnceClosure synchronous_done_callback) override;
+  void SchedulePendingLossyWrites() override;
+
+  // Marks the store as having completed initialization.
+  void SetInitializationCompleted();
+
+  // Used for tests to trigger notifications explicitly.
+  void NotifyPrefValueChanged(const std::string& key);
+  void NotifyInitializationCompleted();
+
+  // Some convenience getters/setters.
+  void SetString(const std::string& key, const std::string& value);
+  void SetInteger(const std::string& key, int value);
+  void SetBoolean(const std::string& key, bool value);
+
+  bool GetString(const std::string& key, std::string* value) const;
+  bool GetInteger(const std::string& key, int* value) const;
+  bool GetBoolean(const std::string& key, bool* value) const;
+
+  // Determines whether ReadPrefsAsync completes immediately. Defaults to false
+  // (non-blocking). To block, invoke this with true (blocking) before the call
+  // to ReadPrefsAsync. To unblock, invoke again with false (non-blocking) after
+  // the call to ReadPrefsAsync.
+  void SetBlockAsyncRead(bool block_async_read);
+
+  void ClearMutableValues() override;
+  void OnStoreDeletionFromDisk() override;
+
+  // Getter and Setter methods for setting and getting the state of the
+  // |TestingPrefStore|.
+  virtual void set_read_only(bool read_only);
+  void set_read_success(bool read_success);
+  void set_read_error(PersistentPrefStore::PrefReadError read_error);
+  bool committed() { return committed_; }
+
+ protected:
+  ~TestingPrefStore() override;
+
+ private:
+  void CheckPrefIsSerializable(const std::string& key,
+                               const base::Value& value);
+
+  // Stores the preference values.
+  PrefValueMap prefs_;
+
+  // Flag that indicates if the PrefStore is read-only
+  bool read_only_;
+
+  // The result to pass to PrefStore::Observer::OnInitializationCompleted
+  bool read_success_;
+
+  // The result to return from ReadPrefs or ReadPrefsAsync.
+  PersistentPrefStore::PrefReadError read_error_;
+
+  // Whether a call to ReadPrefsAsync should block.
+  bool block_async_read_;
+
+  // Whether there is a pending call to ReadPrefsAsync.
+  bool pending_async_read_;
+
+  // Whether initialization has been completed.
+  bool init_complete_;
+
+  // Whether the store contents have been committed to disk since the last
+  // mutation.
+  bool committed_;
+
+  std::unique_ptr<ReadErrorDelegate> error_delegate_;
+  base::ObserverList<PrefStore::Observer, true>::Unchecked observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestingPrefStore);
+};
+
+#endif  // COMPONENTS_PREFS_TESTING_PREF_STORE_H_
diff --git a/src/components/prefs/value_map_pref_store.cc b/src/components/prefs/value_map_pref_store.cc
new file mode 100644
index 0000000..79c2017
--- /dev/null
+++ b/src/components/prefs/value_map_pref_store.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2012 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 "components/prefs/value_map_pref_store.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/stl_util.h"
+#include "base/values.h"
+
+ValueMapPrefStore::ValueMapPrefStore() {}
+
+bool ValueMapPrefStore::GetValue(const std::string& key,
+                                 const base::Value** value) const {
+  return prefs_.GetValue(key, value);
+}
+
+std::unique_ptr<base::DictionaryValue> ValueMapPrefStore::GetValues() const {
+  return prefs_.AsDictionaryValue();
+}
+
+void ValueMapPrefStore::AddObserver(PrefStore::Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void ValueMapPrefStore::RemoveObserver(PrefStore::Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+bool ValueMapPrefStore::HasObservers() const {
+  return observers_.might_have_observers();
+}
+
+void ValueMapPrefStore::SetValue(const std::string& key,
+                                 std::unique_ptr<base::Value> value,
+                                 uint32_t flags) {
+  DCHECK(value);
+  if (prefs_.SetValue(key, base::Value::FromUniquePtrValue(std::move(value)))) {
+    for (Observer& observer : observers_)
+      observer.OnPrefValueChanged(key);
+  }
+}
+
+void ValueMapPrefStore::RemoveValue(const std::string& key, uint32_t flags) {
+  if (prefs_.RemoveValue(key)) {
+    for (Observer& observer : observers_)
+      observer.OnPrefValueChanged(key);
+  }
+}
+
+bool ValueMapPrefStore::GetMutableValue(const std::string& key,
+                                        base::Value** value) {
+  return prefs_.GetValue(key, value);
+}
+
+void ValueMapPrefStore::ReportValueChanged(const std::string& key,
+                                           uint32_t flags) {
+  for (Observer& observer : observers_)
+    observer.OnPrefValueChanged(key);
+}
+
+void ValueMapPrefStore::SetValueSilently(const std::string& key,
+                                         std::unique_ptr<base::Value> value,
+                                         uint32_t flags) {
+  DCHECK(value);
+  prefs_.SetValue(key, base::Value::FromUniquePtrValue(std::move(value)));
+}
+
+ValueMapPrefStore::~ValueMapPrefStore() {}
+
+void ValueMapPrefStore::NotifyInitializationCompleted() {
+  for (Observer& observer : observers_)
+    observer.OnInitializationCompleted(true);
+}
diff --git a/src/components/prefs/value_map_pref_store.h b/src/components/prefs/value_map_pref_store.h
new file mode 100644
index 0000000..f0de0cd
--- /dev/null
+++ b/src/components/prefs/value_map_pref_store.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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 COMPONENTS_PREFS_VALUE_MAP_PREF_STORE_H_
+#define COMPONENTS_PREFS_VALUE_MAP_PREF_STORE_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/prefs/prefs_export.h"
+#include "components/prefs/writeable_pref_store.h"
+
+// A basic PrefStore implementation that uses a simple name-value map for
+// storing the preference values.
+class COMPONENTS_PREFS_EXPORT ValueMapPrefStore : public WriteablePrefStore {
+ public:
+  ValueMapPrefStore();
+
+  // PrefStore overrides:
+  bool GetValue(const std::string& key,
+                const base::Value** value) const override;
+  std::unique_ptr<base::DictionaryValue> GetValues() const override;
+  void AddObserver(PrefStore::Observer* observer) override;
+  void RemoveObserver(PrefStore::Observer* observer) override;
+  bool HasObservers() const override;
+
+  // WriteablePrefStore overrides:
+  void SetValue(const std::string& key,
+                std::unique_ptr<base::Value> value,
+                uint32_t flags) override;
+  void RemoveValue(const std::string& key, uint32_t flags) override;
+  bool GetMutableValue(const std::string& key, base::Value** value) override;
+  void ReportValueChanged(const std::string& key, uint32_t flags) override;
+  void SetValueSilently(const std::string& key,
+                        std::unique_ptr<base::Value> value,
+                        uint32_t flags) override;
+
+ protected:
+  ~ValueMapPrefStore() override;
+
+  // Notify observers about the initialization completed event.
+  void NotifyInitializationCompleted();
+
+ private:
+  PrefValueMap prefs_;
+
+  base::ObserverList<PrefStore::Observer, true>::Unchecked observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(ValueMapPrefStore);
+};
+
+#endif  // COMPONENTS_PREFS_VALUE_MAP_PREF_STORE_H_
diff --git a/src/components/prefs/writeable_pref_store.cc b/src/components/prefs/writeable_pref_store.cc
new file mode 100644
index 0000000..d51985e
--- /dev/null
+++ b/src/components/prefs/writeable_pref_store.cc
@@ -0,0 +1,14 @@
+// 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 "components/prefs/writeable_pref_store.h"
+
+void WriteablePrefStore::ReportSubValuesChanged(
+    const std::string& key,
+    std::set<std::vector<std::string>> path_components,
+    uint32_t flags) {
+  // Default implementation. Subclasses may use |path_components| to improve
+  // performance.
+  ReportValueChanged(key, flags);
+}
diff --git a/src/components/prefs/writeable_pref_store.h b/src/components/prefs/writeable_pref_store.h
new file mode 100644
index 0000000..9a69c7c
--- /dev/null
+++ b/src/components/prefs/writeable_pref_store.h
@@ -0,0 +1,86 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PREFS_WRITEABLE_PREF_STORE_H_
+#define COMPONENTS_PREFS_WRITEABLE_PREF_STORE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "components/prefs/pref_store.h"
+
+namespace base {
+class Value;
+}
+
+// A pref store that can be written to as well as read from.
+class COMPONENTS_PREFS_EXPORT WriteablePrefStore : public PrefStore {
+ public:
+  // PrefWriteFlags can be used to change the way a pref will be written to
+  // storage.
+  enum PrefWriteFlags : uint32_t {
+    // No flags are specified.
+    DEFAULT_PREF_WRITE_FLAGS = 0,
+
+    // This marks the pref as "lossy". There is no strict time guarantee on when
+    // a lossy pref will be persisted to permanent storage when it is modified.
+    LOSSY_PREF_WRITE_FLAG = 1 << 1
+  };
+
+  WriteablePrefStore() {}
+
+  // Sets a |value| for |key| in the store. |value| must be non-NULL. |flags| is
+  // a bitmask of PrefWriteFlags.
+  virtual void SetValue(const std::string& key,
+                        std::unique_ptr<base::Value> value,
+                        uint32_t flags) = 0;
+
+  // Removes the value for |key|.
+  virtual void RemoveValue(const std::string& key, uint32_t flags) = 0;
+
+  // Equivalent to PrefStore::GetValue but returns a mutable value.
+  virtual bool GetMutableValue(const std::string& key,
+                               base::Value** result) = 0;
+
+  // Triggers a value changed notification. This function or
+  // ReportSubValuesChanged needs to be called if one retrieves a list or
+  // dictionary with GetMutableValue and change its value. SetValue takes care
+  // of notifications itself. Note that ReportValueChanged will trigger
+  // notifications even if nothing has changed.  |flags| is a bitmask of
+  // PrefWriteFlags.
+  virtual void ReportValueChanged(const std::string& key, uint32_t flags) = 0;
+
+  // Triggers a value changed notification for |path_components| in the |key|
+  // pref. This function or ReportValueChanged needs to be called if one
+  // retrieves a list or dictionary with GetMutableValue and change its value.
+  // SetValue takes care of notifications itself. Note that
+  // ReportSubValuesChanged will trigger notifications even if nothing has
+  // changed. |flags| is a bitmask of PrefWriteFlags.
+  virtual void ReportSubValuesChanged(
+      const std::string& key,
+      std::set<std::vector<std::string>> path_components,
+      uint32_t flags);
+
+  // Same as SetValue, but doesn't generate notifications. This is used by
+  // PrefService::GetMutableUserPref() in order to put empty entries
+  // into the user pref store. Using SetValue is not an option since existing
+  // tests rely on the number of notifications generated. |flags| is a bitmask
+  // of PrefWriteFlags.
+  virtual void SetValueSilently(const std::string& key,
+                                std::unique_ptr<base::Value> value,
+                                uint32_t flags) = 0;
+
+ protected:
+  ~WriteablePrefStore() override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(WriteablePrefStore);
+};
+
+#endif  // COMPONENTS_PREFS_WRITEABLE_PREF_STORE_H_
diff --git a/src/components/test/data/update_client/ChromeRecovery.crx3 b/src/components/test/data/update_client/ChromeRecovery.crx3
new file mode 100644
index 0000000..a30e55c
--- /dev/null
+++ b/src/components/test/data/update_client/ChromeRecovery.crx3
Binary files differ
diff --git a/src/components/test/data/update_client/binary_bsdiff_patch.bin b/src/components/test/data/update_client/binary_bsdiff_patch.bin
new file mode 100644
index 0000000..e5913f2
--- /dev/null
+++ b/src/components/test/data/update_client/binary_bsdiff_patch.bin
Binary files differ
diff --git a/src/components/test/data/update_client/binary_courgette_patch.bin b/src/components/test/data/update_client/binary_courgette_patch.bin
new file mode 100644
index 0000000..674701e
--- /dev/null
+++ b/src/components/test/data/update_client/binary_courgette_patch.bin
Binary files differ
diff --git a/src/components/test/data/update_client/binary_input.bin b/src/components/test/data/update_client/binary_input.bin
new file mode 100644
index 0000000..1b22a54
--- /dev/null
+++ b/src/components/test/data/update_client/binary_input.bin
Binary files differ
diff --git a/src/components/test/data/update_client/binary_output.bin b/src/components/test/data/update_client/binary_output.bin
new file mode 100644
index 0000000..f1811e2
--- /dev/null
+++ b/src/components/test/data/update_client/binary_output.bin
Binary files differ
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc.pem b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc.pem
new file mode 100644
index 0000000..5b01b77
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEArecmr1F0I/Y4IQh+7Y/fbYzpYAFK7fIE+Zt9xbWOKVAOQiKb
+0AAFqNHgrEfDZojK4iV4y7vfBbWPm4Iglr+fNdCStbdw4woFrsmxcpVwfBlmulj3
+1DmOqHXGv4+7uOYRdgIpfSYyt2I9+ReQxjkS56IcQ3XcDmzDch2Pqa4aHv+JGLkZ
+fL9buHxej9WCvsDZes2czTG+/bhJcZrL6VLOZqGLss63Uh7VSGJvv35X+AV+/jwr
+PsQSUg42EYKyvpQ/ge+dX6d3DXxoQ6+g0WArkaJPNfcfeBgEAlBqs5QKA/43q1NP
+1nRFJ8wxccCQ2bZ0iqYjB5bFNCzRKfpJmOu6iwIDAQABAoIBAB5jUgM4vI68K7q5
+/VQN3AEMqos4Lpu9utjTLvspapoVfyhXW9rQ5ixF4Hi6YY69QJab7avMPICG5X1L
+E97DxVJmC/zs/TDvQ2bzn6piKrHEzoqpmUTgeek+C2jV+PuqWErCvfU8g4hABoxM
+beP1fTQ0w7OWGMn8f3qlZ8FCxxW7T7xtTZ7G80LsP0Pqr2kKgQGimwNKCS0RsGpk
+8srM0SdWSydp16plF3XRcs8Pd2lBq+cPI707b6hwqKciEXf3LVdhKCgNGZewVOb9
+5rtvhO5TYGfM/BdVIVAPLH065SLzXCoRCWC0nMnSR3IM6H45Vuow2CK0JkhMlwOi
+r2TfEUECgYEA1zOLewUWG537UN+PNnABAQ4/VS1/4Co3tH8xCpBYyF8K7+jR+wI6
+VeNM/HHJMAWFHSA32ntfAgS8xpearb/EDfJgWFTSOwDEfEnZLmjOjRRtIy+MbwNu
+k/6CCgORAmAB/ZPdiGtkPoq5zsWrcrSKw9Qvru96C/d8ojzEiVUZFFkCgYEAzt9A
+4yM9MIDOzA2U//OuLz6asbgVBevZVuPOxQTvBr3U9zlg0D8nm6zGkiQT+M76lskd
+YQdesfhde3ZymhjRrzk3eQ5Kx+E4Cu3h8XCQ5UQQRXATgqrSMng3QE9G2B55m+Gx
+nHWRvG7GWlldOF0aw9vBPkaDWhZxbZpIXxQyuYMCgYEAqJBBYtz7EirO3ewe+18E
+ClWkmg2HqoinFYTDXyjtwhVgNcdCIsD9/KSMeviNxEWunL/NwB7+rlATpeK+T/Zz
+lRETEl1uRrU1Mr2NlmKV17/2NKdb8uDXTqj+JuXgQeH97ShTUDX+UVbMcAA5aSGt
+x/J+XyJJkPIL9/Btabz0ZhECgYEAhZEryfuUpF5mJuWWEHP7QNHA+4IJQNt4ZluL
+wzVeRxIGEOFhGtPL9P1m8DCPHJQ2w2hyqZUilPHxGafucoQXznS41lZd/hPyJUxW
+F5dAVmbNwqeMUU+Ni7yGn+UPrrZuejEMhiJ6yBkVxHvyblPe1hpq+JR6do4LCmMU
+2x6laZUCgYEApvIk/jxuqZ3hrUnMR+3xZ7U8xwD+TFCQwz31htDjr0heASTaP7MZ
++nBI02fNjyeDV8VdMlKfL6NbY46ttbgZgM2aZ0cG6MB8c8FkR3sg9qqbVXLIuLDC
+S3LGqdNKAWLzgVb8k0Bk23ZHNrdbHE/R+I5Oy0HJ355DSshBn67K61U=
+-----END RSA PRIVATE KEY-----
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_changing_binary_file b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_changing_binary_file
new file mode 100755
index 0000000..99f2f83
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_changing_binary_file
Binary files differ
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_changing_text_file b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_changing_text_file
new file mode 100644
index 0000000..96b605b
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_changing_text_file
@@ -0,0 +1 @@
+moisture farm boy
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_static_text_file b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_static_text_file
new file mode 100644
index 0000000..6939ff7
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_static_text_file
@@ -0,0 +1 @@
+space princess
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/manifest.json b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/manifest.json
new file mode 100644
index 0000000..aa3058a
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/manifest.json
@@ -0,0 +1,5 @@
+{
+  "name": "The ihfo towah app.",
+  "manifest_version": 2,
+  "version": "1.0"
+}
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/commands.json b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/commands.json
new file mode 100644
index 0000000..83e4733
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/commands.json
@@ -0,0 +1 @@
+[{"output": "a_changing_text_file", "patch": "f0", "sha256": "aa607adc0b3cd83e7e9f3ebd4ae84671e2b0b288c9b7fed1e0c710025719caf5", "op": "create"}, {"output": "a_changing_binary_file", "input": "a_changing_binary_file", "patch": "f1", "sha256": "dbd6da0fd6f748b7ec95f0eed6c1f6a046d161bebb04994fe1ce730e237a733d", "op": "bsdiff"}, {"output": "manifest.json", "patch": "f2", "sha256": "1cf8d0b433c09c465994e952a79ff6980c68cc7493609f0233a4634de6276e9c", "op": "create"}, {"output": "a_static_text_file", "input": "a_static_text_file", "sha256": "806bfdfbf05afab546b3741e84fb1ccffdf75eef8629c24c5f84add2785b7bcc", "op": "copy"}]
\ No newline at end of file
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f0 b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f0
new file mode 100644
index 0000000..5634cfa
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f0
@@ -0,0 +1 @@
+jedi knight
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f1 b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f1
new file mode 100644
index 0000000..4be3652
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f1
Binary files differ
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f2 b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f2
new file mode 100644
index 0000000..6efc473
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f2
@@ -0,0 +1,5 @@
+{

+  "name": "The ihfo towah app.",

+  "manifest_version": 2,

+  "version": "2.0"

+}

diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/commands.json b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/commands.json
new file mode 100644
index 0000000..b42e1a4
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/commands.json
@@ -0,0 +1 @@
+[{"sha256": "806bfdfbf05afab546b3741e84fb1ccffdf75eef8629c24c5f84add2785b7bcc", "output": "a_static_text_file", "op": "copy", "input": "a_static_text_file"}, {"sha256": "1cf8d0b433c09c465994e952a79ff6980c68cc7493609f0233a4634de6276e9c", "output": "manifest.json", "op": "create", "patch": "f1"}, {"sha256": "aa607adc0b3cd83e7e9f3ebd4ae84671e2b0b288c9b7fed1e0c710025719caf5", "output": "a_changing_text_file", "op": "create", "patch": "f2"}, {"patch": "f3", "sha256": "dbd6da0fd6f748b7ec95f0eed6c1f6a046d161bebb04994fe1ce730e237a733d", "output": "a_changing_binary_file", "op": "courgette", "input": "a_changing_binary_file"}]
\ No newline at end of file
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f1 b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f1
new file mode 100644
index 0000000..6efc473
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f1
@@ -0,0 +1,5 @@
+{

+  "name": "The ihfo towah app.",

+  "manifest_version": 2,

+  "version": "2.0"

+}

diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f2 b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f2
new file mode 100644
index 0000000..5634cfa
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f2
@@ -0,0 +1 @@
+jedi knight
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f3 b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f3
new file mode 100644
index 0000000..4be3652
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f3
Binary files differ
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_changing_binary_file b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_changing_binary_file
new file mode 100755
index 0000000..6b0286e
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_changing_binary_file
Binary files differ
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_changing_text_file b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_changing_text_file
new file mode 100644
index 0000000..5634cfa
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_changing_text_file
@@ -0,0 +1 @@
+jedi knight
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_static_text_file b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_static_text_file
new file mode 100644
index 0000000..6939ff7
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_static_text_file
@@ -0,0 +1 @@
+space princess
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/manifest.json b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/manifest.json
new file mode 100644
index 0000000..6f0c564
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/manifest.json
@@ -0,0 +1,5 @@
+{
+  "name": "The ihfo towah app.",
+  "manifest_version": 2,
+  "version": "2.0"
+}
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx
new file mode 100644
index 0000000..e40dbcd
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx
Binary files differ
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx
new file mode 100644
index 0000000..f28be58
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx
Binary files differ
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad.crx b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad.crx
new file mode 100644
index 0000000..e7c3397
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad.crx
Binary files differ
diff --git a/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx
new file mode 100644
index 0000000..d4cd473
--- /dev/null
+++ b/src/components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx
Binary files differ
diff --git a/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.crx b/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.crx
new file mode 100644
index 0000000..4fb2a3b
--- /dev/null
+++ b/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.crx
Binary files differ
diff --git a/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.pem b/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.pem
new file mode 100644
index 0000000..f3dddf2
--- /dev/null
+++ b/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.pem
@@ -0,0 +1,15 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALrxtbyL9HNJp5c5w
+0u4HDRI/37Vt2jnKwuoep8upmzpfIlYTIvRTp/VrE9HovBWKS7bWSB1zo8jnbh5vw
+Kv5r+JcaBnxJ29lNx9D+D+6TCVuUWa2ph8OhWnPTdTysND1voPF4kEXivjxM2UU5G
+CCsCI1Hbqpt/WLXvwC7XYDNa3AgMBAAECgYAmWJxBr6eV2l7hGz0fFAXdB7g4yRfV
+Ec33lziH3GbQ4lfkSFNzPpzVFlxA6t3WVKYf8WhnyyJypAifJYHzyCcZesyKGSf+N
+icwm2j01UNVyCejFgWxM8f+H656C88eFHh2ciRzjTSJUB0Q8usszeYGRiKT2gqnDB
+yZDQPaeUlvyQJBAOzq1DPhUdYYFpuWUwKoOSKDxmSRrxCxVczI8DRDxLQGFIOuG3Y
+/t2Ga+ynFYL86/hYWGJRg8FQgB25N8PC1dnMCQQDKAHLbZEBN1n2oKZ//B+lsvsuG
+ewx4Sh4a7HJYncDZniXN7qlDPR/FqjYNKhWy7sJGFvpAl5xjYWUqS8Qoh0mtAkAdb
+vc6Es4l+Qwl/EEH3XprrU9igy7qtf1g2b3t4FV5wB+gVGsY/8V6nNSDVgA1AdE7UT
+dwh7KT4P7LV2eNoB29AkAJ5N+7Uzu7FEhvViqaCT8rrmS1n41KzwaOdwpawM9TWWW
+sooXz3yiRO/cNygPqEbu+Rx3W0BY+3+Ren/tpqMa5AkBNDHqOX+yGLJbkM21B9Exi
+eFbHHhDDW0lWf6pfcnLHGCFi0Vdx2xOjhH+JhM1v8TzoLWsy9GFqrkmqsofhDduI
+-----END PRIVATE KEY-----
diff --git a/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf/component1.dll b/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf/component1.dll
new file mode 100755
index 0000000..d981136
--- /dev/null
+++ b/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf/component1.dll
Binary files differ
diff --git a/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf/manifest.json b/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf/manifest.json
new file mode 100644
index 0000000..9286968
--- /dev/null
+++ b/src/components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf/manifest.json
@@ -0,0 +1,9 @@
+{
+  "name": "Component Test 1",
+  "version": "1.0",
+  "manifest_version": 2,
+  "description": "A test component.",
+  "plugins": [
+    { "path": "component1.dll" }
+  ]
+}
diff --git a/src/components/test/data/update_client/runaction_test_win.crx3 b/src/components/test/data/update_client/runaction_test_win.crx3
new file mode 100644
index 0000000..4837214
--- /dev/null
+++ b/src/components/test/data/update_client/runaction_test_win.crx3
Binary files differ
diff --git a/src/components/test/data/update_client/updatecheck_reply_1.json b/src/components/test/data/update_client/updatecheck_reply_1.json
new file mode 100644
index 0000000..c9114df
--- /dev/null
+++ b/src/components/test/data/update_client/updatecheck_reply_1.json
@@ -0,0 +1,18 @@
+)]}'
+{"response":{
+ "protocol":"3.1",
+ "app":[
+  {"appid":"jebgalgnebhfojomionfpkfelancnnkf",
+   "status":"ok",
+   "updatecheck":{
+   "status":"ok",
+   "urls":{"url":[{"codebase":"http://localhost/download/"}]},
+   "actions":{"action":[{"run":"this"}]},
+   "manifest":{
+    "version":"1.0",
+    "prodversionmin":"11.0.1.0",
+    "packages":{"package":[{"name":"jebgalgnebhfojomionfpkfelancnnkf.crx"}]}}
+    }
+   }
+ ]
+}}
diff --git a/src/components/test/data/update_client/updatecheck_reply_4.json b/src/components/test/data/update_client/updatecheck_reply_4.json
new file mode 100644
index 0000000..38580ab
--- /dev/null
+++ b/src/components/test/data/update_client/updatecheck_reply_4.json
@@ -0,0 +1,18 @@
+)]}'
+{"response":{
+ "protocol":"3.1",
+ "daystart":{"elapsed_days":3383},
+ "app":[
+  {"appid":"jebgalgnebhfojomionfpkfelancnnkf",
+   "status":"ok",
+   "updatecheck":{
+   "status":"ok",
+   "urls":{"url":[{"codebase":"http://localhost/download/"}]},
+   "manifest":{
+    "version":"1.0",
+    "prodversionmin":"11.0.1.0",
+    "packages":{"package":[{"name":"jebgalgnebhfojomionfpkfelancnnkf.crx"}]}}
+    }
+   }
+ ]
+}}
diff --git a/src/components/test/data/update_client/updatecheck_reply_noupdate.json b/src/components/test/data/update_client/updatecheck_reply_noupdate.json
new file mode 100644
index 0000000..d069050
--- /dev/null
+++ b/src/components/test/data/update_client/updatecheck_reply_noupdate.json
@@ -0,0 +1,14 @@
+)]}'
+{"response":{
+ "protocol":"3.1",
+ "daystart":{"elapsed_days":3383},
+ "app":[
+  {"appid":"jebgalgnebhfojomionfpkfelancnnkf",
+   "status":"ok",
+   "updatecheck":{
+   "status":"noupdate",
+   "actions":{"action":[{"run":"this"}]}
+   }
+  }
+ ]
+}}
diff --git a/src/components/test/data/update_client/updatecheck_reply_parse_error.json b/src/components/test/data/update_client/updatecheck_reply_parse_error.json
new file mode 100644
index 0000000..82f95fc
--- /dev/null
+++ b/src/components/test/data/update_client/updatecheck_reply_parse_error.json
@@ -0,0 +1,7 @@
+)]}'
+{"response":{
+ "protocol":"3.0",
+ "app":[
+  {"appid":"jebgalgnebhfojomionfpkfelancnnkf"}
+ ]
+}}
diff --git a/src/components/test/data/update_client/updatecheck_reply_unknownapp.json b/src/components/test/data/update_client/updatecheck_reply_unknownapp.json
new file mode 100644
index 0000000..5f1b522
--- /dev/null
+++ b/src/components/test/data/update_client/updatecheck_reply_unknownapp.json
@@ -0,0 +1,9 @@
+)]}'
+{"response":{
+ "protocol":"3.1",
+ "app":[
+  {"appid":"jebgalgnebhfojomionfpkfelancnnkf",
+   "status":"error-unknownApplication"
+  }
+ ]
+}}
diff --git a/src/components/update_client/BUILD.gn b/src/components/update_client/BUILD.gn
new file mode 100644
index 0000000..8ec4b59
--- /dev/null
+++ b/src/components/update_client/BUILD.gn
@@ -0,0 +1,241 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//net/features.gni")
+
+source_set("network_impl") {
+  sources = [
+    "net/network_chromium.h",
+    "net/network_impl.cc",
+    "net/network_impl.h",
+  ]
+
+  deps = [
+    ":update_client",
+    "//base",
+    "//net",
+    "//services/network/public/cpp:cpp",
+    "//url",
+  ]
+}
+
+source_set("unzip_impl") {
+  sources = [
+    "unzip/unzip_impl.cc",
+    "unzip/unzip_impl.h",
+  ]
+  deps = [
+    ":update_client",
+    "//components/services/unzip/public/cpp",
+  ]
+}
+
+source_set("patch_impl") {
+  sources = [
+    "patch/patch_impl.cc",
+    "patch/patch_impl.h",
+  ]
+  deps = [
+    ":update_client",
+    "//components/services/patch/public/cpp",
+    "//components/services/patch/public/mojom",
+    "//mojo/public/cpp/bindings",
+  ]
+}
+
+group("common_impl") {
+  public_deps = [
+    ":network_impl",
+    ":patch_impl",
+    ":unzip_impl",
+  ]
+}
+
+static_library("update_client") {
+  sources = [
+    "action_runner.cc",
+    "action_runner.h",
+    "action_runner_win.cc",
+    "activity_data_service.h",
+    "background_downloader_win.cc",
+    "background_downloader_win.h",
+    "command_line_config_policy.cc",
+    "command_line_config_policy.h",
+    "component.cc",
+    "component.h",
+    "component_patcher.cc",
+    "component_patcher.h",
+    "component_patcher_operation.cc",
+    "component_patcher_operation.h",
+    "component_unpacker.cc",
+    "component_unpacker.h",
+    "configurator.h",
+    "crx_downloader.cc",
+    "crx_downloader.h",
+    "crx_update_item.h",
+    "network.cc",
+    "network.h",
+    "patcher.h",
+    "persisted_data.cc",
+    "persisted_data.h",
+    "ping_manager.cc",
+    "ping_manager.h",
+    "protocol_definition.cc",
+    "protocol_definition.h",
+    "protocol_handler.cc",
+    "protocol_handler.h",
+    "protocol_parser.cc",
+    "protocol_parser.h",
+    "protocol_parser_json.cc",
+    "protocol_parser_json.h",
+    "protocol_serializer.cc",
+    "protocol_serializer.h",
+    "protocol_serializer_json.cc",
+    "protocol_serializer_json.h",
+    "request_sender.cc",
+    "request_sender.h",
+    "task.h",
+    "task_send_uninstall_ping.cc",
+    "task_send_uninstall_ping.h",
+    "task_traits.h",
+    "task_update.cc",
+    "task_update.h",
+    "unzipper.h",
+    "update_checker.cc",
+    "update_checker.h",
+    "update_client.cc",
+    "update_client.h",
+    "update_client_errors.h",
+    "update_client_internal.h",
+    "update_engine.cc",
+    "update_engine.h",
+    "update_query_params.cc",
+    "update_query_params.h",
+    "update_query_params_delegate.cc",
+    "update_query_params_delegate.h",
+    "updater_state.cc",
+    "updater_state.h",
+    "updater_state_mac.mm",
+    "updater_state_win.cc",
+    "url_fetcher_downloader.cc",
+    "url_fetcher_downloader.h",
+    "utils.cc",
+    "utils.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/client_update_protocol",
+    "//components/crx_file",
+    "//components/prefs",
+    "//components/version_info:version_info",
+    "//courgette:courgette_lib",
+    "//crypto",
+    "//url",
+  ]
+}
+
+static_library("test_support") {
+  testonly = true
+  sources = [
+    "net/url_loader_post_interceptor.cc",
+    "net/url_loader_post_interceptor.h",
+    "test_configurator.cc",
+    "test_configurator.h",
+    "test_installer.cc",
+    "test_installer.h",
+  ]
+
+  public_deps = [
+    ":update_client",
+  ]
+
+  deps = [
+    ":network_impl",
+    ":patch_impl",
+    ":unzip_impl",
+    "//base",
+    "//components/prefs",
+    "//components/services/patch:in_process",
+    "//components/services/unzip:in_process",
+    "//mojo/public/cpp/bindings",
+    "//net:test_support",
+    "//services/network:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//url",
+  ]
+}
+
+bundle_data("unit_tests_bundle_data") {
+  visibility = [ ":unit_tests" ]
+  testonly = true
+  sources = [
+    "//components/test/data/update_client/binary_bsdiff_patch.bin",
+    "//components/test/data/update_client/binary_courgette_patch.bin",
+    "//components/test/data/update_client/binary_input.bin",
+    "//components/test/data/update_client/binary_output.bin",
+    "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx",
+    "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx",
+    "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx",
+    "//components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.crx",
+    "//components/test/data/update_client/runaction_test_win.crx3",
+    "//components/test/data/update_client/updatecheck_reply_1.json",
+    "//components/test/data/update_client/updatecheck_reply_4.json",
+    "//components/test/data/update_client/updatecheck_reply_noupdate.json",
+    "//components/test/data/update_client/updatecheck_reply_parse_error.json",
+    "//components/test/data/update_client/updatecheck_reply_unknownapp.json",
+  ]
+  outputs = [
+    "{{bundle_resources_dir}}/" +
+        "{{source_root_relative_dir}}/{{source_file_part}}",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "component_patcher_unittest.cc",
+    "component_patcher_unittest.h",
+    "component_unpacker_unittest.cc",
+    "persisted_data_unittest.cc",
+    "ping_manager_unittest.cc",
+    "protocol_parser_json_unittest.cc",
+    "protocol_serializer_json_unittest.cc",
+    "protocol_serializer_unittest.cc",
+    "request_sender_unittest.cc",
+    "update_checker_unittest.cc",
+    "update_client_unittest.cc",
+    "update_query_params_unittest.cc",
+    "updater_state_unittest.cc",
+    "utils_unittest.cc",
+  ]
+
+  if (!disable_file_support) {
+    sources += [ "crx_downloader_unittest.cc" ]
+  }
+
+  deps = [
+    ":network_impl",
+    ":patch_impl",
+    ":test_support",
+    ":unit_tests_bundle_data",
+    ":unzip_impl",
+    ":update_client",
+    "//base",
+    "//components/crx_file",
+    "//components/prefs",
+    "//components/prefs:test_support",
+    "//components/services/patch:in_process",
+    "//components/version_info:version_info",
+    "//courgette:courgette_lib",
+    "//net:test_support",
+    "//services/network:test_support",
+    "//services/network/public/cpp:cpp",
+    "//services/network/public/cpp:cpp_base",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//third_party/re2",
+  ]
+}
diff --git a/src/components/update_client/action_runner.cc b/src/components/update_client/action_runner.cc
new file mode 100644
index 0000000..490aeeb
--- /dev/null
+++ b/src/components/update_client/action_runner.cc
@@ -0,0 +1,142 @@
+// 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 "components/update_client/action_runner.h"
+
+#include <iterator>
+#include <stack>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/task_traits.h"
+#include "components/update_client/unzipper.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_engine.h"
+
+namespace {
+
+#if defined(OS_STARBOARD)
+void CleanupDirectory(base::FilePath& dir) {
+  std::stack<std::string> directories;
+  base::FileEnumerator file_enumerator(
+      dir, true,
+      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+  for (auto path = file_enumerator.Next(); !path.value().empty();
+       path = file_enumerator.Next()) {
+    base::FileEnumerator::FileInfo info(file_enumerator.GetInfo());
+
+    if (info.IsDirectory()) {
+      directories.push(path.value());
+    } else {
+      SbFileDelete(path.value().c_str());
+    }
+  }
+  while (!directories.empty()) {
+    SbFileDelete(directories.top().c_str());
+    directories.pop();
+  }
+}
+#endif
+
+}  // namespace
+
+namespace update_client {
+
+ActionRunner::ActionRunner(const Component& component)
+    : is_per_user_install_(component.config()->IsPerUserInstall()),
+      component_(component),
+      main_task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
+
+ActionRunner::~ActionRunner() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+void ActionRunner::Run(Callback run_complete) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  run_complete_ = std::move(run_complete);
+
+  base::CreateSequencedTaskRunnerWithTraits(kTaskTraits)
+      ->PostTask(
+          FROM_HERE,
+          base::BindOnce(&ActionRunner::RunOnTaskRunner, base::Unretained(this),
+                         component_.config()->GetUnzipperFactory()->Create(),
+                         component_.config()->GetPatcherFactory()->Create()));
+}
+
+void ActionRunner::RunOnTaskRunner(std::unique_ptr<Unzipper> unzip,
+                                   scoped_refptr<Patcher> patch) {
+  const auto installer = component_.crx_component()->installer;
+
+  base::FilePath crx_path;
+  installer->GetInstalledFile(component_.action_run(), &crx_path);
+
+  if (!is_per_user_install_) {
+    RunRecoveryCRXElevated(std::move(crx_path));
+    return;
+  }
+
+  const auto config = component_.config();
+  auto unpacker = base::MakeRefCounted<ComponentUnpacker>(
+      config->GetRunActionKeyHash(), crx_path, installer, std::move(unzip),
+      std::move(patch), component_.crx_component()->crx_format_requirement);
+  unpacker->Unpack(
+      base::BindOnce(&ActionRunner::UnpackComplete, base::Unretained(this)));
+}
+
+void ActionRunner::UnpackComplete(const ComponentUnpacker::Result& result) {
+  if (result.error != UnpackerError::kNone) {
+    DCHECK(!base::DirectoryExists(result.unpack_path));
+
+    main_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(run_complete_), false,
+                       static_cast<int>(result.error), result.extended_error));
+    return;
+  }
+
+  unpack_path_ = result.unpack_path;
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&ActionRunner::RunCommand, base::Unretained(this),
+                     MakeCommandLine(result.unpack_path)));
+}
+
+#if !defined(OS_WIN)
+
+void ActionRunner::RunRecoveryCRXElevated(const base::FilePath& crx_path) {
+  NOTREACHED();
+}
+
+void ActionRunner::RunCommand(const base::CommandLine& cmdline) {
+#if defined(OS_STARBOARD)
+  CleanupDirectory(unpack_path_);
+#else
+  base::DeleteFile(unpack_path_, true);
+#endif
+  main_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(std::move(run_complete_), false, -1, 0));
+}
+
+base::CommandLine ActionRunner::MakeCommandLine(
+    const base::FilePath& unpack_path) const {
+  return base::CommandLine(base::CommandLine::NO_PROGRAM);
+}
+
+#endif  // OS_WIN
+
+}  // namespace update_client
diff --git a/src/components/update_client/action_runner.h b/src/components/update_client/action_runner.h
new file mode 100644
index 0000000..8638fc2
--- /dev/null
+++ b/src/components/update_client/action_runner.h
@@ -0,0 +1,75 @@
+// 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 COMPONENTS_UPDATE_CLIENT_ACTION_RUNNER_H_
+#define COMPONENTS_UPDATE_CLIENT_ACTION_RUNNER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "build/build_config.h"
+#include "components/update_client/component_unpacker.h"
+
+namespace base {
+class CommandLine;
+class Process;
+class SingleThreadTaskRunner;
+}  // namespace base
+
+namespace update_client {
+
+class Component;
+
+class ActionRunner {
+ public:
+  using Callback =
+      base::OnceCallback<void(bool succeeded, int error_code, int extra_code1)>;
+
+  explicit ActionRunner(const Component& component);
+  ~ActionRunner();
+
+  void Run(Callback run_complete);
+
+ private:
+  void RunOnTaskRunner(std::unique_ptr<Unzipper> unzipper,
+                       scoped_refptr<Patcher> patcher);
+  void UnpackComplete(const ComponentUnpacker::Result& result);
+
+  void RunCommand(const base::CommandLine& cmdline);
+  void RunRecoveryCRXElevated(const base::FilePath& crx_path);
+
+  base::CommandLine MakeCommandLine(const base::FilePath& unpack_path) const;
+
+  void WaitForCommand(base::Process process);
+
+#if defined(OS_WIN)
+  void RunRecoveryCRXElevatedInSTA(const base::FilePath& crx_path);
+#endif
+
+  bool is_per_user_install_ = false;
+  const Component& component_;
+
+  // Used to post callbacks to the main thread.
+  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+
+  // Contains the unpack path for the component associated with the run action.
+  base::FilePath unpack_path_;
+
+  Callback run_complete_;
+
+  THREAD_CHECKER(thread_checker_);
+  DISALLOW_COPY_AND_ASSIGN(ActionRunner);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_ACTION_RUNNER_H_
diff --git a/src/components/update_client/action_runner_win.cc b/src/components/update_client/action_runner_win.cc
new file mode 100644
index 0000000..6985ee6
--- /dev/null
+++ b/src/components/update_client/action_runner_win.cc
@@ -0,0 +1,91 @@
+// 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 "components/update_client/action_runner.h"
+
+#include <tuple>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/process/launch.h"
+#include "base/process/process.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/post_task.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/task_traits.h"
+
+namespace {
+
+const base::FilePath::CharType kRecoveryFileName[] =
+    FILE_PATH_LITERAL("ChromeRecovery.exe");
+
+}  // namespace
+
+namespace update_client {
+
+void ActionRunner::RunCommand(const base::CommandLine& cmdline) {
+  base::LaunchOptions options;
+  options.start_hidden = true;
+  base::Process process = base::LaunchProcess(cmdline, options);
+
+  base::PostTaskWithTraits(
+      FROM_HERE, kTaskTraitsRunCommand,
+      base::BindOnce(&ActionRunner::WaitForCommand, base::Unretained(this),
+                     std::move(process)));
+}
+
+void ActionRunner::WaitForCommand(base::Process process) {
+  int exit_code = 0;
+  const base::TimeDelta kMaxWaitTime = base::TimeDelta::FromSeconds(600);
+  const bool succeeded =
+      process.WaitForExitWithTimeout(kMaxWaitTime, &exit_code);
+  base::DeleteFile(unpack_path_, true);
+  main_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(run_complete_), succeeded, exit_code, 0));
+}
+
+base::CommandLine ActionRunner::MakeCommandLine(
+    const base::FilePath& unpack_path) const {
+  base::CommandLine command_line(unpack_path.Append(kRecoveryFileName));
+  if (!is_per_user_install_)
+    command_line.AppendSwitch("system");
+  command_line.AppendSwitchASCII(
+      "browser-version", component_.config()->GetBrowserVersion().GetString());
+  command_line.AppendSwitchASCII("sessionid", component_.session_id());
+  const auto app_guid = component_.config()->GetAppGuid();
+  if (!app_guid.empty())
+    command_line.AppendSwitchASCII("appguid", app_guid);
+  VLOG(1) << "run action: " << command_line.GetCommandLineString();
+  return command_line;
+}
+
+void ActionRunner::RunRecoveryCRXElevated(const base::FilePath& crx_path) {
+  base::CreateCOMSTATaskRunnerWithTraits(
+      kTaskTraitsRunCommand, base::SingleThreadTaskRunnerThreadMode::DEDICATED)
+      ->PostTask(FROM_HERE,
+                 base::BindOnce(&ActionRunner::RunRecoveryCRXElevatedInSTA,
+                                base::Unretained(this), crx_path));
+}
+
+void ActionRunner::RunRecoveryCRXElevatedInSTA(const base::FilePath& crx_path) {
+  bool succeeded = false;
+  int error_code = 0;
+  int extra_code = 0;
+  const auto config = component_.config();
+  std::tie(succeeded, error_code, extra_code) =
+      component_.config()->GetRecoveryCRXElevator().Run(
+          crx_path, config->GetAppGuid(),
+          config->GetBrowserVersion().GetString(), component_.session_id());
+  main_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(std::move(run_complete_), succeeded, error_code,
+                                extra_code));
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/activity_data_service.h b/src/components/update_client/activity_data_service.h
new file mode 100644
index 0000000..5d3744c
--- /dev/null
+++ b/src/components/update_client/activity_data_service.h
@@ -0,0 +1,42 @@
+// 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 COMPONENTS_UPDATE_CLIENT_ACTIVITY_DATA_SERVICE_H_
+#define COMPONENTS_UPDATE_CLIENT_ACTIVITY_DATA_SERVICE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+
+namespace update_client {
+
+const int kDateFirstTime = -1;
+const int kDaysFirstTime = -1;
+const int kDateUnknown = -2;
+const int kDaysUnknown = -2;
+
+// This is an interface that injects certain update information (active, days
+// since ...) into the update engine of the update client.
+// GetDaysSinceLastActive and GetDaysSinceLastRollCall are used for backward
+// compatibility.
+class ActivityDataService {
+ public:
+  // Returns the current state of the active bit of the specified |id|.
+  virtual bool GetActiveBit(const std::string& id) const = 0;
+
+  // Clears the active bit of the specified |id|.
+  virtual void ClearActiveBit(const std::string& id) = 0;
+
+  // The following 2 functions return the number of days since last
+  // active/roll call.
+  virtual int GetDaysSinceLastActive(const std::string& id) const = 0;
+  virtual int GetDaysSinceLastRollCall(const std::string& id) const = 0;
+
+  virtual ~ActivityDataService() {}
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_ACTIVITY_DATA_SERVICE_H_
diff --git a/src/components/update_client/background_downloader_win.cc b/src/components/update_client/background_downloader_win.cc
new file mode 100644
index 0000000..860c494
--- /dev/null
+++ b/src/components/update_client/background_downloader_win.cc
@@ -0,0 +1,901 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/background_downloader_win.h"
+
+#include <objbase.h>
+#include <winerror.h>
+
+#include <stddef.h>
+#include <stdint.h>
+#include <functional>
+#include <iomanip>
+#include <limits>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/win/atl.h"
+#include "base/win/scoped_co_mem.h"
+#include "components/update_client/task_traits.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+#include "url/gurl.h"
+
+using Microsoft::WRL::ComPtr;
+using base::win::ScopedCoMem;
+
+// The class BackgroundDownloader in this module is an adapter between
+// the CrxDownloader interface and the BITS service interfaces.
+// The interface exposed on the CrxDownloader code runs on the main thread,
+// while the BITS specific code runs on a separate thread passed in by the
+// client. For every url to download, a BITS job is created, unless there is
+// already an existing job for that url, in which case, the downloader
+// connects to it. Once a job is associated with the url, the code looks for
+// changes in the BITS job state. The checks are triggered by a timer.
+// The BITS job contains just one file to download. There could only be one
+// download in progress at a time. If Chrome closes down before the download is
+// complete, the BITS job remains active and finishes in the background, without
+// any intervention. The job can be completed next time the code runs, if the
+// file is still needed, otherwise it will be cleaned up on a periodic basis.
+//
+// To list the BITS jobs for a user, use the |bitsadmin| tool. The command line
+// to do that is: "bitsadmin /list /verbose". Another useful command is
+// "bitsadmin /info" and provide the job id returned by the previous /list
+// command.
+//
+// Ignoring the suspend/resume issues since this code is not using them, the
+// job state machine implemented by BITS is something like this:
+//
+//  Suspended--->Queued--->Connecting---->Transferring--->Transferred
+//       |          ^         |                 |               |
+//       |          |         V                 V               | (complete)
+//       +----------|---------+-----------------+-----+         V
+//                  |         |                 |     |    Acknowledged
+//                  |         V                 V     |
+//                  |  Transient Error------->Error   |
+//                  |         |                 |     |(cancel)
+//                  |         +-------+---------+--->-+
+//                  |                 V               |
+//                  |   (resume)      |               |
+//                  +------<----------+               +---->Cancelled
+//
+// The job is created in the "suspended" state. Once |Resume| is called,
+// BITS queues up the job, then tries to connect, begins transferring the
+// job bytes, and moves the job to the "transferred state, after the job files
+// have been transferred. When calling |Complete| for a job, the job files are
+// made available to the caller, and the job is moved to the "acknowledged"
+// state.
+// At any point, the job can be cancelled, in which case, the job is moved
+// to the "cancelled state" and the job object is removed from the BITS queue.
+// Along the way, the job can encounter recoverable and non-recoverable errors.
+// BITS moves the job to "transient error" or "error", depending on which kind
+// of error has occured.
+// If  the job has reached the "transient error" state, BITS retries the
+// job after a certain programmable delay. If the job can't be completed in a
+// certain time interval, BITS stops retrying and errors the job out. This time
+// interval is also programmable.
+// If the job is in either of the error states, the job parameters can be
+// adjusted to handle the error, after which the job can be resumed, and the
+// whole cycle starts again.
+// Jobs that are not touched in 90 days (or a value set by group policy) are
+// automatically disposed off by BITS. This concludes the brief description of
+// a job lifetime, according to BITS.
+//
+// In addition to how BITS is managing the life time of the job, there are a
+// couple of special cases defined by the BackgroundDownloader.
+// First, if the job encounters any of the 5xx HTTP responses, the job is
+// not retried, in order to avoid DDOS-ing the servers.
+// Second, there is a simple mechanism to detect stuck jobs, and allow the rest
+// of the code to move on to trying other urls or trying other components.
+// Last, after completing a job, irrespective of the outcome, the jobs older
+// than a week are proactively cleaned up.
+
+namespace update_client {
+
+namespace {
+
+// All jobs created by this module have a specific description so they can
+// be found at run-time or by using system administration tools.
+const base::char16 kJobName[] = L"Chrome Component Updater";
+
+// How often the code looks for changes in the BITS job state.
+const int kJobPollingIntervalSec = 4;
+
+// How long BITS waits before retrying a job after the job encountered
+// a transient error. If this value is not set, the BITS default is 10 minutes.
+const int kMinimumRetryDelayMin = 1;
+
+// How long to wait for stuck jobs. Stuck jobs could be queued for too long,
+// have trouble connecting, could be suspended for any reason, or they have
+// encountered some transient error.
+const int kJobStuckTimeoutMin = 15;
+
+// How long BITS waits before giving up on a job that could not be completed
+// since the job has encountered its first transient error. If this value is
+// not set, the BITS default is 14 days.
+const int kSetNoProgressTimeoutDays = 1;
+
+// How often the jobs which were started but not completed for any reason
+// are cleaned up. Reasons for jobs to be left behind include browser restarts,
+// system restarts, etc. Also, the check to purge stale jobs only happens
+// at most once a day. If the job clean up code is not running, the BITS
+// default policy is to cancel jobs after 90 days of inactivity.
+const int kPurgeStaleJobsAfterDays = 3;
+const int kPurgeStaleJobsIntervalBetweenChecksDays = 1;
+
+// Number of maximum BITS jobs this downloader can create and queue up.
+const int kMaxQueuedJobs = 10;
+
+// Retrieves the singleton instance of GIT for this process.
+HRESULT GetGit(ComPtr<IGlobalInterfaceTable>* git) {
+  return ::CoCreateInstance(CLSID_StdGlobalInterfaceTable, nullptr,
+                            CLSCTX_INPROC_SERVER,
+                            IID_PPV_ARGS(git->GetAddressOf()));
+}
+
+// Retrieves an interface pointer from the process GIT for a given |cookie|.
+HRESULT GetInterfaceFromGit(const ComPtr<IGlobalInterfaceTable>& git,
+                            DWORD cookie,
+                            REFIID riid,
+                            void** ppv) {
+  return git->GetInterfaceFromGlobal(cookie, riid, ppv);
+}
+
+// Registers an interface pointer in GIT and returns its corresponding |cookie|.
+template <typename T>
+HRESULT RegisterInterfaceInGit(const ComPtr<IGlobalInterfaceTable>& git,
+                               const ComPtr<T>& p,
+                               DWORD* cookie) {
+  return git->RegisterInterfaceInGlobal(p.Get(), __uuidof(T), cookie);
+}
+
+// Returns the status code from a given BITS error.
+int GetHttpStatusFromBitsError(HRESULT error) {
+  // BITS errors are defined in bitsmsg.h. Although not documented, it is
+  // clear that all errors corresponding to http status code have the high
+  // word equal to 0x8019 and the low word equal to the http status code.
+  const int kHttpStatusFirst = 100;  // Continue.
+  const int kHttpStatusLast = 505;   // Version not supported.
+  bool is_valid = HIWORD(error) == 0x8019 &&
+                  LOWORD(error) >= kHttpStatusFirst &&
+                  LOWORD(error) <= kHttpStatusLast;
+  return is_valid ? LOWORD(error) : 0;
+}
+
+// Returns the files in a BITS job.
+HRESULT GetFilesInJob(const ComPtr<IBackgroundCopyJob>& job,
+                      std::vector<ComPtr<IBackgroundCopyFile>>* files) {
+  ComPtr<IEnumBackgroundCopyFiles> enum_files;
+  HRESULT hr = job->EnumFiles(enum_files.GetAddressOf());
+  if (FAILED(hr))
+    return hr;
+
+  ULONG num_files = 0;
+  hr = enum_files->GetCount(&num_files);
+  if (FAILED(hr))
+    return hr;
+
+  for (ULONG i = 0; i != num_files; ++i) {
+    ComPtr<IBackgroundCopyFile> file;
+    if (enum_files->Next(1, file.GetAddressOf(), nullptr) == S_OK && file.Get())
+      files->push_back(file);
+  }
+
+  return S_OK;
+}
+
+// Returns the file name, the url, and some per-file progress information.
+// The function out parameters can be NULL if that data is not requested.
+HRESULT GetJobFileProperties(const ComPtr<IBackgroundCopyFile>& file,
+                             base::string16* local_name,
+                             base::string16* remote_name,
+                             BG_FILE_PROGRESS* progress) {
+  if (!file)
+    return E_FAIL;
+
+  HRESULT hr = S_OK;
+
+  if (local_name) {
+    ScopedCoMem<base::char16> name;
+    hr = file->GetLocalName(&name);
+    if (FAILED(hr))
+      return hr;
+    local_name->assign(name);
+  }
+
+  if (remote_name) {
+    ScopedCoMem<base::char16> name;
+    hr = file->GetRemoteName(&name);
+    if (FAILED(hr))
+      return hr;
+    remote_name->assign(name);
+  }
+
+  if (progress) {
+    BG_FILE_PROGRESS bg_file_progress = {};
+    hr = file->GetProgress(&bg_file_progress);
+    if (FAILED(hr))
+      return hr;
+    *progress = bg_file_progress;
+  }
+
+  return hr;
+}
+
+// Returns the number of bytes downloaded and bytes to download for all files
+// in the job. If the values are not known or if an error has occurred,
+// a value of -1 is reported.
+HRESULT GetJobByteCount(const ComPtr<IBackgroundCopyJob>& job,
+                        int64_t* downloaded_bytes,
+                        int64_t* total_bytes) {
+  *downloaded_bytes = -1;
+  *total_bytes = -1;
+
+  if (!job)
+    return E_FAIL;
+
+  BG_JOB_PROGRESS job_progress = {};
+  HRESULT hr = job->GetProgress(&job_progress);
+  if (FAILED(hr))
+    return hr;
+
+  const uint64_t kMaxNumBytes =
+      static_cast<uint64_t>(std::numeric_limits<int64_t>::max());
+  if (job_progress.BytesTransferred <= kMaxNumBytes)
+    *downloaded_bytes = job_progress.BytesTransferred;
+
+  if (job_progress.BytesTotal <= kMaxNumBytes &&
+      job_progress.BytesTotal != BG_SIZE_UNKNOWN)
+    *total_bytes = job_progress.BytesTotal;
+
+  return S_OK;
+}
+
+HRESULT GetJobDisplayName(const ComPtr<IBackgroundCopyJob>& job,
+                          base::string16* name) {
+  ScopedCoMem<base::char16> local_name;
+  const HRESULT hr = job->GetDisplayName(&local_name);
+  if (FAILED(hr))
+    return hr;
+  *name = local_name.get();
+  return S_OK;
+}
+
+// Returns the job error code in |error_code| if the job is in the transient
+// or the final error state. Otherwise, the job error is not available and
+// the function fails.
+HRESULT GetJobError(const ComPtr<IBackgroundCopyJob>& job,
+                    HRESULT* error_code_out) {
+  *error_code_out = S_OK;
+  ComPtr<IBackgroundCopyError> copy_error;
+  HRESULT hr = job->GetError(copy_error.GetAddressOf());
+  if (FAILED(hr))
+    return hr;
+
+  BG_ERROR_CONTEXT error_context = BG_ERROR_CONTEXT_NONE;
+  HRESULT error_code = S_OK;
+  hr = copy_error->GetError(&error_context, &error_code);
+  if (FAILED(hr))
+    return hr;
+
+  *error_code_out = FAILED(error_code) ? error_code : E_FAIL;
+  return S_OK;
+}
+
+// Finds the component updater jobs matching the given predicate.
+// Returns S_OK if the function has found at least one job, returns S_FALSE if
+// no job was found, and it returns an error otherwise.
+template <class Predicate>
+HRESULT FindBitsJobIf(Predicate pred,
+                      const ComPtr<IBackgroundCopyManager>& bits_manager,
+                      std::vector<ComPtr<IBackgroundCopyJob>>* jobs) {
+  ComPtr<IEnumBackgroundCopyJobs> enum_jobs;
+  HRESULT hr = bits_manager->EnumJobs(0, enum_jobs.GetAddressOf());
+  if (FAILED(hr))
+    return hr;
+
+  ULONG job_count = 0;
+  hr = enum_jobs->GetCount(&job_count);
+  if (FAILED(hr))
+    return hr;
+
+  // Iterate over jobs, run the predicate, and select the job only if
+  // the job description matches the component updater jobs.
+  for (ULONG i = 0; i != job_count; ++i) {
+    ComPtr<IBackgroundCopyJob> current_job;
+    if (enum_jobs->Next(1, current_job.GetAddressOf(), nullptr) == S_OK &&
+        pred(current_job)) {
+      base::string16 job_name;
+      hr = GetJobDisplayName(current_job, &job_name);
+      if (job_name.compare(kJobName) == 0)
+        jobs->push_back(current_job);
+    }
+  }
+
+  return jobs->empty() ? S_FALSE : S_OK;
+}
+
+bool JobCreationOlderThanDaysPredicate(ComPtr<IBackgroundCopyJob> job,
+                                       int num_days) {
+  BG_JOB_TIMES times = {};
+  HRESULT hr = job->GetTimes(&times);
+  if (FAILED(hr))
+    return false;
+
+  const base::TimeDelta time_delta(base::TimeDelta::FromDays(num_days));
+  const base::Time creation_time(base::Time::FromFileTime(times.CreationTime));
+
+  return creation_time + time_delta < base::Time::Now();
+}
+
+bool JobFileUrlEqualPredicate(ComPtr<IBackgroundCopyJob> job, const GURL& url) {
+  std::vector<ComPtr<IBackgroundCopyFile>> files;
+  HRESULT hr = GetFilesInJob(job, &files);
+  if (FAILED(hr))
+    return false;
+
+  for (size_t i = 0; i != files.size(); ++i) {
+    ScopedCoMem<base::char16> remote_name;
+    if (SUCCEEDED(files[i]->GetRemoteName(&remote_name)) &&
+        url == GURL(base::StringPiece16(remote_name)))
+      return true;
+  }
+
+  return false;
+}
+
+// Creates an instance of the BITS manager.
+HRESULT CreateBitsManager(ComPtr<IBackgroundCopyManager>* bits_manager) {
+  ComPtr<IBackgroundCopyManager> local_bits_manager;
+  HRESULT hr =
+      ::CoCreateInstance(__uuidof(BackgroundCopyManager), nullptr, CLSCTX_ALL,
+                         IID_PPV_ARGS(&local_bits_manager));
+  if (FAILED(hr)) {
+    return hr;
+  }
+  *bits_manager = local_bits_manager;
+  return S_OK;
+}
+
+void CleanupJob(const ComPtr<IBackgroundCopyJob>& job) {
+  if (!job)
+    return;
+
+  // Get the file paths associated with this job before canceling the job.
+  // Canceling the job removes it from the BITS queue right away. It appears
+  // that it is still possible to query for the properties of the job after
+  // the job has been canceled. It seems safer though to get the paths first.
+  std::vector<ComPtr<IBackgroundCopyFile>> files;
+  GetFilesInJob(job, &files);
+
+  std::vector<base::FilePath> paths;
+  for (const auto& file : files) {
+    base::string16 local_name;
+    HRESULT hr = GetJobFileProperties(file, &local_name, nullptr, nullptr);
+    if (SUCCEEDED(hr))
+      paths.push_back(base::FilePath(local_name));
+  }
+
+  job->Cancel();
+
+  for (const auto& path : paths)
+    DeleteFileAndEmptyParentDirectory(path);
+}
+
+}  // namespace
+
+BackgroundDownloader::BackgroundDownloader(
+    std::unique_ptr<CrxDownloader> successor)
+    : CrxDownloader(std::move(successor)),
+      com_task_runner_(base::CreateCOMSTATaskRunnerWithTraits(
+          kTaskTraitsBackgroundDownloader)),
+      git_cookie_bits_manager_(0),
+      git_cookie_job_(0) {}
+
+BackgroundDownloader::~BackgroundDownloader() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  timer_.reset();
+}
+
+void BackgroundDownloader::StartTimer() {
+  timer_.reset(new base::OneShotTimer);
+  timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(kJobPollingIntervalSec),
+                this, &BackgroundDownloader::OnTimer);
+}
+
+void BackgroundDownloader::OnTimer() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  com_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&BackgroundDownloader::OnDownloading,
+                                base::Unretained(this)));
+}
+
+void BackgroundDownloader::DoStartDownload(const GURL& url) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  com_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&BackgroundDownloader::BeginDownload,
+                                base::Unretained(this), url));
+}
+
+// Called one time when this class is asked to do a download.
+void BackgroundDownloader::BeginDownload(const GURL& url) {
+  DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+  download_start_time_ = base::TimeTicks::Now();
+  job_stuck_begin_time_ = download_start_time_;
+
+  HRESULT hr = BeginDownloadHelper(url);
+  if (FAILED(hr)) {
+    EndDownload(hr);
+    return;
+  }
+
+  VLOG(1) << "Starting BITS download for: " << url.spec();
+
+  ResetInterfacePointers();
+  main_task_runner()->PostTask(FROM_HERE,
+                               base::BindOnce(&BackgroundDownloader::StartTimer,
+                                              base::Unretained(this)));
+}
+
+// Creates or opens an existing BITS job to download the |url|, and handles
+// the marshalling of the interfaces in GIT.
+HRESULT BackgroundDownloader::BeginDownloadHelper(const GURL& url) {
+  ComPtr<IGlobalInterfaceTable> git;
+  HRESULT hr = GetGit(&git);
+  if (FAILED(hr))
+    return hr;
+
+  hr = CreateBitsManager(&bits_manager_);
+  if (FAILED(hr))
+    return hr;
+
+  hr = QueueBitsJob(url, &job_);
+  if (FAILED(hr))
+    return hr;
+
+  hr = RegisterInterfaceInGit(git, bits_manager_, &git_cookie_bits_manager_);
+  if (FAILED(hr))
+    return hr;
+
+  hr = RegisterInterfaceInGit(git, job_, &git_cookie_job_);
+  if (FAILED(hr))
+    return hr;
+
+  return S_OK;
+}
+
+// Called any time the timer fires.
+void BackgroundDownloader::OnDownloading() {
+  DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+  HRESULT hr = UpdateInterfacePointers();
+  if (FAILED(hr)) {
+    EndDownload(hr);
+    return;
+  }
+
+  BG_JOB_STATE job_state = BG_JOB_STATE_ERROR;
+  hr = job_->GetState(&job_state);
+  if (FAILED(hr)) {
+    EndDownload(hr);
+    return;
+  }
+
+  bool is_handled = false;
+  switch (job_state) {
+    case BG_JOB_STATE_TRANSFERRED:
+      is_handled = OnStateTransferred();
+      break;
+
+    case BG_JOB_STATE_ERROR:
+      is_handled = OnStateError();
+      break;
+
+    case BG_JOB_STATE_CANCELLED:
+      is_handled = OnStateCancelled();
+      break;
+
+    case BG_JOB_STATE_ACKNOWLEDGED:
+      is_handled = OnStateAcknowledged();
+      break;
+
+    case BG_JOB_STATE_QUEUED:
+    // Fall through.
+    case BG_JOB_STATE_CONNECTING:
+    // Fall through.
+    case BG_JOB_STATE_SUSPENDED:
+      is_handled = OnStateQueued();
+      break;
+
+    case BG_JOB_STATE_TRANSIENT_ERROR:
+      is_handled = OnStateTransientError();
+      break;
+
+    case BG_JOB_STATE_TRANSFERRING:
+      is_handled = OnStateTransferring();
+      break;
+
+    default:
+      break;
+  }
+
+  if (is_handled)
+    return;
+
+  ResetInterfacePointers();
+  main_task_runner()->PostTask(FROM_HERE,
+                               base::BindOnce(&BackgroundDownloader::StartTimer,
+                                              base::Unretained(this)));
+}
+
+// Completes the BITS download, picks up the file path of the response, and
+// notifies the CrxDownloader. The function should be called only once.
+void BackgroundDownloader::EndDownload(HRESULT error) {
+  DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+  const base::TimeTicks download_end_time(base::TimeTicks::Now());
+  const base::TimeDelta download_time =
+      download_end_time >= download_start_time_
+          ? download_end_time - download_start_time_
+          : base::TimeDelta();
+
+  int64_t downloaded_bytes = -1;
+  int64_t total_bytes = -1;
+  GetJobByteCount(job_, &downloaded_bytes, &total_bytes);
+
+  if (FAILED(error))
+    CleanupJob(job_);
+
+  ClearGit();
+
+  // Consider the url handled if it has been successfully downloaded or a
+  // 5xx has been received.
+  const bool is_handled =
+      SUCCEEDED(error) || IsHttpServerError(GetHttpStatusFromBitsError(error));
+
+  const int error_to_report = SUCCEEDED(error) ? 0 : error;
+
+  DCHECK(static_cast<bool>(error_to_report) == !base::PathExists(response_));
+
+  DownloadMetrics download_metrics;
+  download_metrics.url = url();
+  download_metrics.downloader = DownloadMetrics::kBits;
+  download_metrics.error = error_to_report;
+  download_metrics.downloaded_bytes = downloaded_bytes;
+  download_metrics.total_bytes = total_bytes;
+  download_metrics.download_time_ms = download_time.InMilliseconds();
+
+  Result result;
+  result.error = error_to_report;
+  if (!result.error)
+    result.response = response_;
+  main_task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&BackgroundDownloader::OnDownloadComplete,
+                                base::Unretained(this), is_handled, result,
+                                download_metrics));
+
+  // Once the task is posted to the the main thread, this object may be deleted
+  // by its owner. It is not safe to access members of this object on this task
+  // runner from now on.
+}
+
+// Called when the BITS job has been transferred successfully. Completes the
+// BITS job by removing it from the BITS queue and making the download
+// available to the caller.
+bool BackgroundDownloader::OnStateTransferred() {
+  EndDownload(CompleteJob());
+  return true;
+}
+
+// Called when the job has encountered an error and no further progress can
+// be made. Cancels this job and removes it from the BITS queue.
+bool BackgroundDownloader::OnStateError() {
+  HRESULT error_code = S_OK;
+  HRESULT hr = GetJobError(job_, &error_code);
+  if (FAILED(hr))
+    error_code = hr;
+
+  DCHECK(FAILED(error_code));
+  EndDownload(error_code);
+  return true;
+}
+
+// Called when the download was completed. This notification is not seen
+// in the current implementation but provided here as a defensive programming
+// measure.
+bool BackgroundDownloader::OnStateAcknowledged() {
+  EndDownload(E_UNEXPECTED);
+  return true;
+}
+
+// Called when the download was cancelled. Same as above.
+bool BackgroundDownloader::OnStateCancelled() {
+  EndDownload(E_UNEXPECTED);
+  return true;
+}
+
+// Called when the job has encountered a transient error, such as a
+// network disconnect, a server error, or some other recoverable error.
+bool BackgroundDownloader::OnStateTransientError() {
+  // If the job appears to be stuck, handle the transient error as if
+  // it were a final error. This causes the job to be cancelled and a specific
+  // error be returned, if the error was available.
+  if (IsStuck()) {
+    return OnStateError();
+  }
+
+  // Don't retry at all if the transient error was a 5xx.
+  HRESULT error_code = S_OK;
+  HRESULT hr = GetJobError(job_, &error_code);
+  if (SUCCEEDED(hr) &&
+      IsHttpServerError(GetHttpStatusFromBitsError(error_code))) {
+    return OnStateError();
+  }
+
+  return false;
+}
+
+bool BackgroundDownloader::OnStateQueued() {
+  if (!IsStuck())
+    return false;
+
+  // Terminate the download if the job has not made progress in a while.
+  EndDownload(E_ABORT);
+  return true;
+}
+
+bool BackgroundDownloader::OnStateTransferring() {
+  // Resets the baseline for detecting a stuck job since the job is transferring
+  // data and it is making progress.
+  job_stuck_begin_time_ = base::TimeTicks::Now();
+
+  main_task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&BackgroundDownloader::OnDownloadProgress,
+                                base::Unretained(this)));
+  return false;
+}
+
+HRESULT BackgroundDownloader::QueueBitsJob(const GURL& url,
+                                           ComPtr<IBackgroundCopyJob>* job) {
+  DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+  size_t num_jobs = std::numeric_limits<size_t>::max();
+  HRESULT hr = GetBackgroundDownloaderJobCount(&num_jobs);
+
+  // The metric records a large number if the job count can't be read.
+  UMA_HISTOGRAM_COUNTS_100("UpdateClient.BackgroundDownloaderJobs", num_jobs);
+
+  // Remove some old jobs from the BITS queue before creating new jobs.
+  CleanupStaleJobs();
+
+  if (FAILED(hr) || num_jobs >= kMaxQueuedJobs)
+    return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF,
+                        CrxDownloaderError::BITS_TOO_MANY_JOBS);
+
+  ComPtr<IBackgroundCopyJob> local_job;
+  hr = CreateOrOpenJob(url, &local_job);
+  if (FAILED(hr)) {
+    CleanupJob(local_job);
+    return hr;
+  }
+
+  const bool is_new_job = hr == S_OK;
+  UMA_HISTOGRAM_BOOLEAN("UpdateClient.BackgroundDownloaderNewJob", is_new_job);
+
+  hr = local_job->Resume();
+  if (FAILED(hr)) {
+    CleanupJob(local_job);
+    return hr;
+  }
+
+  *job = local_job;
+  return S_OK;
+}
+
+HRESULT BackgroundDownloader::CreateOrOpenJob(const GURL& url,
+                                              ComPtr<IBackgroundCopyJob>* job) {
+  std::vector<ComPtr<IBackgroundCopyJob>> jobs;
+  HRESULT hr = FindBitsJobIf(
+      [&url](ComPtr<IBackgroundCopyJob> job) {
+        return JobFileUrlEqualPredicate(job, url);
+      },
+      bits_manager_, &jobs);
+  if (SUCCEEDED(hr) && !jobs.empty()) {
+    *job = jobs.front();
+    return S_FALSE;
+  }
+
+  ComPtr<IBackgroundCopyJob> local_job;
+
+  GUID guid = {0};
+  hr = bits_manager_->CreateJob(kJobName, BG_JOB_TYPE_DOWNLOAD, &guid,
+                                local_job.GetAddressOf());
+  if (FAILED(hr)) {
+    CleanupJob(local_job);
+    return hr;
+  }
+
+  hr = InitializeNewJob(local_job, url);
+  if (FAILED(hr)) {
+    CleanupJob(local_job);
+    return hr;
+  }
+
+  *job = local_job;
+  return S_OK;
+}
+
+HRESULT BackgroundDownloader::InitializeNewJob(
+    const ComPtr<IBackgroundCopyJob>& job,
+    const GURL& url) {
+  base::FilePath tempdir;
+  if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_BITS_"),
+                                    &tempdir))
+    return E_FAIL;
+
+  const base::string16 filename(base::SysUTF8ToWide(url.ExtractFileName()));
+  HRESULT hr = job->AddFile(base::SysUTF8ToWide(url.spec()).c_str(),
+                            tempdir.Append(filename).AsUTF16Unsafe().c_str());
+  if (FAILED(hr))
+    return hr;
+
+  hr = job->SetDescription(filename.c_str());
+  if (FAILED(hr))
+    return hr;
+
+  hr = job->SetPriority(BG_JOB_PRIORITY_NORMAL);
+  if (FAILED(hr))
+    return hr;
+
+  hr = job->SetMinimumRetryDelay(60 * kMinimumRetryDelayMin);
+  if (FAILED(hr))
+    return hr;
+
+  const int kSecondsDay = 60 * 60 * 24;
+  hr = job->SetNoProgressTimeout(kSecondsDay * kSetNoProgressTimeoutDays);
+  if (FAILED(hr))
+    return hr;
+
+  return S_OK;
+}
+
+bool BackgroundDownloader::IsStuck() {
+  const base::TimeDelta job_stuck_timeout(
+      base::TimeDelta::FromMinutes(kJobStuckTimeoutMin));
+  return job_stuck_begin_time_ + job_stuck_timeout < base::TimeTicks::Now();
+}
+
+HRESULT BackgroundDownloader::CompleteJob() {
+  HRESULT hr = job_->Complete();
+  if (FAILED(hr) && hr != BG_S_UNABLE_TO_DELETE_FILES)
+    return hr;
+
+  std::vector<ComPtr<IBackgroundCopyFile>> files;
+  hr = GetFilesInJob(job_, &files);
+  if (FAILED(hr))
+    return hr;
+
+  if (files.empty())
+    return E_UNEXPECTED;
+
+  base::string16 local_name;
+  BG_FILE_PROGRESS progress = {0};
+  hr = GetJobFileProperties(files.front(), &local_name, nullptr, &progress);
+  if (FAILED(hr))
+    return hr;
+
+  // Sanity check the post-conditions of a successful download, including
+  // the file and job invariants. The byte counts for a job and its file
+  // must match as a job only contains one file.
+  DCHECK(progress.Completed);
+  DCHECK_EQ(progress.BytesTotal, progress.BytesTransferred);
+
+  response_ = base::FilePath(local_name);
+
+  return S_OK;
+}
+
+HRESULT BackgroundDownloader::UpdateInterfacePointers() {
+  DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+  bits_manager_ = nullptr;
+  job_ = nullptr;
+
+  ComPtr<IGlobalInterfaceTable> git;
+  HRESULT hr = GetGit(&git);
+  if (FAILED(hr))
+    return hr;
+
+  hr = GetInterfaceFromGit(git, git_cookie_bits_manager_,
+                           IID_PPV_ARGS(bits_manager_.GetAddressOf()));
+  if (FAILED(hr))
+    return hr;
+
+  hr = GetInterfaceFromGit(git, git_cookie_job_,
+                           IID_PPV_ARGS(job_.GetAddressOf()));
+  if (FAILED(hr))
+    return hr;
+
+  return S_OK;
+}
+
+void BackgroundDownloader::ResetInterfacePointers() {
+  job_ = nullptr;
+  bits_manager_ = nullptr;
+}
+
+HRESULT BackgroundDownloader::ClearGit() {
+  DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+  ResetInterfacePointers();
+
+  ComPtr<IGlobalInterfaceTable> git;
+  HRESULT hr = GetGit(&git);
+  if (FAILED(hr))
+    return hr;
+
+  const DWORD cookies[] = {git_cookie_job_, git_cookie_bits_manager_};
+
+  for (auto cookie : cookies) {
+    // TODO(sorin): check the result of the call, see crbug.com/644857.
+    git->RevokeInterfaceFromGlobal(cookie);
+  }
+
+  return S_OK;
+}
+
+HRESULT BackgroundDownloader::GetBackgroundDownloaderJobCount(
+    size_t* num_jobs) {
+  DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(bits_manager_);
+
+  std::vector<ComPtr<IBackgroundCopyJob>> jobs;
+  const HRESULT hr =
+      FindBitsJobIf([](const ComPtr<IBackgroundCopyJob>&) { return true; },
+                    bits_manager_, &jobs);
+  if (FAILED(hr))
+    return hr;
+
+  *num_jobs = jobs.size();
+  return S_OK;
+}
+
+void BackgroundDownloader::CleanupStaleJobs() {
+  DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(bits_manager_);
+
+  static base::Time last_sweep;
+
+  const base::TimeDelta time_delta(
+      base::TimeDelta::FromDays(kPurgeStaleJobsIntervalBetweenChecksDays));
+  const base::Time current_time(base::Time::Now());
+  if (last_sweep + time_delta > current_time)
+    return;
+
+  last_sweep = current_time;
+
+  std::vector<ComPtr<IBackgroundCopyJob>> jobs;
+  FindBitsJobIf(
+      [](ComPtr<IBackgroundCopyJob> job) {
+        return JobCreationOlderThanDaysPredicate(job, kPurgeStaleJobsAfterDays);
+      },
+      bits_manager_, &jobs);
+
+  for (const auto& job : jobs)
+    CleanupJob(job);
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/background_downloader_win.h b/src/components/update_client/background_downloader_win.h
new file mode 100644
index 0000000..2817ba7
--- /dev/null
+++ b/src/components/update_client/background_downloader_win.h
@@ -0,0 +1,157 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_BACKGROUND_DOWNLOADER_WIN_H_
+#define COMPONENTS_UPDATE_CLIENT_BACKGROUND_DOWNLOADER_WIN_H_
+
+#include <bits.h>
+#include <windows.h>
+#include <wrl/client.h>
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "components/update_client/crx_downloader.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+}  // namespace base
+
+namespace update_client {
+
+// Implements a downloader in terms of the BITS service. The public interface
+// of this class and the CrxDownloader overrides are expected to be called
+// from the main thread. The rest of the class code runs on a sequenced
+// task runner, usually associated with a blocking thread pool. The task runner
+// must initialize COM.
+//
+// This class manages a COM client for Windows BITS. The client uses polling,
+// triggered by an one-shot timer, to get state updates from BITS. Since the
+// timer has thread afinity, the callbacks from the timer are delegated to
+// a sequenced task runner, which handles all client COM interaction with
+// the BITS service.
+class BackgroundDownloader : public CrxDownloader {
+ public:
+  explicit BackgroundDownloader(std::unique_ptr<CrxDownloader> successor);
+  ~BackgroundDownloader() override;
+
+ private:
+  // Overrides for CrxDownloader.
+  void DoStartDownload(const GURL& url) override;
+
+  // Called asynchronously on the |com_task_runner_| at different stages during
+  // the download. |OnDownloading| can be called multiple times.
+  // |EndDownload| switches the execution flow from the |com_task_runner_| to
+  // the main thread. Accessing any data members of this object from the
+  // |com_task_runner_| after calling |EndDownload| is unsafe.
+  void BeginDownload(const GURL& url);
+  void OnDownloading();
+  void EndDownload(HRESULT hr);
+
+  HRESULT BeginDownloadHelper(const GURL& url);
+
+  // Handles the job state transitions to a final state. Returns true always
+  // since the download has reached a final state and no further processing for
+  // this download is needed.
+  bool OnStateTransferred();
+  bool OnStateError();
+  bool OnStateCancelled();
+  bool OnStateAcknowledged();
+
+  // Handles the transition to a transient state where the job is in the
+  // queue but not actively transferring data. Returns true if the download has
+  // been in this state for too long and it will be abandoned, or false, if
+  // further processing for this download is needed.
+  bool OnStateQueued();
+
+  // Handles the job state transition to a transient error state, which may or
+  // may not be considered final, depending on the error. Returns true if
+  // the state is final, or false, if the download is allowed to continue.
+  bool OnStateTransientError();
+
+  // Handles the job state corresponding to transferring data. Returns false
+  // always since this is never a final state.
+  bool OnStateTransferring();
+
+  void StartTimer();
+  void OnTimer();
+
+  // Creates or opens a job for the given url and queues it up. Returns S_OK if
+  // a new job was created or S_FALSE if an existing job for the |url| was found
+  // in the BITS queue.
+  HRESULT QueueBitsJob(const GURL& url,
+                       Microsoft::WRL::ComPtr<IBackgroundCopyJob>* job);
+  HRESULT CreateOrOpenJob(const GURL& url,
+                          Microsoft::WRL::ComPtr<IBackgroundCopyJob>* job);
+  HRESULT InitializeNewJob(
+      const Microsoft::WRL::ComPtr<IBackgroundCopyJob>& job,
+      const GURL& url);
+
+  // Returns true if at the time of the call, it appears that the job
+  // has not been making progress toward completion.
+  bool IsStuck();
+
+  // Makes the downloaded file available to the caller by renaming the
+  // temporary file to its destination and removing it from the BITS queue.
+  HRESULT CompleteJob();
+
+  // Revokes the interface pointers from GIT.
+  HRESULT ClearGit();
+
+  // Updates the BITS interface pointers so that they can be used by the
+  // thread calling the function. Call this function to get valid COM interface
+  // pointers when a thread from the thread pool enters the object.
+  HRESULT UpdateInterfacePointers();
+
+  // Resets the BITS interface pointers. Call this function when a thread
+  // from the thread pool leaves the object to release the interface pointers.
+  void ResetInterfacePointers();
+
+  // Returns the number of jobs in the BITS queue which were created by this
+  // downloader.
+  HRESULT GetBackgroundDownloaderJobCount(size_t* num_jobs);
+
+  // Cleans up incompleted jobs that are too old.
+  void CleanupStaleJobs();
+
+  // Ensures that we are running on the same thread we created the object on.
+  base::ThreadChecker thread_checker_;
+
+  // Executes blocking COM calls to BITS.
+  scoped_refptr<base::SequencedTaskRunner> com_task_runner_;
+
+  // The timer has thread affinity. This member is initialized and destroyed
+  // on the main task runner.
+  std::unique_ptr<base::OneShotTimer> timer_;
+
+  DWORD git_cookie_bits_manager_;
+  DWORD git_cookie_job_;
+
+  // COM interface pointers are valid for the thread that called
+  // |UpdateInterfacePointers| to get pointers to COM proxies, which are valid
+  // for that thread only.
+  Microsoft::WRL::ComPtr<IBackgroundCopyManager> bits_manager_;
+  Microsoft::WRL::ComPtr<IBackgroundCopyJob> job_;
+
+  // Contains the time when the download of the current url has started.
+  base::TimeTicks download_start_time_;
+
+  // Contains the time when the BITS job is last seen making progress.
+  base::TimeTicks job_stuck_begin_time_;
+
+  // Contains the path of the downloaded file if the download was successful.
+  base::FilePath response_;
+
+  DISALLOW_COPY_AND_ASSIGN(BackgroundDownloader);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_BACKGROUND_DOWNLOADER_WIN_H_
diff --git a/src/components/update_client/command_line_config_policy.cc b/src/components/update_client/command_line_config_policy.cc
new file mode 100644
index 0000000..899d8ac
--- /dev/null
+++ b/src/components/update_client/command_line_config_policy.cc
@@ -0,0 +1,44 @@
+// Copyright 2018 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 "components/update_client/command_line_config_policy.h"
+
+#include "build/build_config.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+bool CommandLineConfigPolicy::BackgroundDownloadsEnabled() const {
+#if defined(OS_WIN)
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool CommandLineConfigPolicy::DeltaUpdatesEnabled() const {
+  return true;
+}
+
+bool CommandLineConfigPolicy::FastUpdate() const {
+  return false;
+}
+
+bool CommandLineConfigPolicy::PingsEnabled() const {
+  return true;
+}
+
+bool CommandLineConfigPolicy::TestRequest() const {
+  return false;
+}
+
+GURL CommandLineConfigPolicy::UrlSourceOverride() const {
+  return GURL();
+}
+
+int CommandLineConfigPolicy::InitialDelay() const {
+  return 0;
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/command_line_config_policy.h b/src/components/update_client/command_line_config_policy.h
new file mode 100644
index 0000000..2d201a5
--- /dev/null
+++ b/src/components/update_client/command_line_config_policy.h
@@ -0,0 +1,44 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_COMMAND_LINE_CONFIG_POLICY_H_
+#define COMPONENTS_UPDATE_CLIENT_COMMAND_LINE_CONFIG_POLICY_H_
+
+class GURL;
+
+namespace update_client {
+
+// This class provides additional settings from command line switches to the
+// main configurator.
+class CommandLineConfigPolicy {
+ public:
+  // If true, background downloads are enabled.
+  virtual bool BackgroundDownloadsEnabled() const;
+
+  // If true, differential updates are enabled.
+  virtual bool DeltaUpdatesEnabled() const;
+
+  // If true, speed up the initial update checking.
+  virtual bool FastUpdate() const;
+
+  // If true, pings are enabled. Pings are the requests sent to the update
+  // server that report the success or failure of installs or update attempts.
+  virtual bool PingsEnabled() const;
+
+  // If true, add "testrequest" attribute to update check requests.
+  virtual bool TestRequest() const;
+
+  // The override URL for updates. Can be empty.
+  virtual GURL UrlSourceOverride() const;
+
+  // If non-zero, time interval in seconds until the first component
+  // update check.
+  virtual int InitialDelay() const;
+
+  virtual ~CommandLineConfigPolicy() {}
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_COMMAND_LINE_CONFIG_POLICY_H_
diff --git a/src/components/update_client/component.cc b/src/components/update_client/component.cc
new file mode 100644
index 0000000..73f36cd
--- /dev/null
+++ b/src/components/update_client/component.cc
@@ -0,0 +1,1042 @@
+// 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 "components/update_client/component.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "components/update_client/action_runner.h"
+#include "components/update_client/component_unpacker.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/network.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/protocol_definition.h"
+#include "components/update_client/protocol_serializer.h"
+#include "components/update_client/task_traits.h"
+#include "components/update_client/unzipper.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/update_engine.h"
+#include "components/update_client/utils.h"
+
+// The state machine representing how a CRX component changes during an update.
+//
+//     +------------------------- kNew
+//     |                            |
+//     |                            V
+//     |                        kChecking
+//     |                            |
+//     V                error       V     no           no
+//  kUpdateError <------------- [update?] -> [action?] -> kUpToDate  kUpdated
+//     ^                            |           |            ^        ^
+//     |                        yes |           | yes        |        |
+//     |                            V           |            |        |
+//     |                        kCanUpdate      +--------> kRun       |
+//     |                            |                                 |
+//     |                no          V                                 |
+//     |               +-<- [differential update?]                    |
+//     |               |               |                              |
+//     |               |           yes |                              |
+//     |               | error         V                              |
+//     |               +-<----- kDownloadingDiff            kRun---->-+
+//     |               |               |                     ^        |
+//     |               |               |                 yes |        |
+//     |               | error         V                     |        |
+//     |               +-<----- kUpdatingDiff ---------> [action?] ->-+
+//     |               |                                     ^     no
+//     |    error      V                                     |
+//     +-<-------- kDownloading                              |
+//     |               |                                     |
+//     |               |                                     |
+//     |    error      V                                     |
+//     +-<-------- kUpdating --------------------------------+
+
+namespace update_client {
+
+namespace {
+
+using InstallOnBlockingTaskRunnerCompleteCallback = base::OnceCallback<
+    void(ErrorCategory error_category, int error_code, int extra_code1)>;
+
+void InstallComplete(
+    scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+    InstallOnBlockingTaskRunnerCompleteCallback callback,
+    const base::FilePath& unpack_path,
+    const CrxInstaller::Result& result) {
+  base::PostTaskWithTraits(
+      FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
+      base::BindOnce(
+          [](scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+             InstallOnBlockingTaskRunnerCompleteCallback callback,
+             const base::FilePath& unpack_path,
+             const CrxInstaller::Result& result) {
+
+// For Cobalt, don't delete the unpack_path, which is not a temp directory.
+// Cobalt uses a dedicated installation slot obtained from the Installation
+// Manager.
+#if !defined(OS_STARBOARD)
+            base::DeleteFile(unpack_path, true);
+#endif
+            const ErrorCategory error_category =
+                result.error ? ErrorCategory::kInstall : ErrorCategory::kNone;
+            main_task_runner->PostTask(
+                FROM_HERE, base::BindOnce(std::move(callback), error_category,
+                                          static_cast<int>(result.error),
+                                          result.extended_error));
+          },
+          main_task_runner, std::move(callback), unpack_path, result));
+}
+
+void InstallOnBlockingTaskRunner(
+    scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+    const base::FilePath& unpack_path,
+    const std::string& public_key,
+#if defined(OS_STARBOARD)
+    const int installation_index,
+    const bool is_channel_changed,
+#endif
+    const std::string& fingerprint,
+    scoped_refptr<CrxInstaller> installer,
+    InstallOnBlockingTaskRunnerCompleteCallback callback) {
+  DCHECK(base::DirectoryExists(unpack_path));
+
+#if !defined(OS_STARBOARD)
+  // Acquire the ownership of the |unpack_path|.
+  base::ScopedTempDir unpack_path_owner;
+  ignore_result(unpack_path_owner.Set(unpack_path));
+#endif
+
+  if (static_cast<int>(fingerprint.size()) !=
+      base::WriteFile(
+          unpack_path.Append(FILE_PATH_LITERAL("manifest.fingerprint")),
+          fingerprint.c_str(), base::checked_cast<int>(fingerprint.size()))) {
+    const CrxInstaller::Result result(InstallError::FINGERPRINT_WRITE_FAILED);
+    main_task_runner->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(callback), ErrorCategory::kInstall,
+                       static_cast<int>(result.error), result.extended_error));
+    return;
+  }
+
+#if defined(OS_STARBOARD)
+  InstallError install_error = InstallError::NONE;
+  const CobaltExtensionInstallationManagerApi* installation_api =
+      static_cast<const CobaltExtensionInstallationManagerApi*>(
+          SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
+  if (!installation_api) {
+    SB_LOG(ERROR) << "Failed to get installation manager api.";
+    // TODO: add correct error code.
+    install_error = InstallError::GENERIC_ERROR;
+  } else if (installation_index == IM_EXT_INVALID_INDEX) {
+    SB_LOG(ERROR) << "Installation index is invalid.";
+    // TODO: add correct error code.
+    install_error = InstallError::GENERIC_ERROR;
+  } else {
+    int ret =
+        installation_api->RequestRollForwardToInstallation(installation_index);
+    if (ret == IM_EXT_ERROR) {
+      SB_LOG(ERROR) << "Failed to request roll forward.";
+      // TODO: add correct error code.
+      install_error = InstallError::GENERIC_ERROR;
+    }
+  }
+
+  CrxInstaller::Result result(install_error);
+  InstallComplete(main_task_runner, std::move(callback), unpack_path, result);
+
+  // Restart the app if installation is successful after the web app sets a new
+  // channel.
+  if (install_error == InstallError::NONE && is_channel_changed) {
+    SbSystemRequestStop(0);
+  }
+#else
+  installer->Install(
+      unpack_path, public_key,
+      base::BindOnce(&InstallComplete, main_task_runner, std::move(callback),
+                     unpack_path_owner.Take()));
+#endif
+}
+
+void UnpackCompleteOnBlockingTaskRunner(
+    scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+    const base::FilePath& crx_path,
+#if defined(OS_STARBOARD)
+    const int installation_index,
+    const bool is_channel_changed,
+#endif
+    const std::string& fingerprint,
+    scoped_refptr<CrxInstaller> installer,
+    InstallOnBlockingTaskRunnerCompleteCallback callback,
+    const ComponentUnpacker::Result& result) {
+
+#if defined(OS_STARBOARD)
+  base::DeleteFile(crx_path, false);
+#else
+  update_client::DeleteFileAndEmptyParentDirectory(crx_path);
+#endif
+
+  if (result.error != UnpackerError::kNone) {
+    main_task_runner->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(callback), ErrorCategory::kUnpack,
+                       static_cast<int>(result.error), result.extended_error));
+    return;
+  }
+
+  base::PostTaskWithTraits(
+      FROM_HERE, kTaskTraits,
+      base::BindOnce(&InstallOnBlockingTaskRunner, main_task_runner,
+                     result.unpack_path, result.public_key,
+#if defined(OS_STARBOARD)
+                     installation_index, is_channel_changed,
+#endif
+                     fingerprint, installer, std::move(callback)));
+}
+
+void StartInstallOnBlockingTaskRunner(
+    scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+    const std::vector<uint8_t>& pk_hash,
+    const base::FilePath& crx_path,
+#if defined(OS_STARBOARD)
+    const int installation_index,
+    PersistedData* metadata,
+    const std::string& id,
+    const std::string& version,
+    const bool is_channel_changed,
+#endif
+    const std::string& fingerprint,
+    scoped_refptr<CrxInstaller> installer,
+    std::unique_ptr<Unzipper> unzipper_,
+    scoped_refptr<Patcher> patcher_,
+    crx_file::VerifierFormat crx_format,
+    InstallOnBlockingTaskRunnerCompleteCallback callback) {
+  auto unpacker = base::MakeRefCounted<ComponentUnpacker>(
+      pk_hash, crx_path, installer, std::move(unzipper_), std::move(patcher_),
+      crx_format, metadata, id, version);
+
+  unpacker->Unpack(base::BindOnce(&UnpackCompleteOnBlockingTaskRunner,
+                                  main_task_runner, crx_path,
+#if defined(OS_STARBOARD)
+                                  installation_index, is_channel_changed,
+#endif
+                                  fingerprint, installer, std::move(callback)));
+}
+
+// Returns a string literal corresponding to the value of the downloader |d|.
+const char* DownloaderToString(CrxDownloader::DownloadMetrics::Downloader d) {
+  switch (d) {
+    case CrxDownloader::DownloadMetrics::kUrlFetcher:
+      return "direct";
+    case CrxDownloader::DownloadMetrics::kBits:
+      return "bits";
+    default:
+      return "unknown";
+  }
+}
+
+}  // namespace
+
+Component::Component(const UpdateContext& update_context, const std::string& id)
+    : id_(id),
+      state_(std::make_unique<StateNew>(this)),
+      update_context_(update_context) {}
+
+Component::~Component() {}
+
+scoped_refptr<Configurator> Component::config() const {
+  return update_context_.config;
+}
+
+std::string Component::session_id() const {
+  return update_context_.session_id;
+}
+
+bool Component::is_foreground() const {
+  return update_context_.is_foreground;
+}
+
+void Component::Handle(CallbackHandleComplete callback_handle_complete) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(state_);
+
+  callback_handle_complete_ = std::move(callback_handle_complete);
+
+  state_->Handle(
+      base::BindOnce(&Component::ChangeState, base::Unretained(this)));
+}
+
+void Component::ChangeState(std::unique_ptr<State> next_state) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  previous_state_ = state();
+  if (next_state)
+    state_ = std::move(next_state);
+  else
+    is_handled_ = true;
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, std::move(callback_handle_complete_));
+}
+
+CrxUpdateItem Component::GetCrxUpdateItem() const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  CrxUpdateItem crx_update_item;
+  crx_update_item.state = state_->state();
+  crx_update_item.id = id_;
+  if (crx_component_)
+    crx_update_item.component = *crx_component_;
+  crx_update_item.last_check = last_check_;
+  crx_update_item.next_version = next_version_;
+  crx_update_item.next_fp = next_fp_;
+  crx_update_item.error_category = error_category_;
+  crx_update_item.error_code = error_code_;
+  crx_update_item.extra_code1 = extra_code1_;
+
+  return crx_update_item;
+}
+
+void Component::SetParseResult(const ProtocolParser::Result& result) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  DCHECK_EQ(0, update_check_error_);
+
+  status_ = result.status;
+  action_run_ = result.action_run;
+
+  if (result.manifest.packages.empty())
+    return;
+
+  next_version_ = base::Version(result.manifest.version);
+  const auto& package = result.manifest.packages.front();
+  next_fp_ = package.fingerprint;
+
+  // Resolve the urls by combining the base urls with the package names.
+  for (const auto& crx_url : result.crx_urls) {
+    const GURL url = crx_url.Resolve(package.name);
+    if (url.is_valid())
+      crx_urls_.push_back(url);
+  }
+  for (const auto& crx_diffurl : result.crx_diffurls) {
+    const GURL url = crx_diffurl.Resolve(package.namediff);
+    if (url.is_valid())
+      crx_diffurls_.push_back(url);
+  }
+
+  hash_sha256_ = package.hash_sha256;
+  hashdiff_sha256_ = package.hashdiff_sha256;
+}
+
+void Component::Uninstall(const base::Version& version, int reason) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  DCHECK_EQ(ComponentState::kNew, state());
+
+  crx_component_ = CrxComponent();
+  crx_component_->version = version;
+
+  previous_version_ = version;
+  next_version_ = base::Version("0");
+  extra_code1_ = reason;
+
+  state_ = std::make_unique<StateUninstalled>(this);
+}
+
+void Component::SetUpdateCheckResult(
+    const base::Optional<ProtocolParser::Result>& result,
+    ErrorCategory error_category,
+    int error) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_EQ(ComponentState::kChecking, state());
+
+  error_category_ = error_category;
+  error_code_ = error;
+  if (result)
+    SetParseResult(result.value());
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, std::move(update_check_complete_));
+}
+
+bool Component::CanDoBackgroundDownload() const {
+  // Foreground component updates are always downloaded in foreground.
+  return !is_foreground() &&
+         (crx_component() && crx_component()->allows_background_download) &&
+         update_context_.config->EnabledBackgroundDownloader();
+}
+
+void Component::AppendEvent(base::Value event) {
+  events_.push_back(std::move(event));
+}
+
+void Component::NotifyObservers(UpdateClient::Observer::Events event) const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  update_context_.notify_observers_callback.Run(event, id_);
+}
+
+base::TimeDelta Component::GetUpdateDuration() const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (update_begin_.is_null())
+    return base::TimeDelta();
+
+  const base::TimeDelta update_cost(base::TimeTicks::Now() - update_begin_);
+  DCHECK_GE(update_cost, base::TimeDelta());
+  const base::TimeDelta max_update_delay =
+      base::TimeDelta::FromSeconds(update_context_.config->UpdateDelay());
+  return std::min(update_cost, max_update_delay);
+}
+
+base::Value Component::MakeEventUpdateComplete() const {
+  base::Value event(base::Value::Type::DICTIONARY);
+  event.SetKey("eventtype", base::Value(3));
+  event.SetKey(
+      "eventresult",
+      base::Value(static_cast<int>(state() == ComponentState::kUpdated)));
+  if (error_category() != ErrorCategory::kNone)
+    event.SetKey("errorcat", base::Value(static_cast<int>(error_category())));
+  if (error_code())
+    event.SetKey("errorcode", base::Value(error_code()));
+  if (extra_code1())
+    event.SetKey("extracode1", base::Value(extra_code1()));
+  if (HasDiffUpdate(*this)) {
+    const int diffresult = static_cast<int>(!diff_update_failed());
+    event.SetKey("diffresult", base::Value(diffresult));
+  }
+  if (diff_error_category() != ErrorCategory::kNone) {
+    const int differrorcat = static_cast<int>(diff_error_category());
+    event.SetKey("differrorcat", base::Value(differrorcat));
+  }
+  if (diff_error_code())
+    event.SetKey("differrorcode", base::Value(diff_error_code()));
+  if (diff_extra_code1())
+    event.SetKey("diffextracode1", base::Value(diff_extra_code1()));
+  if (!previous_fp().empty())
+    event.SetKey("previousfp", base::Value(previous_fp()));
+  if (!next_fp().empty())
+    event.SetKey("nextfp", base::Value(next_fp()));
+  DCHECK(previous_version().IsValid());
+  event.SetKey("previousversion", base::Value(previous_version().GetString()));
+  if (next_version().IsValid())
+    event.SetKey("nextversion", base::Value(next_version().GetString()));
+  return event;
+}
+
+base::Value Component::MakeEventDownloadMetrics(
+    const CrxDownloader::DownloadMetrics& dm) const {
+  base::Value event(base::Value::Type::DICTIONARY);
+  event.SetKey("eventtype", base::Value(14));
+  event.SetKey("eventresult", base::Value(static_cast<int>(dm.error == 0)));
+  event.SetKey("downloader", base::Value(DownloaderToString(dm.downloader)));
+  if (dm.error)
+    event.SetKey("errorcode", base::Value(dm.error));
+  event.SetKey("url", base::Value(dm.url.spec()));
+
+  // -1 means that the  byte counts are not known.
+  if (dm.total_bytes != -1 && dm.total_bytes < kProtocolMaxInt)
+    event.SetKey("total", base::Value(static_cast<double>(dm.total_bytes)));
+  if (dm.downloaded_bytes != -1 && dm.total_bytes < kProtocolMaxInt) {
+    event.SetKey("downloaded",
+                 base::Value(static_cast<double>(dm.downloaded_bytes)));
+  }
+  if (dm.download_time_ms && dm.total_bytes < kProtocolMaxInt) {
+    event.SetKey("download_time_ms",
+                 base::Value(static_cast<double>(dm.download_time_ms)));
+  }
+  DCHECK(previous_version().IsValid());
+  event.SetKey("previousversion", base::Value(previous_version().GetString()));
+  if (next_version().IsValid())
+    event.SetKey("nextversion", base::Value(next_version().GetString()));
+  return event;
+}
+
+base::Value Component::MakeEventUninstalled() const {
+  DCHECK(state() == ComponentState::kUninstalled);
+  base::Value event(base::Value::Type::DICTIONARY);
+  event.SetKey("eventtype", base::Value(4));
+  event.SetKey("eventresult", base::Value(1));
+  if (extra_code1())
+    event.SetKey("extracode1", base::Value(extra_code1()));
+  DCHECK(previous_version().IsValid());
+  event.SetKey("previousversion", base::Value(previous_version().GetString()));
+  DCHECK(next_version().IsValid());
+  event.SetKey("nextversion", base::Value(next_version().GetString()));
+  return event;
+}
+
+base::Value Component::MakeEventActionRun(bool succeeded,
+                                          int error_code,
+                                          int extra_code1) const {
+  base::Value event(base::Value::Type::DICTIONARY);
+  event.SetKey("eventtype", base::Value(42));
+  event.SetKey("eventresult", base::Value(static_cast<int>(succeeded)));
+  if (error_code)
+    event.SetKey("errorcode", base::Value(error_code));
+  if (extra_code1)
+    event.SetKey("extracode1", base::Value(extra_code1));
+  return event;
+}
+
+std::vector<base::Value> Component::GetEvents() const {
+  std::vector<base::Value> events;
+  for (const auto& event : events_)
+    events.push_back(event.Clone());
+  return events;
+}
+
+Component::State::State(Component* component, ComponentState state)
+    : state_(state), component_(*component) {}
+
+Component::State::~State() {}
+
+void Component::State::Handle(CallbackNextState callback_next_state) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  callback_next_state_ = std::move(callback_next_state);
+
+  DoHandle();
+}
+
+void Component::State::TransitionState(std::unique_ptr<State> next_state) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(next_state);
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(callback_next_state_), std::move(next_state)));
+}
+
+void Component::State::EndState() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback_next_state_), nullptr));
+}
+
+Component::StateNew::StateNew(Component* component)
+    : State(component, ComponentState::kNew) {}
+
+Component::StateNew::~StateNew() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateNew::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = State::component();
+  if (component.crx_component()) {
+    TransitionState(std::make_unique<StateChecking>(&component));
+  } else {
+    component.error_code_ = static_cast<int>(Error::CRX_NOT_FOUND);
+    component.error_category_ = ErrorCategory::kService;
+    TransitionState(std::make_unique<StateUpdateError>(&component));
+  }
+}
+
+Component::StateChecking::StateChecking(Component* component)
+    : State(component, ComponentState::kChecking) {}
+
+Component::StateChecking::~StateChecking() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+// Unlike how other states are handled, this function does not change the
+// state right away. The state transition happens when the UpdateChecker
+// calls Component::UpdateCheckComplete and |update_check_complete_| is invoked.
+// This is an artifact of how multiple components must be checked for updates
+// together but the state machine defines the transitions for one component
+// at a time.
+void Component::StateChecking::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = State::component();
+  DCHECK(component.crx_component());
+
+  component.last_check_ = base::TimeTicks::Now();
+  component.update_check_complete_ = base::BindOnce(
+      &Component::StateChecking::UpdateCheckComplete, base::Unretained(this));
+
+#if defined(OS_STARBOARD)
+  // Set component.is_channel_changed_ here so that it's synced with the
+  // "updaterchannelchanged" attribute in the update check request sent to
+  // Omaha.
+  component.is_channel_changed_ =
+      component.update_context_.config->IsChannelChanged();
+#endif
+
+  component.NotifyObservers(Events::COMPONENT_CHECKING_FOR_UPDATES);
+}
+
+void Component::StateChecking::UpdateCheckComplete() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  auto& component = State::component();
+  if (!component.error_code_) {
+    if (component.status_ == "ok") {
+      TransitionState(std::make_unique<StateCanUpdate>(&component));
+      return;
+    }
+
+    if (component.status_ == "noupdate") {
+      if (component.action_run_.empty())
+        TransitionState(std::make_unique<StateUpToDate>(&component));
+      else
+        TransitionState(std::make_unique<StateRun>(&component));
+      return;
+    }
+  }
+
+  TransitionState(std::make_unique<StateUpdateError>(&component));
+}
+
+Component::StateUpdateError::StateUpdateError(Component* component)
+    : State(component, ComponentState::kUpdateError) {}
+
+Component::StateUpdateError::~StateUpdateError() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUpdateError::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = State::component();
+
+  DCHECK_NE(ErrorCategory::kNone, component.error_category_);
+  DCHECK_NE(0, component.error_code_);
+
+  // Create an event only when the server response included an update.
+  if (component.IsUpdateAvailable())
+    component.AppendEvent(component.MakeEventUpdateComplete());
+
+  EndState();
+  component.NotifyObservers(Events::COMPONENT_UPDATE_ERROR);
+}
+
+Component::StateCanUpdate::StateCanUpdate(Component* component)
+    : State(component, ComponentState::kCanUpdate) {}
+
+Component::StateCanUpdate::~StateCanUpdate() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateCanUpdate::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = State::component();
+  DCHECK(component.crx_component());
+
+  component.is_update_available_ = true;
+  component.NotifyObservers(Events::COMPONENT_UPDATE_FOUND);
+
+  if (component.crx_component()
+          ->supports_group_policy_enable_component_updates &&
+      !component.update_context_.enabled_component_updates) {
+    component.error_category_ = ErrorCategory::kService;
+    component.error_code_ = static_cast<int>(ServiceError::UPDATE_DISABLED);
+    component.extra_code1_ = 0;
+    TransitionState(std::make_unique<StateUpdateError>(&component));
+    return;
+  }
+
+  // Start computing the cost of the this update from here on.
+  component.update_begin_ = base::TimeTicks::Now();
+
+  if (CanTryDiffUpdate())
+    TransitionState(std::make_unique<StateDownloadingDiff>(&component));
+  else
+    TransitionState(std::make_unique<StateDownloading>(&component));
+}
+
+// Returns true if a differential update is available, it has not failed yet,
+// and the configuration allows this update.
+bool Component::StateCanUpdate::CanTryDiffUpdate() const {
+  const auto& component = Component::State::component();
+  return HasDiffUpdate(component) && !component.diff_error_code_ &&
+         component.update_context_.config->EnabledDeltas();
+}
+
+Component::StateUpToDate::StateUpToDate(Component* component)
+    : State(component, ComponentState::kUpToDate) {}
+
+Component::StateUpToDate::~StateUpToDate() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUpToDate::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = State::component();
+  DCHECK(component.crx_component());
+
+  component.NotifyObservers(Events::COMPONENT_NOT_UPDATED);
+  EndState();
+}
+
+Component::StateDownloadingDiff::StateDownloadingDiff(Component* component)
+    : State(component, ComponentState::kDownloadingDiff) {}
+
+Component::StateDownloadingDiff::~StateDownloadingDiff() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateDownloadingDiff::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  const auto& component = Component::State::component();
+  const auto& update_context = component.update_context_;
+
+  DCHECK(component.crx_component());
+
+  crx_downloader_ = update_context.crx_downloader_factory(
+      component.CanDoBackgroundDownload(),
+      update_context.config->GetNetworkFetcherFactory());
+
+  const auto& id = component.id_;
+  crx_downloader_->set_progress_callback(
+      base::Bind(&Component::StateDownloadingDiff::DownloadProgress,
+                 base::Unretained(this), id));
+  crx_downloader_->StartDownload(
+      component.crx_diffurls_, component.hashdiff_sha256_,
+      base::BindOnce(&Component::StateDownloadingDiff::DownloadComplete,
+                     base::Unretained(this), id));
+
+  component.NotifyObservers(Events::COMPONENT_UPDATE_DOWNLOADING);
+}
+
+// Called when progress is being made downloading a CRX. Can be called multiple
+// times due to how the CRX downloader switches between different downloaders
+// and fallback urls.
+void Component::StateDownloadingDiff::DownloadProgress(const std::string& id) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  component().NotifyObservers(Events::COMPONENT_UPDATE_DOWNLOADING);
+}
+
+void Component::StateDownloadingDiff::DownloadComplete(
+    const std::string& id,
+    const CrxDownloader::Result& download_result) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = Component::State::component();
+  for (const auto& download_metrics : crx_downloader_->download_metrics())
+    component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
+
+  crx_downloader_.reset();
+
+  if (download_result.error) {
+    DCHECK(download_result.response.empty());
+    component.diff_error_category_ = ErrorCategory::kDownload;
+    component.diff_error_code_ = download_result.error;
+
+    TransitionState(std::make_unique<StateDownloading>(&component));
+    return;
+  }
+
+  component.crx_path_ = download_result.response;
+
+#if defined(OS_STARBOARD)
+  component.installation_index_ = download_result.installation_index;
+#endif
+
+  TransitionState(std::make_unique<StateUpdatingDiff>(&component));
+}
+
+Component::StateDownloading::StateDownloading(Component* component)
+    : State(component, ComponentState::kDownloading) {}
+
+Component::StateDownloading::~StateDownloading() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateDownloading::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  const auto& component = Component::State::component();
+  const auto& update_context = component.update_context_;
+
+  DCHECK(component.crx_component());
+
+  crx_downloader_ = update_context.crx_downloader_factory(
+      component.CanDoBackgroundDownload(),
+      update_context.config->GetNetworkFetcherFactory());
+
+  const auto& id = component.id_;
+  crx_downloader_->set_progress_callback(
+      base::Bind(&Component::StateDownloading::DownloadProgress,
+                 base::Unretained(this), id));
+  crx_downloader_->StartDownload(
+      component.crx_urls_, component.hash_sha256_,
+      base::BindOnce(&Component::StateDownloading::DownloadComplete,
+                     base::Unretained(this), id));
+
+  component.NotifyObservers(Events::COMPONENT_UPDATE_DOWNLOADING);
+}
+
+// Called when progress is being made downloading a CRX. Can be called multiple
+// times due to how the CRX downloader switches between different downloaders
+// and fallback urls.
+void Component::StateDownloading::DownloadProgress(const std::string& id) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  component().NotifyObservers(Events::COMPONENT_UPDATE_DOWNLOADING);
+}
+
+void Component::StateDownloading::DownloadComplete(
+    const std::string& id,
+    const CrxDownloader::Result& download_result) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = Component::State::component();
+
+  for (const auto& download_metrics : crx_downloader_->download_metrics())
+    component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
+
+  crx_downloader_.reset();
+
+  if (download_result.error) {
+    DCHECK(download_result.response.empty());
+    component.error_category_ = ErrorCategory::kDownload;
+    component.error_code_ = download_result.error;
+
+    TransitionState(std::make_unique<StateUpdateError>(&component));
+    return;
+  }
+
+  component.crx_path_ = download_result.response;
+
+#if defined(OS_STARBOARD)
+  component.installation_index_ = download_result.installation_index;
+#endif
+
+  TransitionState(std::make_unique<StateUpdating>(&component));
+}
+
+Component::StateUpdatingDiff::StateUpdatingDiff(Component* component)
+    : State(component, ComponentState::kUpdatingDiff) {}
+
+Component::StateUpdatingDiff::~StateUpdatingDiff() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUpdatingDiff::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  const auto& component = Component::State::component();
+  const auto& update_context = component.update_context_;
+
+  DCHECK(component.crx_component());
+
+  component.NotifyObservers(Events::COMPONENT_UPDATE_READY);
+
+  base::CreateSequencedTaskRunnerWithTraits(kTaskTraits)
+      ->PostTask(
+          FROM_HERE,
+          base::BindOnce(
+              &update_client::StartInstallOnBlockingTaskRunner,
+              base::ThreadTaskRunnerHandle::Get(),
+              component.crx_component()->pk_hash, component.crx_path_,
+#if defined(OS_STARBOARD)
+              component.installation_index_,
+              update_context.update_checker->GetPersistedData(), component.id_,
+              component.next_version_.GetString(),
+              component.is_channel_changed_,
+#endif
+              component.next_fp_, component.crx_component()->installer,
+              update_context.config->GetUnzipperFactory()->Create(),
+              update_context.config->GetPatcherFactory()->Create(),
+              component.crx_component()->crx_format_requirement,
+              base::BindOnce(&Component::StateUpdatingDiff::InstallComplete,
+                             base::Unretained(this))));
+}
+
+void Component::StateUpdatingDiff::InstallComplete(ErrorCategory error_category,
+                                                   int error_code,
+                                                   int extra_code1) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = Component::State::component();
+
+  component.diff_error_category_ = error_category;
+  component.diff_error_code_ = error_code;
+  component.diff_extra_code1_ = extra_code1;
+
+  if (component.diff_error_code_ != 0) {
+    TransitionState(std::make_unique<StateDownloading>(&component));
+    return;
+  }
+
+  DCHECK_EQ(ErrorCategory::kNone, component.diff_error_category_);
+  DCHECK_EQ(0, component.diff_error_code_);
+  DCHECK_EQ(0, component.diff_extra_code1_);
+
+  DCHECK_EQ(ErrorCategory::kNone, component.error_category_);
+  DCHECK_EQ(0, component.error_code_);
+  DCHECK_EQ(0, component.extra_code1_);
+
+  if (component.action_run_.empty())
+    TransitionState(std::make_unique<StateUpdated>(&component));
+  else
+    TransitionState(std::make_unique<StateRun>(&component));
+}
+
+Component::StateUpdating::StateUpdating(Component* component)
+    : State(component, ComponentState::kUpdating) {}
+
+Component::StateUpdating::~StateUpdating() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUpdating::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  const auto& component = Component::State::component();
+  const auto& update_context = component.update_context_;
+
+  DCHECK(component.crx_component());
+
+  component.NotifyObservers(Events::COMPONENT_UPDATE_READY);
+
+  base::CreateSequencedTaskRunnerWithTraits(kTaskTraits)
+      ->PostTask(FROM_HERE,
+                 base::BindOnce(
+                     &update_client::StartInstallOnBlockingTaskRunner,
+                     base::ThreadTaskRunnerHandle::Get(),
+                     component.crx_component()->pk_hash, component.crx_path_,
+#if defined(OS_STARBOARD)
+                     component.installation_index_,
+                     update_context.update_checker->GetPersistedData(),
+                     component.id_, component.next_version_.GetString(),
+                     component.is_channel_changed_,
+#endif
+                     component.next_fp_, component.crx_component()->installer,
+                     update_context.config->GetUnzipperFactory()->Create(),
+                     update_context.config->GetPatcherFactory()->Create(),
+                     component.crx_component()->crx_format_requirement,
+                     base::BindOnce(&Component::StateUpdating::InstallComplete,
+                                    base::Unretained(this))));
+}
+
+void Component::StateUpdating::InstallComplete(ErrorCategory error_category,
+                                               int error_code,
+                                               int extra_code1) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = Component::State::component();
+
+  component.error_category_ = error_category;
+  component.error_code_ = error_code;
+  component.extra_code1_ = extra_code1;
+
+  if (component.error_code_ != 0) {
+    TransitionState(std::make_unique<StateUpdateError>(&component));
+    return;
+  }
+
+  DCHECK_EQ(ErrorCategory::kNone, component.error_category_);
+  DCHECK_EQ(0, component.error_code_);
+  DCHECK_EQ(0, component.extra_code1_);
+
+  if (component.action_run_.empty())
+    TransitionState(std::make_unique<StateUpdated>(&component));
+  else
+    TransitionState(std::make_unique<StateRun>(&component));
+}
+
+Component::StateUpdated::StateUpdated(Component* component)
+    : State(component, ComponentState::kUpdated) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+Component::StateUpdated::~StateUpdated() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUpdated::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = State::component();
+  DCHECK(component.crx_component());
+
+  component.crx_component_->version = component.next_version_;
+  component.crx_component_->fingerprint = component.next_fp_;
+
+  component.AppendEvent(component.MakeEventUpdateComplete());
+
+  component.NotifyObservers(Events::COMPONENT_UPDATED);
+  EndState();
+}
+
+Component::StateUninstalled::StateUninstalled(Component* component)
+    : State(component, ComponentState::kUninstalled) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+Component::StateUninstalled::~StateUninstalled() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUninstalled::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = State::component();
+  DCHECK(component.crx_component());
+
+  component.AppendEvent(component.MakeEventUninstalled());
+
+  EndState();
+}
+
+Component::StateRun::StateRun(Component* component)
+    : State(component, ComponentState::kRun) {}
+
+Component::StateRun::~StateRun() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateRun::DoHandle() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  const auto& component = State::component();
+  DCHECK(component.crx_component());
+
+  action_runner_ = std::make_unique<ActionRunner>(component);
+  action_runner_->Run(
+      base::BindOnce(&StateRun::ActionRunComplete, base::Unretained(this)));
+}
+
+void Component::StateRun::ActionRunComplete(bool succeeded,
+                                            int error_code,
+                                            int extra_code1) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto& component = State::component();
+
+  component.AppendEvent(
+      component.MakeEventActionRun(succeeded, error_code, extra_code1));
+  switch (component.previous_state_) {
+    case ComponentState::kChecking:
+      TransitionState(std::make_unique<StateUpToDate>(&component));
+      return;
+    case ComponentState::kUpdating:
+    case ComponentState::kUpdatingDiff:
+      TransitionState(std::make_unique<StateUpdated>(&component));
+      return;
+    default:
+      break;
+  }
+  NOTREACHED();
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/component.h b/src/components/update_client/component.h
new file mode 100644
index 0000000..b674852
--- /dev/null
+++ b/src/components/update_client/component.h
@@ -0,0 +1,471 @@
+// 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 COMPONENTS_UPDATE_CLIENT_COMPONENT_H_
+#define COMPONENTS_UPDATE_CLIENT_COMPONENT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "base/version.h"
+#include "components/update_client/crx_downloader.h"
+#include "components/update_client/protocol_parser.h"
+#include "components/update_client/update_client.h"
+#include "url/gurl.h"
+
+#if defined(OS_STARBOARD)
+#include "cobalt/extension/installation_manager.h"
+#endif
+
+namespace base {
+class Value;
+}  // namespace base
+
+namespace update_client {
+
+class ActionRunner;
+class Configurator;
+struct CrxUpdateItem;
+struct UpdateContext;
+
+// Describes a CRX component managed by the UpdateEngine. Each |Component| is
+// associated with an UpdateContext.
+class Component {
+ public:
+  using Events = UpdateClient::Observer::Events;
+
+  using CallbackHandleComplete = base::OnceCallback<void()>;
+
+  Component(const UpdateContext& update_context, const std::string& id);
+  ~Component();
+
+  // Handles the current state of the component and makes it transition
+  // to the next component state before |callback_handle_complete_| is invoked.
+  void Handle(CallbackHandleComplete callback_handle_complete);
+
+  CrxUpdateItem GetCrxUpdateItem() const;
+
+  // Sets the uninstall state for this component.
+  void Uninstall(const base::Version& cur_version, int reason);
+
+  // Called by the UpdateEngine when an update check for this component is done.
+  void SetUpdateCheckResult(
+      const base::Optional<ProtocolParser::Result>& result,
+      ErrorCategory error_category,
+      int error);
+
+  // Returns true if the component has reached a final state and no further
+  // handling and state transitions are possible.
+  bool IsHandled() const { return is_handled_; }
+
+  // Returns true if an update is available for this component, meaning that
+  // the update server has return a response containing an update.
+  bool IsUpdateAvailable() const { return is_update_available_; }
+
+  base::TimeDelta GetUpdateDuration() const;
+
+  ComponentState state() const { return state_->state(); }
+
+  std::string id() const { return id_; }
+
+  const base::Optional<CrxComponent>& crx_component() const {
+    return crx_component_;
+  }
+  void set_crx_component(const CrxComponent& crx_component) {
+    crx_component_ = crx_component;
+  }
+
+  const base::Version& previous_version() const { return previous_version_; }
+  void set_previous_version(const base::Version& previous_version) {
+    previous_version_ = previous_version;
+  }
+
+  const base::Version& next_version() const { return next_version_; }
+
+  std::string previous_fp() const { return previous_fp_; }
+  void set_previous_fp(const std::string& previous_fp) {
+    previous_fp_ = previous_fp;
+  }
+
+  std::string next_fp() const { return next_fp_; }
+  void set_next_fp(const std::string& next_fp) { next_fp_ = next_fp; }
+
+  bool is_foreground() const;
+
+  const std::vector<GURL>& crx_diffurls() const { return crx_diffurls_; }
+
+  bool diff_update_failed() const { return !!diff_error_code_; }
+
+  ErrorCategory error_category() const { return error_category_; }
+  int error_code() const { return error_code_; }
+  int extra_code1() const { return extra_code1_; }
+  ErrorCategory diff_error_category() const { return diff_error_category_; }
+  int diff_error_code() const { return diff_error_code_; }
+  int diff_extra_code1() const { return diff_extra_code1_; }
+
+  std::string action_run() const { return action_run_; }
+
+  scoped_refptr<Configurator> config() const;
+
+  std::string session_id() const;
+
+  const std::vector<base::Value>& events() const { return events_; }
+
+  // Returns a clone of the component events.
+  std::vector<base::Value> GetEvents() const;
+
+ private:
+  friend class MockPingManagerImpl;
+  friend class UpdateCheckerTest;
+
+  FRIEND_TEST_ALL_PREFIXES(PingManagerTest, SendPing);
+  FRIEND_TEST_ALL_PREFIXES(PingManagerTest, RequiresEncryption);
+  FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, NoUpdateActionRun);
+  FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, UpdateCheckCupError);
+  FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, UpdateCheckError);
+  FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, UpdateCheckInvalidAp);
+  FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest,
+                           UpdateCheckRequiresEncryptionError);
+  FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, UpdateCheckSuccess);
+  FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, UpdateCheckUpdateDisabled);
+
+  // Describes an abstraction for implementing the behavior of a component and
+  // the transition from one state to another.
+  class State {
+   public:
+    using CallbackNextState =
+        base::OnceCallback<void(std::unique_ptr<State> next_state)>;
+
+    State(Component* component, ComponentState state);
+    virtual ~State();
+
+    // Handles the current state and initiates a transition to a new state.
+    // The transition to the new state is non-blocking and it is completed
+    // by the outer component, after the current state is fully handled.
+    void Handle(CallbackNextState callback);
+
+    ComponentState state() const { return state_; }
+
+   protected:
+    // Initiates the transition to the new state.
+    void TransitionState(std::unique_ptr<State> new_state);
+
+    // Makes the current state a final state where no other state transition
+    // can further occur.
+    void EndState();
+
+    Component& component() { return component_; }
+    const Component& component() const { return component_; }
+
+    base::ThreadChecker thread_checker_;
+
+    const ComponentState state_;
+
+   private:
+    virtual void DoHandle() = 0;
+
+    Component& component_;
+    CallbackNextState callback_next_state_;
+  };
+
+  class StateNew : public State {
+   public:
+    explicit StateNew(Component* component);
+    ~StateNew() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+
+    DISALLOW_COPY_AND_ASSIGN(StateNew);
+  };
+
+  class StateChecking : public State {
+   public:
+    explicit StateChecking(Component* component);
+    ~StateChecking() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+
+    void UpdateCheckComplete();
+
+    DISALLOW_COPY_AND_ASSIGN(StateChecking);
+  };
+
+  class StateUpdateError : public State {
+   public:
+    explicit StateUpdateError(Component* component);
+    ~StateUpdateError() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+
+    DISALLOW_COPY_AND_ASSIGN(StateUpdateError);
+  };
+
+  class StateCanUpdate : public State {
+   public:
+    explicit StateCanUpdate(Component* component);
+    ~StateCanUpdate() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+    bool CanTryDiffUpdate() const;
+
+    DISALLOW_COPY_AND_ASSIGN(StateCanUpdate);
+  };
+
+  class StateUpToDate : public State {
+   public:
+    explicit StateUpToDate(Component* component);
+    ~StateUpToDate() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+
+    DISALLOW_COPY_AND_ASSIGN(StateUpToDate);
+  };
+
+  class StateDownloadingDiff : public State {
+   public:
+    explicit StateDownloadingDiff(Component* component);
+    ~StateDownloadingDiff() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+
+    // Called when progress is being made downloading a CRX. Can be called
+    // multiple times due to how the CRX downloader switches between
+    // different downloaders and fallback urls.
+    void DownloadProgress(const std::string& id);
+
+    void DownloadComplete(const std::string& id,
+                          const CrxDownloader::Result& download_result);
+
+    // Downloads updates for one CRX id only.
+    std::unique_ptr<CrxDownloader> crx_downloader_;
+
+    DISALLOW_COPY_AND_ASSIGN(StateDownloadingDiff);
+  };
+
+  class StateDownloading : public State {
+   public:
+    explicit StateDownloading(Component* component);
+    ~StateDownloading() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+
+    // Called when progress is being made downloading a CRX. Can be called
+    // multiple times due to how the CRX downloader switches between
+    // different downloaders and fallback urls.
+    void DownloadProgress(const std::string& id);
+
+    void DownloadComplete(const std::string& id,
+                          const CrxDownloader::Result& download_result);
+
+    // Downloads updates for one CRX id only.
+    std::unique_ptr<CrxDownloader> crx_downloader_;
+
+    DISALLOW_COPY_AND_ASSIGN(StateDownloading);
+  };
+
+  class StateUpdatingDiff : public State {
+   public:
+    explicit StateUpdatingDiff(Component* component);
+    ~StateUpdatingDiff() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+
+    void InstallComplete(ErrorCategory error_category,
+                         int error_code,
+                         int extra_code1);
+
+    DISALLOW_COPY_AND_ASSIGN(StateUpdatingDiff);
+  };
+
+  class StateUpdating : public State {
+   public:
+    explicit StateUpdating(Component* component);
+    ~StateUpdating() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+
+    void InstallComplete(ErrorCategory error_category,
+                         int error_code,
+                         int extra_code1);
+
+    DISALLOW_COPY_AND_ASSIGN(StateUpdating);
+  };
+
+  class StateUpdated : public State {
+   public:
+    explicit StateUpdated(Component* component);
+    ~StateUpdated() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+
+    DISALLOW_COPY_AND_ASSIGN(StateUpdated);
+  };
+
+  class StateUninstalled : public State {
+   public:
+    explicit StateUninstalled(Component* component);
+    ~StateUninstalled() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+
+    DISALLOW_COPY_AND_ASSIGN(StateUninstalled);
+  };
+
+  class StateRun : public State {
+   public:
+    explicit StateRun(Component* component);
+    ~StateRun() override;
+
+   private:
+    // State overrides.
+    void DoHandle() override;
+
+    void ActionRunComplete(bool succeeded, int error_code, int extra_code1);
+
+    // Runs the action referred by the |action_run_| member of the Component
+    // class.
+    std::unique_ptr<ActionRunner> action_runner_;
+
+    DISALLOW_COPY_AND_ASSIGN(StateRun);
+  };
+
+  // Returns true is the update payload for this component can be downloaded
+  // by a downloader which can do bandwidth throttling on the client side.
+  bool CanDoBackgroundDownload() const;
+
+  void AppendEvent(base::Value event);
+
+  // Changes the component state and notifies the caller of the |Handle|
+  // function that the handling of this component state is complete.
+  void ChangeState(std::unique_ptr<State> next_state);
+
+  // Notifies registered observers about changes in the state of the component.
+  void NotifyObservers(Events event) const;
+
+  void SetParseResult(const ProtocolParser::Result& result);
+
+  // These functions return a specific event. Each data member of the event is
+  // represented as a key-value pair in a dictionary value.
+  base::Value MakeEventUpdateComplete() const;
+  base::Value MakeEventDownloadMetrics(
+      const CrxDownloader::DownloadMetrics& download_metrics) const;
+  base::Value MakeEventUninstalled() const;
+  base::Value MakeEventActionRun(bool succeeded,
+                                 int error_code,
+                                 int extra_code1) const;
+
+  base::ThreadChecker thread_checker_;
+
+  const std::string id_;
+  base::Optional<CrxComponent> crx_component_;
+
+  // The status of the updatecheck response.
+  std::string status_;
+
+  // Time when an update check for this CRX has happened.
+  base::TimeTicks last_check_;
+
+  // Time when the update of this CRX has begun.
+  base::TimeTicks update_begin_;
+
+  // A component can be made available for download from several urls.
+  std::vector<GURL> crx_urls_;
+  std::vector<GURL> crx_diffurls_;
+
+  // The cryptographic hash values for the component payload.
+  std::string hash_sha256_;
+  std::string hashdiff_sha256_;
+
+  // The from/to version and fingerprint values.
+  base::Version previous_version_;
+  base::Version next_version_;
+  std::string previous_fp_;
+  std::string next_fp_;
+
+  // Contains the file name of the payload to run. This member is set by
+  // the update response parser, when the update response includes a run action.
+  std::string action_run_;
+
+  // True if the update check response for this component includes an update.
+  bool is_update_available_ = false;
+
+  // The error reported by the update checker.
+  int update_check_error_ = 0;
+
+  base::FilePath crx_path_;
+
+#if defined(OS_STARBOARD)
+  int installation_index_ = IM_EXT_INVALID_INDEX;
+  bool is_channel_changed_ = false;
+#endif
+
+  // The error information for full and differential updates.
+  // The |error_category| contains a hint about which module in the component
+  // updater generated the error. The |error_code| constains the error and
+  // the |extra_code1| usually contains a system error, but it can contain
+  // any extended information that is relevant to either the category or the
+  // error itself.
+  ErrorCategory error_category_ = ErrorCategory::kNone;
+  int error_code_ = 0;
+  int extra_code1_ = 0;
+  ErrorCategory diff_error_category_ = ErrorCategory::kNone;
+  int diff_error_code_ = 0;
+  int diff_extra_code1_ = 0;
+
+  // Contains the events which are therefore serialized in the requests.
+  std::vector<base::Value> events_;
+
+  CallbackHandleComplete callback_handle_complete_;
+  std::unique_ptr<State> state_;
+  const UpdateContext& update_context_;
+
+  base::OnceClosure update_check_complete_;
+
+  ComponentState previous_state_ = ComponentState::kLastStatus;
+
+  // True if this component has reached a final state because all its states
+  // have been handled.
+  bool is_handled_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(Component);
+};
+
+using IdToComponentPtrMap = std::map<std::string, std::unique_ptr<Component>>;
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_COMPONENT_H_
diff --git a/src/components/update_client/component_patcher.cc b/src/components/update_client/component_patcher.cc
new file mode 100644
index 0000000..9a8feaa
--- /dev/null
+++ b/src/components/update_client/component_patcher.cc
@@ -0,0 +1,117 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/component_patcher.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/location.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/values.h"
+#include "components/update_client/component_patcher_operation.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+
+namespace update_client {
+
+namespace {
+
+// Deserialize the commands file (present in delta update packages). The top
+// level must be a list.
+base::ListValue* ReadCommands(const base::FilePath& unpack_path) {
+  const base::FilePath commands =
+      unpack_path.Append(FILE_PATH_LITERAL("commands.json"));
+  if (!base::PathExists(commands))
+    return nullptr;
+
+  JSONFileValueDeserializer deserializer(commands);
+  std::unique_ptr<base::Value> root =
+      deserializer.Deserialize(nullptr, nullptr);
+
+  return (root.get() && root->is_list())
+             ? static_cast<base::ListValue*>(root.release())
+             : nullptr;
+}
+
+}  // namespace
+
+ComponentPatcher::ComponentPatcher(const base::FilePath& input_dir,
+                                   const base::FilePath& unpack_dir,
+                                   scoped_refptr<CrxInstaller> installer,
+                                   scoped_refptr<Patcher> patcher)
+    : input_dir_(input_dir),
+      unpack_dir_(unpack_dir),
+      installer_(installer),
+      patcher_(patcher) {}
+
+ComponentPatcher::~ComponentPatcher() {}
+
+void ComponentPatcher::Start(Callback callback) {
+  callback_ = std::move(callback);
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&ComponentPatcher::StartPatching,
+                                scoped_refptr<ComponentPatcher>(this)));
+}
+
+void ComponentPatcher::StartPatching() {
+  commands_.reset(ReadCommands(input_dir_));
+  if (!commands_) {
+    DonePatching(UnpackerError::kDeltaBadCommands, 0);
+  } else {
+    next_command_ = commands_->begin();
+    PatchNextFile();
+  }
+}
+
+void ComponentPatcher::PatchNextFile() {
+  if (next_command_ == commands_->end()) {
+    DonePatching(UnpackerError::kNone, 0);
+    return;
+  }
+  const base::DictionaryValue* command_args;
+  if (!next_command_->GetAsDictionary(&command_args)) {
+    DonePatching(UnpackerError::kDeltaBadCommands, 0);
+    return;
+  }
+
+  std::string operation;
+  if (command_args->GetString(kOp, &operation)) {
+    current_operation_ = CreateDeltaUpdateOp(operation, patcher_);
+  }
+
+  if (!current_operation_) {
+    DonePatching(UnpackerError::kDeltaUnsupportedCommand, 0);
+    return;
+  }
+  current_operation_->Run(
+      command_args, input_dir_, unpack_dir_, installer_,
+      base::BindOnce(&ComponentPatcher::DonePatchingFile,
+                     scoped_refptr<ComponentPatcher>(this)));
+}
+
+void ComponentPatcher::DonePatchingFile(UnpackerError error,
+                                        int extended_error) {
+  if (error != UnpackerError::kNone) {
+    DonePatching(error, extended_error);
+  } else {
+    ++next_command_;
+    PatchNextFile();
+  }
+}
+
+void ComponentPatcher::DonePatching(UnpackerError error, int extended_error) {
+  current_operation_ = nullptr;
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback_), error, extended_error));
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/component_patcher.h b/src/components/update_client/component_patcher.h
new file mode 100644
index 0000000..e31a812
--- /dev/null
+++ b/src/components/update_client/component_patcher.h
@@ -0,0 +1,103 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Component updates can be either differential updates or full updates.
+// Full updates come in CRX format; differential updates come in CRX-style
+// archives, but have a different magic number. They contain "commands.json", a
+// list of commands for the patcher to follow. The patcher uses these commands,
+// the other files in the archive, and the files from the existing installation
+// of the component to create the contents of a full update, which is then
+// installed normally.
+// Component updates are specified by the 'codebasediff' attribute of an
+// updatecheck response:
+//   <updatecheck codebase="http://example.com/extension_1.2.3.4.crx"
+//                hash="12345" size="9854" status="ok" version="1.2.3.4"
+//                prodversionmin="2.0.143.0"
+//                codebasediff="http://example.com/diff_1.2.3.4.crx"
+//                hashdiff="123" sizediff="101"
+//                fp="1.123"/>
+// The component updater will attempt a differential update if it is available
+// and allowed to, and fall back to a full update if it fails.
+//
+// After installation (diff or full), the component updater records "fp", the
+// fingerprint of the installed files, to later identify the existing files to
+// the server so that a proper differential update can be provided next cycle.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_H_
+#define COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "components/update_client/component_unpacker.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace update_client {
+
+class CrxInstaller;
+class DeltaUpdateOp;
+enum class UnpackerError;
+
+// The type of a patch file.
+enum PatchType {
+  kPatchTypeUnknown,
+  kPatchTypeCourgette,
+  kPatchTypeBsdiff,
+};
+
+// Encapsulates a task for applying a differential update to a component.
+class ComponentPatcher : public base::RefCountedThreadSafe<ComponentPatcher> {
+ public:
+  using Callback = base::OnceCallback<void(UnpackerError, int)>;
+
+  // Takes an unpacked differential CRX (|input_dir|) and a component installer,
+  // and sets up the class to create a new (non-differential) unpacked CRX.
+  // If |in_process| is true, patching will be done completely within the
+  // existing process. Otherwise, some steps of patching may be done
+  // out-of-process.
+  ComponentPatcher(const base::FilePath& input_dir,
+                   const base::FilePath& unpack_dir,
+                   scoped_refptr<CrxInstaller> installer,
+                   scoped_refptr<Patcher> patcher);
+
+  // Starts patching files. This member function returns immediately, after
+  // posting a task to do the patching. When patching has been completed,
+  // |callback| will be called with the error codes if any error codes were
+  // encountered.
+  void Start(Callback callback);
+
+ private:
+  friend class base::RefCountedThreadSafe<ComponentPatcher>;
+
+  virtual ~ComponentPatcher();
+
+  void StartPatching();
+
+  void PatchNextFile();
+
+  void DonePatchingFile(UnpackerError error, int extended_error);
+
+  void DonePatching(UnpackerError error, int extended_error);
+
+  const base::FilePath input_dir_;
+  const base::FilePath unpack_dir_;
+  scoped_refptr<CrxInstaller> installer_;
+  scoped_refptr<Patcher> patcher_;
+  Callback callback_;
+  std::unique_ptr<base::ListValue> commands_;
+  base::ListValue::const_iterator next_command_;
+  scoped_refptr<DeltaUpdateOp> current_operation_;
+
+  DISALLOW_COPY_AND_ASSIGN(ComponentPatcher);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_H_
diff --git a/src/components/update_client/component_patcher_operation.cc b/src/components/update_client/component_patcher_operation.cc
new file mode 100644
index 0000000..e07a216
--- /dev/null
+++ b/src/components/update_client/component_patcher_operation.cc
@@ -0,0 +1,223 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/component_patcher_operation.h"
+
+#include <stdint.h>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/location.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "components/courgette/courgette.h"
+#include "components/courgette/third_party/bsdiff/bsdiff.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+
+namespace update_client {
+
+namespace {
+
+const char kOutput[] = "output";
+const char kSha256[] = "sha256";
+
+// The integer offset disambiguates between overlapping error ranges.
+const int kCourgetteErrorOffset = 300;
+const int kBsdiffErrorOffset = 600;
+
+}  // namespace
+
+const char kOp[] = "op";
+const char kBsdiff[] = "bsdiff";
+const char kCourgette[] = "courgette";
+const char kInput[] = "input";
+const char kPatch[] = "patch";
+
+DeltaUpdateOp* CreateDeltaUpdateOp(const std::string& operation,
+                                   scoped_refptr<Patcher> patcher) {
+  if (operation == "copy") {
+    return new DeltaUpdateOpCopy();
+  } else if (operation == "create") {
+    return new DeltaUpdateOpCreate();
+  } else if (operation == "bsdiff" || operation == "courgette") {
+    return new DeltaUpdateOpPatch(operation, patcher);
+  }
+  return nullptr;
+}
+
+DeltaUpdateOp::DeltaUpdateOp() {}
+
+DeltaUpdateOp::~DeltaUpdateOp() {}
+
+void DeltaUpdateOp::Run(const base::DictionaryValue* command_args,
+                        const base::FilePath& input_dir,
+                        const base::FilePath& unpack_dir,
+                        scoped_refptr<CrxInstaller> installer,
+                        ComponentPatcher::Callback callback) {
+  callback_ = std::move(callback);
+  std::string output_rel_path;
+  if (!command_args->GetString(kOutput, &output_rel_path) ||
+      !command_args->GetString(kSha256, &output_sha256_)) {
+    DoneRunning(UnpackerError::kDeltaBadCommands, 0);
+    return;
+  }
+
+  output_abs_path_ =
+      unpack_dir.Append(base::FilePath::FromUTF8Unsafe(output_rel_path));
+  UnpackerError parse_result =
+      DoParseArguments(command_args, input_dir, installer);
+  if (parse_result != UnpackerError::kNone) {
+    DoneRunning(parse_result, 0);
+    return;
+  }
+
+  const base::FilePath parent = output_abs_path_.DirName();
+  if (!base::DirectoryExists(parent)) {
+    if (!base::CreateDirectory(parent)) {
+      DoneRunning(UnpackerError::kIoError, 0);
+      return;
+    }
+  }
+
+  DoRun(base::BindOnce(&DeltaUpdateOp::DoneRunning,
+                       scoped_refptr<DeltaUpdateOp>(this)));
+}
+
+void DeltaUpdateOp::DoneRunning(UnpackerError error, int extended_error) {
+  if (error == UnpackerError::kNone)
+    error = CheckHash();
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback_), error, extended_error));
+}
+
+// Uses the hash as a checksum to confirm that the file now residing in the
+// output directory probably has the contents it should.
+UnpackerError DeltaUpdateOp::CheckHash() {
+  return VerifyFileHash256(output_abs_path_, output_sha256_)
+             ? UnpackerError::kNone
+             : UnpackerError::kDeltaVerificationFailure;
+}
+
+DeltaUpdateOpCopy::DeltaUpdateOpCopy() {}
+
+DeltaUpdateOpCopy::~DeltaUpdateOpCopy() {}
+
+UnpackerError DeltaUpdateOpCopy::DoParseArguments(
+    const base::DictionaryValue* command_args,
+    const base::FilePath& input_dir,
+    scoped_refptr<CrxInstaller> installer) {
+  std::string input_rel_path;
+  if (!command_args->GetString(kInput, &input_rel_path))
+    return UnpackerError::kDeltaBadCommands;
+
+  if (!installer->GetInstalledFile(input_rel_path, &input_abs_path_))
+    return UnpackerError::kDeltaMissingExistingFile;
+
+  return UnpackerError::kNone;
+}
+
+void DeltaUpdateOpCopy::DoRun(ComponentPatcher::Callback callback) {
+  if (!base::CopyFile(input_abs_path_, output_abs_path_))
+    std::move(callback).Run(UnpackerError::kDeltaOperationFailure, 0);
+  else
+    std::move(callback).Run(UnpackerError::kNone, 0);
+}
+
+DeltaUpdateOpCreate::DeltaUpdateOpCreate() {}
+
+DeltaUpdateOpCreate::~DeltaUpdateOpCreate() {}
+
+UnpackerError DeltaUpdateOpCreate::DoParseArguments(
+    const base::DictionaryValue* command_args,
+    const base::FilePath& input_dir,
+    scoped_refptr<CrxInstaller> installer) {
+  std::string patch_rel_path;
+  if (!command_args->GetString(kPatch, &patch_rel_path))
+    return UnpackerError::kDeltaBadCommands;
+
+  patch_abs_path_ =
+      input_dir.Append(base::FilePath::FromUTF8Unsafe(patch_rel_path));
+
+  return UnpackerError::kNone;
+}
+
+void DeltaUpdateOpCreate::DoRun(ComponentPatcher::Callback callback) {
+#if !defined(OS_STARBOARD)
+  if (!base::Move(patch_abs_path_, output_abs_path_))
+    std::move(callback).Run(UnpackerError::kDeltaOperationFailure, 0);
+  else
+    std::move(callback).Run(UnpackerError::kNone, 0);
+#else
+  std::move(callback).Run(UnpackerError::kDeltaOperationFailure, 0);
+#endif
+}
+
+DeltaUpdateOpPatch::DeltaUpdateOpPatch(const std::string& operation,
+                                       scoped_refptr<Patcher> patcher)
+    : operation_(operation), patcher_(patcher) {
+  DCHECK(operation == kBsdiff || operation == kCourgette);
+}
+
+DeltaUpdateOpPatch::~DeltaUpdateOpPatch() {}
+
+UnpackerError DeltaUpdateOpPatch::DoParseArguments(
+    const base::DictionaryValue* command_args,
+    const base::FilePath& input_dir,
+    scoped_refptr<CrxInstaller> installer) {
+  std::string patch_rel_path;
+  std::string input_rel_path;
+  if (!command_args->GetString(kPatch, &patch_rel_path) ||
+      !command_args->GetString(kInput, &input_rel_path))
+    return UnpackerError::kDeltaBadCommands;
+
+  if (!installer->GetInstalledFile(input_rel_path, &input_abs_path_))
+    return UnpackerError::kDeltaMissingExistingFile;
+
+  patch_abs_path_ =
+      input_dir.Append(base::FilePath::FromUTF8Unsafe(patch_rel_path));
+
+  return UnpackerError::kNone;
+}
+
+void DeltaUpdateOpPatch::DoRun(ComponentPatcher::Callback callback) {
+  if (operation_ == kBsdiff) {
+    patcher_->PatchBsdiff(input_abs_path_, patch_abs_path_, output_abs_path_,
+                          base::BindOnce(&DeltaUpdateOpPatch::DonePatching,
+                                         this, std::move(callback)));
+  } else {
+    patcher_->PatchCourgette(input_abs_path_, patch_abs_path_, output_abs_path_,
+                             base::BindOnce(&DeltaUpdateOpPatch::DonePatching,
+                                            this, std::move(callback)));
+  }
+}
+
+void DeltaUpdateOpPatch::DonePatching(ComponentPatcher::Callback callback,
+                                      int result) {
+  if (operation_ == kBsdiff) {
+    if (result == bsdiff::OK) {
+      std::move(callback).Run(UnpackerError::kNone, 0);
+    } else {
+      std::move(callback).Run(UnpackerError::kDeltaOperationFailure,
+                              result + kBsdiffErrorOffset);
+    }
+  } else if (operation_ == kCourgette) {
+    if (result == courgette::C_OK) {
+      std::move(callback).Run(UnpackerError::kNone, 0);
+    } else {
+      std::move(callback).Run(UnpackerError::kDeltaOperationFailure,
+                              result + kCourgetteErrorOffset);
+    }
+  } else {
+    NOTREACHED();
+  }
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/component_patcher_operation.h b/src/components/update_client/component_patcher_operation.h
new file mode 100644
index 0000000..0649ff9
--- /dev/null
+++ b/src/components/update_client/component_patcher_operation.h
@@ -0,0 +1,163 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_OPERATION_H_
+#define COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_OPERATION_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/component_patcher.h"
+#include "components/update_client/component_unpacker.h"
+
+namespace base {
+class DictionaryValue;
+}  // namespace base
+
+namespace update_client {
+
+extern const char kOp[];
+extern const char kBsdiff[];
+extern const char kCourgette[];
+extern const char kInput[];
+extern const char kPatch[];
+
+class CrxInstaller;
+class Patcher;
+enum class UnpackerError;
+
+class DeltaUpdateOp : public base::RefCountedThreadSafe<DeltaUpdateOp> {
+ public:
+  DeltaUpdateOp();
+
+  // Parses, runs, and verifies the operation. Calls |callback| with the
+  // result of the operation. The callback is called using |task_runner|.
+  void Run(const base::DictionaryValue* command_args,
+           const base::FilePath& input_dir,
+           const base::FilePath& unpack_dir,
+           scoped_refptr<CrxInstaller> installer,
+           ComponentPatcher::Callback callback);
+
+ protected:
+  virtual ~DeltaUpdateOp();
+
+  std::string output_sha256_;
+  base::FilePath output_abs_path_;
+
+ private:
+  friend class base::RefCountedThreadSafe<DeltaUpdateOp>;
+
+  UnpackerError CheckHash();
+
+  // Subclasses must override DoParseArguments to parse operation-specific
+  // arguments. DoParseArguments returns DELTA_OK on success; any other code
+  // represents failure.
+  virtual UnpackerError DoParseArguments(
+      const base::DictionaryValue* command_args,
+      const base::FilePath& input_dir,
+      scoped_refptr<CrxInstaller> installer) = 0;
+
+  // Subclasses must override DoRun to actually perform the patching operation.
+  // They must call the provided callback when they have completed their
+  // operations. In practice, the provided callback is always for "DoneRunning".
+  virtual void DoRun(ComponentPatcher::Callback callback) = 0;
+
+  // Callback given to subclasses for when they complete their operation.
+  // Validates the output, and posts a task to the patching operation's
+  // callback.
+  void DoneRunning(UnpackerError error, int extended_error);
+
+  ComponentPatcher::Callback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOp);
+};
+
+// A 'copy' operation takes a file currently residing on the disk and moves it
+// into the unpacking directory: this represents "no change" in the file being
+// installed.
+class DeltaUpdateOpCopy : public DeltaUpdateOp {
+ public:
+  DeltaUpdateOpCopy();
+
+ private:
+  ~DeltaUpdateOpCopy() override;
+
+  // Overrides of DeltaUpdateOp.
+  UnpackerError DoParseArguments(
+      const base::DictionaryValue* command_args,
+      const base::FilePath& input_dir,
+      scoped_refptr<CrxInstaller> installer) override;
+
+  void DoRun(ComponentPatcher::Callback callback) override;
+
+  base::FilePath input_abs_path_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpCopy);
+};
+
+// A 'create' operation takes a full file that was sent in the delta update
+// archive and moves it into the unpacking directory: this represents the
+// addition of a new file, or a file so different that no bandwidth could be
+// saved by transmitting a differential update.
+class DeltaUpdateOpCreate : public DeltaUpdateOp {
+ public:
+  DeltaUpdateOpCreate();
+
+ private:
+  ~DeltaUpdateOpCreate() override;
+
+  // Overrides of DeltaUpdateOp.
+  UnpackerError DoParseArguments(
+      const base::DictionaryValue* command_args,
+      const base::FilePath& input_dir,
+      scoped_refptr<CrxInstaller> installer) override;
+
+  void DoRun(ComponentPatcher::Callback callback) override;
+
+  base::FilePath patch_abs_path_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpCreate);
+};
+
+// Both 'bsdiff' and 'courgette' operations take an existing file on disk,
+// and a bsdiff- or Courgette-format patch file provided in the delta update
+// package, and run bsdiff or Courgette to construct an output file in the
+// unpacking directory.
+class DeltaUpdateOpPatch : public DeltaUpdateOp {
+ public:
+  DeltaUpdateOpPatch(const std::string& operation,
+                     scoped_refptr<Patcher> patcher);
+
+ private:
+  ~DeltaUpdateOpPatch() override;
+
+  // Overrides of DeltaUpdateOp.
+  UnpackerError DoParseArguments(
+      const base::DictionaryValue* command_args,
+      const base::FilePath& input_dir,
+      scoped_refptr<CrxInstaller> installer) override;
+
+  void DoRun(ComponentPatcher::Callback callback) override;
+
+  // |success_code| is the code that indicates a successful patch.
+  // |result| is the code the patching operation returned.
+  void DonePatching(ComponentPatcher::Callback callback, int result);
+
+  std::string operation_;
+  scoped_refptr<Patcher> patcher_;
+  base::FilePath patch_abs_path_;
+  base::FilePath input_abs_path_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpPatch);
+};
+
+DeltaUpdateOp* CreateDeltaUpdateOp(const std::string& operation,
+                                   scoped_refptr<Patcher> patcher);
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_OPERATION_H_
diff --git a/src/components/update_client/component_patcher_unittest.cc b/src/components/update_client/component_patcher_unittest.cc
new file mode 100644
index 0000000..0995703
--- /dev/null
+++ b/src/components/update_client/component_patcher_unittest.cc
@@ -0,0 +1,209 @@
+// Copyright 2013 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 "components/update_client/component_patcher.h"
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "components/courgette/courgette.h"
+#include "components/courgette/third_party/bsdiff/bsdiff.h"
+#include "components/services/patch/in_process_file_patcher.h"
+#include "components/update_client/component_patcher_operation.h"
+#include "components/update_client/component_patcher_unittest.h"
+#include "components/update_client/patch/patch_impl.h"
+#include "components/update_client/test_installer.h"
+#include "components/update_client/update_client_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class TestCallback {
+ public:
+  TestCallback();
+  virtual ~TestCallback() {}
+  void Set(update_client::UnpackerError error, int extra_code);
+
+  update_client::UnpackerError error_;
+  int extra_code_;
+  bool called_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestCallback);
+};
+
+TestCallback::TestCallback()
+    : error_(update_client::UnpackerError::kNone),
+      extra_code_(-1),
+      called_(false) {}
+
+void TestCallback::Set(update_client::UnpackerError error, int extra_code) {
+  error_ = error;
+  extra_code_ = extra_code;
+  called_ = true;
+}
+
+base::FilePath test_file(const char* file) {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  return path.AppendASCII("components")
+      .AppendASCII("test")
+      .AppendASCII("data")
+      .AppendASCII("update_client")
+      .AppendASCII(file);
+}
+
+}  // namespace
+
+namespace update_client {
+
+ComponentPatcherOperationTest::ComponentPatcherOperationTest()
+    : scoped_task_environment_(
+          base::test::ScopedTaskEnvironment::MainThreadType::IO) {
+  EXPECT_TRUE(unpack_dir_.CreateUniqueTempDir());
+  EXPECT_TRUE(input_dir_.CreateUniqueTempDir());
+  EXPECT_TRUE(installed_dir_.CreateUniqueTempDir());
+  installer_ =
+      base::MakeRefCounted<ReadOnlyTestInstaller>(installed_dir_.GetPath());
+}
+
+ComponentPatcherOperationTest::~ComponentPatcherOperationTest() {}
+
+// Verify that a 'create' delta update operation works correctly.
+TEST_F(ComponentPatcherOperationTest, CheckCreateOperation) {
+  EXPECT_TRUE(base::CopyFile(
+      test_file("binary_output.bin"),
+      input_dir_.GetPath().Append(FILE_PATH_LITERAL("binary_output.bin"))));
+
+  std::unique_ptr<base::DictionaryValue> command_args =
+      std::make_unique<base::DictionaryValue>();
+  command_args->SetString("output", "output.bin");
+  command_args->SetString("sha256", binary_output_hash);
+  command_args->SetString("op", "create");
+  command_args->SetString("patch", "binary_output.bin");
+
+  TestCallback callback;
+  scoped_refptr<DeltaUpdateOp> op = base::MakeRefCounted<DeltaUpdateOpCreate>();
+  op->Run(command_args.get(), input_dir_.GetPath(), unpack_dir_.GetPath(),
+          nullptr,
+          base::BindOnce(&TestCallback::Set, base::Unretained(&callback)));
+  scoped_task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(true, callback.called_);
+  EXPECT_EQ(UnpackerError::kNone, callback.error_);
+  EXPECT_EQ(0, callback.extra_code_);
+  EXPECT_TRUE(base::ContentsEqual(
+      unpack_dir_.GetPath().Append(FILE_PATH_LITERAL("output.bin")),
+      test_file("binary_output.bin")));
+}
+
+// Verify that a 'copy' delta update operation works correctly.
+TEST_F(ComponentPatcherOperationTest, CheckCopyOperation) {
+  EXPECT_TRUE(base::CopyFile(
+      test_file("binary_output.bin"),
+      installed_dir_.GetPath().Append(FILE_PATH_LITERAL("binary_output.bin"))));
+
+  std::unique_ptr<base::DictionaryValue> command_args =
+      std::make_unique<base::DictionaryValue>();
+  command_args->SetString("output", "output.bin");
+  command_args->SetString("sha256", binary_output_hash);
+  command_args->SetString("op", "copy");
+  command_args->SetString("input", "binary_output.bin");
+
+  TestCallback callback;
+  scoped_refptr<DeltaUpdateOp> op = base::MakeRefCounted<DeltaUpdateOpCopy>();
+  op->Run(command_args.get(), input_dir_.GetPath(), unpack_dir_.GetPath(),
+          installer_.get(),
+          base::BindOnce(&TestCallback::Set, base::Unretained(&callback)));
+  scoped_task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(true, callback.called_);
+  EXPECT_EQ(UnpackerError::kNone, callback.error_);
+  EXPECT_EQ(0, callback.extra_code_);
+  EXPECT_TRUE(base::ContentsEqual(
+      unpack_dir_.GetPath().Append(FILE_PATH_LITERAL("output.bin")),
+      test_file("binary_output.bin")));
+}
+
+// Verify that a 'courgette' delta update operation works correctly.
+TEST_F(ComponentPatcherOperationTest, CheckCourgetteOperation) {
+  EXPECT_TRUE(base::CopyFile(
+      test_file("binary_input.bin"),
+      installed_dir_.GetPath().Append(FILE_PATH_LITERAL("binary_input.bin"))));
+  EXPECT_TRUE(base::CopyFile(test_file("binary_courgette_patch.bin"),
+                             input_dir_.GetPath().Append(FILE_PATH_LITERAL(
+                                 "binary_courgette_patch.bin"))));
+
+  std::unique_ptr<base::DictionaryValue> command_args =
+      std::make_unique<base::DictionaryValue>();
+  command_args->SetString("output", "output.bin");
+  command_args->SetString("sha256", binary_output_hash);
+  command_args->SetString("op", "courgette");
+  command_args->SetString("input", "binary_input.bin");
+  command_args->SetString("patch", "binary_courgette_patch.bin");
+
+  scoped_refptr<Patcher> patcher =
+      base::MakeRefCounted<PatchChromiumFactory>(
+          base::BindRepeating(&patch::LaunchInProcessFilePatcher))
+          ->Create();
+
+  TestCallback callback;
+  scoped_refptr<DeltaUpdateOp> op = CreateDeltaUpdateOp("courgette", patcher);
+  op->Run(command_args.get(), input_dir_.GetPath(), unpack_dir_.GetPath(),
+          installer_.get(),
+          base::BindOnce(&TestCallback::Set, base::Unretained(&callback)));
+  scoped_task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(true, callback.called_);
+  EXPECT_EQ(UnpackerError::kNone, callback.error_);
+  EXPECT_EQ(0, callback.extra_code_);
+  EXPECT_TRUE(base::ContentsEqual(
+      unpack_dir_.GetPath().Append(FILE_PATH_LITERAL("output.bin")),
+      test_file("binary_output.bin")));
+}
+
+// Verify that a 'bsdiff' delta update operation works correctly.
+TEST_F(ComponentPatcherOperationTest, CheckBsdiffOperation) {
+  EXPECT_TRUE(base::CopyFile(
+      test_file("binary_input.bin"),
+      installed_dir_.GetPath().Append(FILE_PATH_LITERAL("binary_input.bin"))));
+  EXPECT_TRUE(base::CopyFile(test_file("binary_bsdiff_patch.bin"),
+                             input_dir_.GetPath().Append(FILE_PATH_LITERAL(
+                                 "binary_bsdiff_patch.bin"))));
+
+  std::unique_ptr<base::DictionaryValue> command_args =
+      std::make_unique<base::DictionaryValue>();
+  command_args->SetString("output", "output.bin");
+  command_args->SetString("sha256", binary_output_hash);
+  command_args->SetString("op", "courgette");
+  command_args->SetString("input", "binary_input.bin");
+  command_args->SetString("patch", "binary_bsdiff_patch.bin");
+
+  // The operation needs a Patcher to access the PatchService.
+  scoped_refptr<Patcher> patcher =
+      base::MakeRefCounted<PatchChromiumFactory>(
+          base::BindRepeating(&patch::LaunchInProcessFilePatcher))
+          ->Create();
+
+  TestCallback callback;
+  scoped_refptr<DeltaUpdateOp> op = CreateDeltaUpdateOp("bsdiff", patcher);
+  op->Run(command_args.get(), input_dir_.GetPath(), unpack_dir_.GetPath(),
+          installer_.get(),
+          base::BindOnce(&TestCallback::Set, base::Unretained(&callback)));
+  scoped_task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(true, callback.called_);
+  EXPECT_EQ(UnpackerError::kNone, callback.error_);
+  EXPECT_EQ(0, callback.extra_code_);
+  EXPECT_TRUE(base::ContentsEqual(
+      unpack_dir_.GetPath().Append(FILE_PATH_LITERAL("output.bin")),
+      test_file("binary_output.bin")));
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/component_patcher_unittest.h b/src/components/update_client/component_patcher_unittest.h
new file mode 100644
index 0000000..ec5b849
--- /dev/null
+++ b/src/components/update_client/component_patcher_unittest.h
@@ -0,0 +1,41 @@
+// Copyright 2013 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 COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_UNITTEST_H_
+#define COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_UNITTEST_H_
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "base/test/scoped_task_environment.h"
+#include "components/courgette/courgette.h"
+#include "components/courgette/third_party/bsdiff/bsdiff.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+class ReadOnlyTestInstaller;
+
+const char binary_output_hash[] =
+    "599aba6d15a7da390621ef1bacb66601ed6aed04dadc1f9b445dcfe31296142a";
+
+class ComponentPatcherOperationTest : public testing::Test {
+ public:
+  ComponentPatcherOperationTest();
+  ~ComponentPatcherOperationTest() override;
+
+ protected:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  base::ScopedTempDir input_dir_;
+  base::ScopedTempDir installed_dir_;
+  base::ScopedTempDir unpack_dir_;
+  scoped_refptr<ReadOnlyTestInstaller> installer_;
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_UNITTEST_H_
diff --git a/src/components/update_client/component_unpacker.cc b/src/components/update_client/component_unpacker.cc
new file mode 100644
index 0000000..c80919e
--- /dev/null
+++ b/src/components/update_client/component_unpacker.cc
@@ -0,0 +1,205 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/component_unpacker.h"
+
+#include <stdint.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/update_client/component_patcher.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/unzipper.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+
+namespace update_client {
+
+ComponentUnpacker::Result::Result() {}
+
+ComponentUnpacker::ComponentUnpacker(const std::vector<uint8_t>& pk_hash,
+                                     const base::FilePath& path,
+                                     scoped_refptr<CrxInstaller> installer,
+                                     std::unique_ptr<Unzipper> unzipper,
+                                     scoped_refptr<Patcher> patcher,
+                                     crx_file::VerifierFormat crx_format)
+    : pk_hash_(pk_hash),
+      path_(path),
+      is_delta_(false),
+      installer_(installer),
+      unzipper_(std::move(unzipper)),
+      patcher_tool_(patcher),
+      crx_format_(crx_format),
+      error_(UnpackerError::kNone),
+#if defined(OS_STARBOARD)
+      extended_error_(0),
+      metadata_(nullptr),
+      id_(""),
+      update_version_("") {}
+#else
+      extended_error_(0) {}
+#endif
+
+#if defined(OS_STARBOARD)
+ComponentUnpacker::ComponentUnpacker(const std::vector<uint8_t>& pk_hash,
+                                     const base::FilePath& path,
+                                     scoped_refptr<CrxInstaller> installer,
+                                     std::unique_ptr<Unzipper> unzipper,
+                                     scoped_refptr<Patcher> patcher,
+                                     crx_file::VerifierFormat crx_format,
+                                     PersistedData* metadata,
+                                     const std::string& id,
+                                     const std::string& update_version)
+    : pk_hash_(pk_hash),
+      path_(path),
+      is_delta_(false),
+      installer_(installer),
+      unzipper_(std::move(unzipper)),
+      patcher_tool_(patcher),
+      crx_format_(crx_format),
+      error_(UnpackerError::kNone),
+      extended_error_(0),
+      metadata_(metadata),
+      id_(id),
+      update_version_(update_version) {}
+#endif
+
+ComponentUnpacker::~ComponentUnpacker() {}
+
+void ComponentUnpacker::Unpack(Callback callback) {
+  callback_ = std::move(callback);
+  if (!Verify() || !BeginUnzipping())
+    EndUnpacking();
+}
+
+bool ComponentUnpacker::Verify() {
+  VLOG(1) << "Verifying component: " << path_.value();
+
+  if (path_.empty()) {
+    error_ = UnpackerError::kInvalidParams;
+    return false;
+  }
+  std::vector<std::vector<uint8_t>> required_keys;
+  if (!pk_hash_.empty())
+    required_keys.push_back(pk_hash_);
+  const crx_file::VerifierResult result =
+      crx_file::Verify(path_, crx_format_, required_keys,
+                       std::vector<uint8_t>(), &public_key_, nullptr);
+  if (result != crx_file::VerifierResult::OK_FULL &&
+      result != crx_file::VerifierResult::OK_DELTA) {
+    error_ = UnpackerError::kInvalidFile;
+    extended_error_ = static_cast<int>(result);
+    SB_LOG(INFO) << "Verification failed. Verifier error = " << extended_error_;
+    return false;
+  }
+  is_delta_ = result == crx_file::VerifierResult::OK_DELTA;
+  VLOG(1) << "Verification successful: " << path_.value();
+  return true;
+}
+
+bool ComponentUnpacker::BeginUnzipping() {
+  // Mind the reference to non-const type, passed as an argument below.
+  base::FilePath& destination = is_delta_ ? unpack_diff_path_ : unpack_path_;
+
+#if !defined(OS_STARBOARD)
+  if (!base::CreateNewTempDirectory(base::FilePath::StringType(),
+                                    &destination)) {
+    VLOG(1) << "Unable to create temporary directory for unpacking.";
+    error_ = UnpackerError::kUnzipPathError;
+    return false;
+  }
+#else
+  // The directory of path_ is the installation slot.
+  destination = path_.DirName();
+
+#endif
+  VLOG(1) << "Unpacking in: " << destination.value();
+  unzipper_->Unzip(path_, destination,
+                   base::BindOnce(&ComponentUnpacker::EndUnzipping, this));
+
+  return true;
+}
+
+void ComponentUnpacker::EndUnzipping(bool result) {
+  if (!result) {
+    VLOG(1) << "Unzipping failed.";
+    error_ = UnpackerError::kUnzipFailed;
+    EndUnpacking();
+    return;
+  }
+  VLOG(1) << "Unpacked successfully";
+  BeginPatching();
+}
+
+bool ComponentUnpacker::BeginPatching() {
+  if (is_delta_) {  // Package is a diff package.
+    // Use a different temp directory for the patch output files.
+    if (!base::CreateNewTempDirectory(base::FilePath::StringType(),
+                                      &unpack_path_)) {
+      error_ = UnpackerError::kUnzipPathError;
+      return false;
+    }
+    patcher_ = base::MakeRefCounted<ComponentPatcher>(
+        unpack_diff_path_, unpack_path_, installer_, patcher_tool_);
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&ComponentPatcher::Start, patcher_,
+                       base::BindOnce(&ComponentUnpacker::EndPatching,
+                                      scoped_refptr<ComponentUnpacker>(this))));
+  } else {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(&ComponentUnpacker::EndPatching,
+                                  scoped_refptr<ComponentUnpacker>(this),
+                                  UnpackerError::kNone, 0));
+  }
+  return true;
+}
+
+void ComponentUnpacker::EndPatching(UnpackerError error, int extended_error) {
+  error_ = error;
+  extended_error_ = extended_error;
+  patcher_ = nullptr;
+
+  EndUnpacking();
+}
+
+void ComponentUnpacker::EndUnpacking() {
+#if !defined(OS_STARBOARD)
+  if (!unpack_diff_path_.empty())
+    base::DeleteFile(unpack_diff_path_, true);
+  if (error_ != UnpackerError::kNone && !unpack_path_.empty())
+    base::DeleteFile(unpack_path_, true);
+#else
+  // Write the version of the unpacked update package to the persisted data.
+  if (error_ == UnpackerError::kNone && metadata_ != nullptr) {
+    metadata_->SetLastUnpackedVersion(id_, update_version_);
+  }
+#endif
+
+  Result result;
+  result.error = error_;
+  result.extended_error = extended_error_;
+  if (error_ == UnpackerError::kNone) {
+    result.unpack_path = unpack_path_;
+    result.public_key = public_key_;
+  }
+
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback_), result));
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/component_unpacker.h b/src/components/update_client/component_unpacker.h
new file mode 100644
index 0000000..af967f0
--- /dev/null
+++ b/src/components/update_client/component_unpacker.h
@@ -0,0 +1,172 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_COMPONENT_UNPACKER_H_
+#define COMPONENTS_UPDATE_CLIENT_COMPONENT_UNPACKER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/update_client_errors.h"
+
+namespace crx_file {
+enum class VerifierFormat;
+}
+
+namespace update_client {
+
+class CrxInstaller;
+class ComponentPatcher;
+class Patcher;
+class Unzipper;
+
+// In charge of unpacking the component CRX package and verifying that it is
+// well formed and the cryptographic signature is correct.
+//
+// This class should be used only by the component updater. It is inspired by
+// and overlaps with code in the extension's SandboxedUnpacker.
+// The main differences are:
+// - The public key hash is full SHA256.
+// - Does not use a sandboxed unpacker. A valid component is fully trusted.
+// - The manifest can have different attributes and resources are not
+//   transcoded.
+//
+// If the CRX is a delta CRX, the flow is:
+//   [ComponentUpdater]      [ComponentPatcher]
+//   Unpack
+//     \_ Verify
+//     \_ Unzip
+//     \_ BeginPatching ---> DifferentialUpdatePatch
+//                             ...
+//   EndPatching <------------ ...
+//     \_ EndUnpacking
+//
+// For a full CRX, the flow is:
+//   [ComponentUpdater]
+//   Unpack
+//     \_ Verify
+//     \_ Unzip
+//     \_ BeginPatching
+//          |
+//          V
+//   EndPatching
+//     \_ EndUnpacking
+//
+// In both cases, if there is an error at any point, the remaining steps will
+// be skipped and EndUnpacking will be called.
+class ComponentUnpacker : public base::RefCountedThreadSafe<ComponentUnpacker> {
+ public:
+  // Contains the result of the unpacking.
+  struct Result {
+    Result();
+
+    // Unpack error: 0 indicates success.
+    UnpackerError error = UnpackerError::kNone;
+
+    // Additional error information, such as errno or last error.
+    int extended_error = 0;
+
+    // Path of the unpacked files if the unpacking was successful.
+    base::FilePath unpack_path;
+
+    // The extracted public key of the package if the unpacking was successful.
+    std::string public_key;
+  };
+
+  using Callback = base::OnceCallback<void(const Result& result)>;
+
+  // Constructs an unpacker for a specific component unpacking operation.
+  // |pk_hash| is the expected public developer key's SHA256 hash. If empty,
+  // the unpacker accepts any developer key. |path| is the current location
+  // of the CRX.
+  ComponentUnpacker(const std::vector<uint8_t>& pk_hash,
+                    const base::FilePath& path,
+                    scoped_refptr<CrxInstaller> installer,
+                    std::unique_ptr<Unzipper> unzipper,
+                    scoped_refptr<Patcher> patcher,
+                    crx_file::VerifierFormat crx_format);
+
+#if defined(OS_STARBOARD)
+  // Constructs an unpacker for a specific component unpacking operation.
+  // |pk_hash| is the expected public developer key's SHA256 hash. If empty,
+  // the unpacker accepts any developer key. |path| is the current location
+  // of the CRX. This unpacker write the update package version to persisted
+  // data.
+  ComponentUnpacker(const std::vector<uint8_t>& pk_hash,
+                    const base::FilePath& path,
+                    scoped_refptr<CrxInstaller> installer,
+                    std::unique_ptr<Unzipper> unzipper,
+                    scoped_refptr<Patcher> patcher,
+                    crx_file::VerifierFormat crx_format,
+                    PersistedData* metadata,
+                    const std::string& id,
+                    const std::string& update_version);
+#endif
+
+  // Begins the actual unpacking of the files. May invoke a patcher and the
+  // component installer if the package is a differential update.
+  // Calls |callback| with the result.
+  void Unpack(Callback callback);
+
+ private:
+  friend class base::RefCountedThreadSafe<ComponentUnpacker>;
+
+  virtual ~ComponentUnpacker();
+
+  // The first step of unpacking is to verify the file. Returns false if an
+  // error is encountered, the file is malformed, or the file is incorrectly
+  // signed.
+  bool Verify();
+
+  // The second step of unpacking is to unzip. Returns false if an early error
+  // is encountered.
+  bool BeginUnzipping();
+  void EndUnzipping(bool error);
+
+  // The third step is to optionally patch files - this is a no-op for full
+  // (non-differential) updates. This step is asynchronous. Returns false if an
+  // error is encountered.
+  bool BeginPatching();
+  void EndPatching(UnpackerError error, int extended_error);
+
+  // The final step is to do clean-up for things that can't be tidied as we go.
+  // If there is an error at any step, the remaining steps are skipped and
+  // EndUnpacking is called. EndUnpacking is responsible for calling the
+  // callback provided in Unpack().
+  void EndUnpacking();
+
+  std::vector<uint8_t> pk_hash_;
+  base::FilePath path_;
+  base::FilePath unpack_path_;
+  base::FilePath unpack_diff_path_;
+  bool is_delta_;
+  scoped_refptr<ComponentPatcher> patcher_;
+  scoped_refptr<CrxInstaller> installer_;
+  Callback callback_;
+  std::unique_ptr<Unzipper> unzipper_;
+  scoped_refptr<Patcher> patcher_tool_;
+  crx_file::VerifierFormat crx_format_;
+  UnpackerError error_;
+  int extended_error_;
+  std::string public_key_;
+#if defined(OS_STARBOARD)
+  PersistedData* metadata_;
+  std::string id_;
+  std::string update_version_;
+#endif
+
+  DISALLOW_COPY_AND_ASSIGN(ComponentUnpacker);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_COMPONENT_UNPACKER_H_
diff --git a/src/components/update_client/component_unpacker_unittest.cc b/src/components/update_client/component_unpacker_unittest.cc
new file mode 100644
index 0000000..617abcb
--- /dev/null
+++ b/src/components/update_client/component_unpacker_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright 2016 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 <iterator>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/update_client/component_unpacker.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/test_configurator.h"
+#include "components/update_client/test_installer.h"
+#include "components/update_client/unzipper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class TestCallback {
+ public:
+  TestCallback();
+  virtual ~TestCallback() {}
+  void Set(update_client::UnpackerError error, int extra_code);
+
+  update_client::UnpackerError error_;
+  int extra_code_;
+  bool called_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestCallback);
+};
+
+TestCallback::TestCallback()
+    : error_(update_client::UnpackerError::kNone),
+      extra_code_(-1),
+      called_(false) {}
+
+void TestCallback::Set(update_client::UnpackerError error, int extra_code) {
+  error_ = error;
+  extra_code_ = extra_code;
+  called_ = true;
+}
+
+base::FilePath test_file(const char* file) {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  return path.AppendASCII("components")
+      .AppendASCII("test")
+      .AppendASCII("data")
+      .AppendASCII("update_client")
+      .AppendASCII(file);
+}
+
+}  // namespace
+
+namespace update_client {
+
+class ComponentUnpackerTest : public testing::Test {
+ public:
+  ComponentUnpackerTest();
+  ~ComponentUnpackerTest() override;
+
+  void UnpackComplete(const ComponentUnpacker::Result& result);
+
+ protected:
+  void RunThreads();
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  const scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_ =
+      base::ThreadTaskRunnerHandle::Get();
+  base::RunLoop runloop_;
+  base::OnceClosure quit_closure_ = runloop_.QuitClosure();
+
+  ComponentUnpacker::Result result_;
+};
+
+ComponentUnpackerTest::ComponentUnpackerTest() = default;
+
+ComponentUnpackerTest::~ComponentUnpackerTest() = default;
+
+void ComponentUnpackerTest::RunThreads() {
+  runloop_.Run();
+}
+
+void ComponentUnpackerTest::UnpackComplete(
+    const ComponentUnpacker::Result& result) {
+  result_ = result;
+  main_thread_task_runner_->PostTask(FROM_HERE, std::move(quit_closure_));
+}
+
+TEST_F(ComponentUnpackerTest, UnpackFullCrx) {
+  auto config = base::MakeRefCounted<TestConfigurator>();
+  scoped_refptr<ComponentUnpacker> component_unpacker =
+      base::MakeRefCounted<ComponentUnpacker>(
+          std::vector<uint8_t>(std::begin(jebg_hash), std::end(jebg_hash)),
+          test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"), nullptr,
+          config->GetUnzipperFactory()->Create(),
+          config->GetPatcherFactory()->Create(),
+          crx_file::VerifierFormat::CRX2_OR_CRX3);
+  component_unpacker->Unpack(base::BindOnce(
+      &ComponentUnpackerTest::UnpackComplete, base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(UnpackerError::kNone, result_.error);
+  EXPECT_EQ(0, result_.extended_error);
+
+  base::FilePath unpack_path = result_.unpack_path;
+  EXPECT_FALSE(unpack_path.empty());
+  EXPECT_TRUE(base::DirectoryExists(unpack_path));
+  EXPECT_EQ(jebg_public_key, result_.public_key);
+
+  int64_t file_size = 0;
+  EXPECT_TRUE(
+      base::GetFileSize(unpack_path.AppendASCII("component1.dll"), &file_size));
+  EXPECT_EQ(1024, file_size);
+  EXPECT_TRUE(
+      base::GetFileSize(unpack_path.AppendASCII("flashtest.pem"), &file_size));
+  EXPECT_EQ(911, file_size);
+  EXPECT_TRUE(
+      base::GetFileSize(unpack_path.AppendASCII("manifest.json"), &file_size));
+  EXPECT_EQ(144, file_size);
+
+  EXPECT_TRUE(base::DeleteFile(unpack_path, true));
+}
+
+TEST_F(ComponentUnpackerTest, UnpackFileNotFound) {
+  scoped_refptr<ComponentUnpacker> component_unpacker =
+      base::MakeRefCounted<ComponentUnpacker>(
+          std::vector<uint8_t>(std::begin(jebg_hash), std::end(jebg_hash)),
+          test_file("file-not-found.crx"), nullptr, nullptr, nullptr,
+          crx_file::VerifierFormat::CRX2_OR_CRX3);
+  component_unpacker->Unpack(base::BindOnce(
+      &ComponentUnpackerTest::UnpackComplete, base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(UnpackerError::kInvalidFile, result_.error);
+  EXPECT_EQ(static_cast<int>(crx_file::VerifierResult::ERROR_FILE_NOT_READABLE),
+            result_.extended_error);
+
+  EXPECT_TRUE(result_.unpack_path.empty());
+}
+
+// Tests a mismatch between the public key hash and the id of the component.
+TEST_F(ComponentUnpackerTest, UnpackFileHashMismatch) {
+  scoped_refptr<ComponentUnpacker> component_unpacker =
+      base::MakeRefCounted<ComponentUnpacker>(
+          std::vector<uint8_t>(std::begin(abag_hash), std::end(abag_hash)),
+          test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"), nullptr, nullptr,
+          nullptr, crx_file::VerifierFormat::CRX2_OR_CRX3);
+  component_unpacker->Unpack(base::BindOnce(
+      &ComponentUnpackerTest::UnpackComplete, base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(UnpackerError::kInvalidFile, result_.error);
+  EXPECT_EQ(
+      static_cast<int>(crx_file::VerifierResult::ERROR_REQUIRED_PROOF_MISSING),
+      result_.extended_error);
+
+  EXPECT_TRUE(result_.unpack_path.empty());
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/configurator.h b/src/components/update_client/configurator.h
new file mode 100644
index 0000000..5baa3cc
--- /dev/null
+++ b/src/components/update_client/configurator.h
@@ -0,0 +1,185 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_CONFIGURATOR_H_
+#define COMPONENTS_UPDATE_CLIENT_CONFIGURATOR_H_
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/containers/flat_map.h"
+#include "base/memory/ref_counted.h"
+
+class GURL;
+class PrefService;
+
+namespace base {
+class FilePath;
+class Version;
+}  // namespace base
+
+namespace update_client {
+
+class ActivityDataService;
+class NetworkFetcherFactory;
+class PatcherFactory;
+class ProtocolHandlerFactory;
+class UnzipperFactory;
+
+using RecoveryCRXElevator = base::OnceCallback<std::tuple<bool, int, int>(
+    const base::FilePath& crx_path,
+    const std::string& browser_appid,
+    const std::string& browser_version,
+    const std::string& session_id)>;
+
+// Controls the component updater behavior.
+// TODO(sorin): this class will be split soon in two. One class controls
+// the behavior of the update client, and the other class controls the
+// behavior of the component updater.
+class Configurator : public base::RefCountedThreadSafe<Configurator> {
+ public:
+  // Delay in seconds from calling Start() to the first update check.
+  virtual int InitialDelay() const = 0;
+
+  // Delay in seconds to every subsequent update check. 0 means don't check.
+  virtual int NextCheckDelay() const = 0;
+
+  // Minimum delta time in seconds before an on-demand check is allowed
+  // for the same component.
+  virtual int OnDemandDelay() const = 0;
+
+  // The time delay in seconds between applying updates for different
+  // components.
+  virtual int UpdateDelay() const = 0;
+
+  // The URLs for the update checks. The URLs are tried in order, the first one
+  // that succeeds wins.
+  virtual std::vector<GURL> UpdateUrl() const = 0;
+
+  // The URLs for pings. Returns an empty vector if and only if pings are
+  // disabled. Similarly, these URLs have a fall back behavior too.
+  virtual std::vector<GURL> PingUrl() const = 0;
+
+  // The ProdId is used as a prefix in some of the version strings which appear
+  // in the protocol requests. Possible values include "chrome", "chromecrx",
+  // "chromiumcrx", and "unknown".
+  virtual std::string GetProdId() const = 0;
+
+  // Version of the application. Used to compare the component manifests.
+  virtual base::Version GetBrowserVersion() const = 0;
+
+  // Returns the value we use for the "updaterchannel=" and "prodchannel="
+  // parameters. Possible return values include: "canary", "dev", "beta", and
+  // "stable".
+  virtual std::string GetChannel() const = 0;
+
+  // Returns the brand code or distribution tag that has been assigned to
+  // a partner. A brand code is a 4-character string used to identify
+  // installations that took place as a result of partner deals or website
+  // promotions.
+  virtual std::string GetBrand() const = 0;
+
+  // Returns the language for the present locale. Possible return values are
+  // standard tags for languages, such as "en", "en-US", "de", "fr", "af", etc.
+  virtual std::string GetLang() const = 0;
+
+  // Returns the OS's long name like "Windows", "Mac OS X", etc.
+  virtual std::string GetOSLongName() const = 0;
+
+  // Parameters added to each url request. It can be empty if none are needed.
+  // Returns a map of name-value pairs that match ^[-_a-zA-Z0-9]$ regex.
+  virtual base::flat_map<std::string, std::string> ExtraRequestParams()
+      const = 0;
+
+  // Provides a hint for the server to control the order in which multiple
+  // download urls are returned. The hint may or may not be honored in the
+  // response returned by the server.
+  // Returns an empty string if no policy is in effect.
+  virtual std::string GetDownloadPreference() const = 0;
+
+  virtual scoped_refptr<NetworkFetcherFactory> GetNetworkFetcherFactory() = 0;
+
+  virtual scoped_refptr<UnzipperFactory> GetUnzipperFactory() = 0;
+
+  virtual scoped_refptr<PatcherFactory> GetPatcherFactory() = 0;
+
+  // True means that this client can handle delta updates.
+  virtual bool EnabledDeltas() const = 0;
+
+  // True if component updates are enabled. Updates for all components are
+  // enabled by default. This method allows enabling or disabling
+  // updates for certain components such as the plugins. Updates for some
+  // components are always enabled and can't be disabled programatically.
+  virtual bool EnabledComponentUpdates() const = 0;
+
+  // True means that the background downloader can be used for downloading
+  // non on-demand components.
+  virtual bool EnabledBackgroundDownloader() const = 0;
+
+  // True if signing of update checks is enabled.
+  virtual bool EnabledCupSigning() const = 0;
+
+  // Returns a PrefService that the update_client can use to store persistent
+  // update information. The PrefService must outlive the entire update_client,
+  // and be safe to access from the thread the update_client is constructed
+  // on.
+  // Returning null is safe and will disable any functionality that requires
+  // persistent storage.
+  virtual PrefService* GetPrefService() const = 0;
+
+  // Returns an ActivityDataService that the update_client can use to access
+  // to update information (namely active bit, last active/rollcall days)
+  // normally stored in the user extension profile.
+  // Similar to PrefService, ActivityDataService must outlive the entire
+  // update_client, and be safe to access from the thread the update_client
+  // is constructed on.
+  // Returning null is safe and will disable any functionality that requires
+  // accessing to the information provided by ActivityDataService.
+  virtual ActivityDataService* GetActivityDataService() const = 0;
+
+  // Returns true if the Chrome is installed for the current user only, or false
+  // if Chrome is installed for all users on the machine. This function must be
+  // called only from a blocking pool thread, as it may access the file system.
+  virtual bool IsPerUserInstall() const = 0;
+
+  // Returns the key hash corresponding to a CRX trusted by ActionRun. The
+  // CRX payloads are signed with this key, and their integrity is verified
+  // during the unpacking by the action runner. This is a dependency injection
+  // feature to support testing.
+  virtual std::vector<uint8_t> GetRunActionKeyHash() const = 0;
+
+  // Returns the app GUID with which Chrome is registered with Google Update, or
+  // an empty string if this brand does not integrate with Google Update.
+  virtual std::string GetAppGuid() const = 0;
+
+  // Returns the class factory to create protocol parser and protocol
+  // serializer object instances.
+  virtual std::unique_ptr<ProtocolHandlerFactory> GetProtocolHandlerFactory()
+      const = 0;
+
+  // Returns a callback which can elevate and run the CRX payload associated
+  // with the improved recovery component. Running this payload repairs the
+  // Chrome update functionality.
+  virtual RecoveryCRXElevator GetRecoveryCRXElevator() const = 0;
+
+#if defined(OS_STARBOARD)
+  // Sets the value we use for the "updaterchannel=" and "prodchannel="
+  // parameters.
+  virtual void SetChannel(const std::string& channel) = 0;
+
+  virtual bool IsChannelChanged() const = 0;
+#endif
+
+ protected:
+  friend class base::RefCountedThreadSafe<Configurator>;
+
+  virtual ~Configurator() {}
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_CONFIGURATOR_H_
diff --git a/src/components/update_client/crx_downloader.cc b/src/components/update_client/crx_downloader.cc
new file mode 100644
index 0000000..35cac8e
--- /dev/null
+++ b/src/components/update_client/crx_downloader.cc
@@ -0,0 +1,217 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/crx_downloader.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#if defined(OS_WIN)
+#include "components/update_client/background_downloader_win.h"
+#endif
+#include "components/update_client/network.h"
+#include "components/update_client/task_traits.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/url_fetcher_downloader.h"
+#include "components/update_client/utils.h"
+
+namespace update_client {
+
+CrxDownloader::DownloadMetrics::DownloadMetrics()
+    : downloader(kNone),
+      error(0),
+      downloaded_bytes(-1),
+      total_bytes(-1),
+      download_time_ms(0) {}
+
+// On Windows, the first downloader in the chain is a background downloader,
+// which uses the BITS service.
+std::unique_ptr<CrxDownloader> CrxDownloader::Create(
+    bool is_background_download,
+    scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+  std::unique_ptr<CrxDownloader> url_fetcher_downloader =
+      std::make_unique<UrlFetcherDownloader>(nullptr, network_fetcher_factory);
+
+#if defined(OS_WIN)
+  if (is_background_download) {
+    return std::make_unique<BackgroundDownloader>(
+        std::move(url_fetcher_downloader));
+  }
+#endif
+
+  return url_fetcher_downloader;
+}
+
+CrxDownloader::CrxDownloader(std::unique_ptr<CrxDownloader> successor)
+    : main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+      successor_(std::move(successor)) {}
+
+CrxDownloader::~CrxDownloader() {}
+
+void CrxDownloader::set_progress_callback(
+    const ProgressCallback& progress_callback) {
+  progress_callback_ = progress_callback;
+}
+
+GURL CrxDownloader::url() const {
+  return current_url_ != urls_.end() ? *current_url_ : GURL();
+}
+
+const std::vector<CrxDownloader::DownloadMetrics>
+CrxDownloader::download_metrics() const {
+  if (!successor_)
+    return download_metrics_;
+
+  std::vector<DownloadMetrics> retval(successor_->download_metrics());
+  retval.insert(retval.begin(), download_metrics_.begin(),
+                download_metrics_.end());
+  return retval;
+}
+
+void CrxDownloader::StartDownloadFromUrl(const GURL& url,
+                                         const std::string& expected_hash,
+                                         DownloadCallback download_callback) {
+  std::vector<GURL> urls;
+  urls.push_back(url);
+  StartDownload(urls, expected_hash, std::move(download_callback));
+}
+
+void CrxDownloader::StartDownload(const std::vector<GURL>& urls,
+                                  const std::string& expected_hash,
+                                  DownloadCallback download_callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto error = CrxDownloaderError::NONE;
+  if (urls.empty()) {
+    error = CrxDownloaderError::NO_URL;
+  } else if (expected_hash.empty()) {
+    error = CrxDownloaderError::NO_HASH;
+  }
+
+  if (error != CrxDownloaderError::NONE) {
+    Result result;
+    result.error = static_cast<int>(error);
+    main_task_runner()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(download_callback), result));
+    return;
+  }
+
+  urls_ = urls;
+  expected_hash_ = expected_hash;
+  current_url_ = urls_.begin();
+  download_callback_ = std::move(download_callback);
+
+  DoStartDownload(*current_url_);
+}
+
+void CrxDownloader::OnDownloadComplete(
+    bool is_handled,
+    const Result& result,
+    const DownloadMetrics& download_metrics) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (!result.error)
+    base::PostTaskWithTraits(
+        FROM_HERE, kTaskTraits,
+        base::BindOnce(&CrxDownloader::VerifyResponse, base::Unretained(this),
+                       is_handled, result, download_metrics));
+  else
+    main_task_runner()->PostTask(
+        FROM_HERE, base::BindOnce(&CrxDownloader::HandleDownloadError,
+                                  base::Unretained(this), is_handled, result,
+                                  download_metrics));
+}
+
+void CrxDownloader::OnDownloadProgress() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (progress_callback_.is_null())
+    return;
+
+  progress_callback_.Run();
+}
+
+// The function mutates the values of the parameters |result| and
+// |download_metrics|.
+void CrxDownloader::VerifyResponse(bool is_handled,
+                                   Result result,
+                                   DownloadMetrics download_metrics) {
+  DCHECK_EQ(0, result.error);
+  DCHECK_EQ(0, download_metrics.error);
+  DCHECK(is_handled);
+
+  if (VerifyFileHash256(result.response, expected_hash_)) {
+    download_metrics_.push_back(download_metrics);
+    main_task_runner()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(download_callback_), result));
+    return;
+  }
+
+  // The download was successful but the response is not trusted. Clean up
+  // the download, mutate the result, and try the remaining fallbacks when
+  // handling the error.
+  result.error = static_cast<int>(CrxDownloaderError::BAD_HASH);
+  download_metrics.error = result.error;
+#if defined(OS_STARBOARD)
+  base::DeleteFile(result.response, false);
+#else
+  DeleteFileAndEmptyParentDirectory(result.response);
+#endif
+  result.response.clear();
+
+  main_task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&CrxDownloader::HandleDownloadError,
+                                base::Unretained(this), is_handled, result,
+                                download_metrics));
+}
+
+void CrxDownloader::HandleDownloadError(
+    bool is_handled,
+    const Result& result,
+    const DownloadMetrics& download_metrics) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_NE(0, result.error);
+  DCHECK(result.response.empty());
+  DCHECK_NE(0, download_metrics.error);
+
+  download_metrics_.push_back(download_metrics);
+
+  // If an error has occured, try the next url if there is any,
+  // or try the successor in the chain if there is any successor.
+  // If this downloader has received a 5xx error for the current url,
+  // as indicated by the |is_handled| flag, remove that url from the list of
+  // urls so the url is never tried again down the chain.
+  if (is_handled) {
+    current_url_ = urls_.erase(current_url_);
+  } else {
+    ++current_url_;
+  }
+
+  // Try downloading from another url from the list.
+  if (current_url_ != urls_.end()) {
+    DoStartDownload(*current_url_);
+    return;
+  }
+
+  // Try downloading using the next downloader.
+  if (successor_ && !urls_.empty()) {
+    successor_->StartDownload(urls_, expected_hash_,
+                              std::move(download_callback_));
+    return;
+  }
+
+  // The download ends here since there is no url nor downloader to handle this
+  // download request further.
+  main_task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(download_callback_), result));
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/crx_downloader.h b/src/components/update_client/crx_downloader.h
new file mode 100644
index 0000000..966ee82
--- /dev/null
+++ b/src/components/update_client/crx_downloader.h
@@ -0,0 +1,172 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_CRX_DOWNLOADER_H_
+#define COMPONENTS_UPDATE_CLIENT_CRX_DOWNLOADER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "url/gurl.h"
+
+#if defined(OS_STARBOARD)
+#include "cobalt/extension/installation_manager.h"
+#endif
+
+namespace update_client {
+
+class NetworkFetcherFactory;
+
+// Defines a download interface for downloading components, with retrying on
+// fallback urls in case of errors. This class implements a chain of
+// responsibility design pattern. It can give successors in the chain a chance
+// to handle a download request, until one of them succeeds, or there are no
+// more urls or successors to try. A callback is always called at the end of
+// the download, one time only.
+// When multiple urls and downloaders exists, first all the urls are tried, in
+// the order they are provided in the StartDownload function argument. After
+// that, the download request is routed to the next downloader in the chain.
+// The members of this class expect to be called from the main thread only.
+class CrxDownloader {
+ public:
+  struct DownloadMetrics {
+    enum Downloader { kNone = 0, kUrlFetcher, kBits };
+
+    DownloadMetrics();
+
+    GURL url;
+
+    Downloader downloader;
+
+    int error;
+
+    int64_t downloaded_bytes;  // -1 means that the byte count is unknown.
+    int64_t total_bytes;
+
+    uint64_t download_time_ms;
+  };
+
+  // Contains the progress or the outcome of the download.
+  struct Result {
+    // Download error: 0 indicates success.
+    int error = 0;
+
+#if defined(OS_STARBOARD)
+    int installation_index = IM_EXT_INVALID_INDEX;
+#endif
+
+    // Path of the downloaded file if the download was successful.
+    base::FilePath response;
+  };
+
+  // The callback fires only once, regardless of how many urls are tried, and
+  // how many successors in the chain of downloaders have handled the
+  // download. The callback interface can be extended if needed to provide
+  // more visibility into how the download has been handled, including
+  // specific error codes and download metrics.
+  using DownloadCallback = base::OnceCallback<void(const Result& result)>;
+
+  // The callback may fire 0 or once during a download. Since this
+  // class implements a chain of responsibility, the callback can fire for
+  // different urls and different downloaders.
+  using ProgressCallback = base::RepeatingCallback<void()>;
+
+  using Factory =
+      std::unique_ptr<CrxDownloader> (*)(bool,
+                                         scoped_refptr<NetworkFetcherFactory>);
+
+  // Factory method to create an instance of this class and build the
+  // chain of responsibility. |is_background_download| specifies that a
+  // background downloader be used, if the platform supports it.
+  // |task_runner| should be a task runner able to run blocking
+  // code such as file IO operations.
+  static std::unique_ptr<CrxDownloader> Create(
+      bool is_background_download,
+      scoped_refptr<NetworkFetcherFactory> network_fetcher_factory);
+  virtual ~CrxDownloader();
+
+  void set_progress_callback(const ProgressCallback& progress_callback);
+
+  // Starts the download. One instance of the class handles one download only.
+  // One instance of CrxDownloader can only be started once, otherwise the
+  // behavior is undefined. The callback gets invoked if the download can't
+  // be started. |expected_hash| represents the SHA256 cryptographic hash of
+  // the download payload, represented as a hexadecimal string.
+  void StartDownloadFromUrl(const GURL& url,
+                            const std::string& expected_hash,
+                            DownloadCallback download_callback);
+  void StartDownload(const std::vector<GURL>& urls,
+                     const std::string& expected_hash,
+                     DownloadCallback download_callback);
+
+  const std::vector<DownloadMetrics> download_metrics() const;
+
+ protected:
+  explicit CrxDownloader(std::unique_ptr<CrxDownloader> successor);
+
+  // Handles the fallback in the case of multiple urls and routing of the
+  // download to the following successor in the chain. Derived classes must call
+  // this function after each attempt at downloading the urls provided
+  // in the StartDownload function.
+  // In case of errors, |is_handled| indicates that a server side error has
+  // occured for the current url and the url should not be retried down
+  // the chain to avoid DDOS of the server. This url will be removed from the
+  // list of url and never tried again.
+  void OnDownloadComplete(bool is_handled,
+                          const Result& result,
+                          const DownloadMetrics& download_metrics);
+
+  // Calls the callback when progress is made.
+  void OnDownloadProgress();
+
+  // Returns the url which is currently being downloaded from.
+  GURL url() const;
+
+  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner() const {
+    return main_task_runner_;
+  }
+
+ private:
+  virtual void DoStartDownload(const GURL& url) = 0;
+
+  void VerifyResponse(bool is_handled,
+                      Result result,
+                      DownloadMetrics download_metrics);
+
+  void HandleDownloadError(bool is_handled,
+                           const Result& result,
+                           const DownloadMetrics& download_metrics);
+
+  base::ThreadChecker thread_checker_;
+
+  // Used to post callbacks to the main thread.
+  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+
+  std::vector<GURL> urls_;
+
+  // The SHA256 hash of the download payload in hexadecimal format.
+  std::string expected_hash_;
+  std::unique_ptr<CrxDownloader> successor_;
+  DownloadCallback download_callback_;
+  ProgressCallback progress_callback_;
+
+  std::vector<GURL>::iterator current_url_;
+
+  std::vector<DownloadMetrics> download_metrics_;
+
+  DISALLOW_COPY_AND_ASSIGN(CrxDownloader);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_CRX_DOWNLOADER_H_
diff --git a/src/components/update_client/crx_downloader_unittest.cc b/src/components/update_client/crx_downloader_unittest.cc
new file mode 100644
index 0000000..03c9002
--- /dev/null
+++ b/src/components/update_client/crx_downloader_unittest.cc
@@ -0,0 +1,418 @@
+// Copyright 2013 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 "components/update_client/crx_downloader.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/update_client/net/network_chromium.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+#include "net/base/net_errors.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::ContentsEqual;
+
+namespace update_client {
+
+namespace {
+
+const char kTestFileName[] = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+
+const char hash_jebg[] =
+    "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+base::FilePath MakeTestFilePath(const char* file) {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  return path.AppendASCII("components/test/data/update_client")
+      .AppendASCII(file);
+}
+
+}  // namespace
+
+class CrxDownloaderTest : public testing::Test {
+ public:
+  CrxDownloaderTest();
+  ~CrxDownloaderTest() override;
+
+  // Overrides from testing::Test.
+  void SetUp() override;
+  void TearDown() override;
+
+  void Quit();
+  void RunThreads();
+  void RunThreadsUntilIdle();
+
+  void DownloadComplete(int crx_context, const CrxDownloader::Result& result);
+
+  void DownloadProgress(int crx_context);
+
+  int GetInterceptorCount() { return interceptor_count_; }
+
+  void AddResponse(const GURL& url,
+                   const base::FilePath& file_path,
+                   int net_error);
+
+ protected:
+  std::unique_ptr<CrxDownloader> crx_downloader_;
+
+  network::TestURLLoaderFactory test_url_loader_factory_;
+
+  CrxDownloader::DownloadCallback callback_;
+  CrxDownloader::ProgressCallback progress_callback_;
+
+  int crx_context_;
+
+  int num_download_complete_calls_;
+  CrxDownloader::Result download_complete_result_;
+
+  // These members are updated by DownloadProgress.
+  int num_progress_calls_;
+
+  // Accumulates the number of loads triggered.
+  int interceptor_count_ = 0;
+
+  // A magic value for the context to be used in the tests.
+  static const int kExpectedContext = 0xaabb;
+
+ private:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  scoped_refptr<network::SharedURLLoaderFactory>
+      test_shared_url_loader_factory_;
+  base::OnceClosure quit_closure_;
+};
+
+const int CrxDownloaderTest::kExpectedContext;
+
+CrxDownloaderTest::CrxDownloaderTest()
+    : callback_(base::BindOnce(&CrxDownloaderTest::DownloadComplete,
+                               base::Unretained(this),
+                               kExpectedContext)),
+      progress_callback_(base::Bind(&CrxDownloaderTest::DownloadProgress,
+                                    base::Unretained(this),
+                                    kExpectedContext)),
+      crx_context_(0),
+      num_download_complete_calls_(0),
+      num_progress_calls_(0),
+      scoped_task_environment_(
+          base::test::ScopedTaskEnvironment::MainThreadType::IO),
+      test_shared_url_loader_factory_(
+          base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+              &test_url_loader_factory_)) {}
+
+CrxDownloaderTest::~CrxDownloaderTest() {}
+
+void CrxDownloaderTest::SetUp() {
+  num_download_complete_calls_ = 0;
+  download_complete_result_ = CrxDownloader::Result();
+  num_progress_calls_ = 0;
+
+  // Do not use the background downloader in these tests.
+  crx_downloader_ = CrxDownloader::Create(
+      false, base::MakeRefCounted<NetworkFetcherChromiumFactory>(
+                 test_shared_url_loader_factory_));
+  crx_downloader_->set_progress_callback(progress_callback_);
+
+  test_url_loader_factory_.SetInterceptor(base::BindLambdaForTesting(
+      [&](const network::ResourceRequest& request) { interceptor_count_++; }));
+}
+
+void CrxDownloaderTest::TearDown() {
+  crx_downloader_.reset();
+}
+
+void CrxDownloaderTest::Quit() {
+  if (!quit_closure_.is_null())
+    std::move(quit_closure_).Run();
+}
+
+void CrxDownloaderTest::DownloadComplete(int crx_context,
+                                         const CrxDownloader::Result& result) {
+  ++num_download_complete_calls_;
+  crx_context_ = crx_context;
+  download_complete_result_ = result;
+  Quit();
+}
+
+void CrxDownloaderTest::DownloadProgress(int crx_context) {
+  ++num_progress_calls_;
+}
+
+void CrxDownloaderTest::AddResponse(const GURL& url,
+                                    const base::FilePath& file_path,
+                                    int net_error) {
+  if (net_error == net::OK) {
+    std::string data;
+    EXPECT_TRUE(base::ReadFileToString(file_path, &data));
+    network::ResourceResponseHead head;
+    head.content_length = data.size();
+    network::URLLoaderCompletionStatus status(net_error);
+    status.decoded_body_length = data.size();
+    test_url_loader_factory_.AddResponse(url, head, data, status);
+    return;
+  }
+
+  EXPECT_NE(net_error, net::OK);
+  test_url_loader_factory_.AddResponse(
+      url, network::ResourceResponseHead(), std::string(),
+      network::URLLoaderCompletionStatus(net_error));
+}
+
+void CrxDownloaderTest::RunThreads() {
+  base::RunLoop runloop;
+  quit_closure_ = runloop.QuitClosure();
+  runloop.Run();
+
+  // Since some tests need to drain currently enqueued tasks such as network
+  // intercepts on the IO thread, run the threads until they are
+  // idle. The component updater service won't loop again until the loop count
+  // is set and the service is started.
+  RunThreadsUntilIdle();
+}
+
+void CrxDownloaderTest::RunThreadsUntilIdle() {
+  scoped_task_environment_.RunUntilIdle();
+  base::RunLoop().RunUntilIdle();
+}
+
+// Tests that starting a download without a url results in an error.
+TEST_F(CrxDownloaderTest, NoUrl) {
+  std::vector<GURL> urls;
+  crx_downloader_->StartDownload(urls, std::string("abcd"),
+                                 std::move(callback_));
+  RunThreadsUntilIdle();
+
+  EXPECT_EQ(1, num_download_complete_calls_);
+  EXPECT_EQ(kExpectedContext, crx_context_);
+  EXPECT_EQ(static_cast<int>(CrxDownloaderError::NO_URL),
+            download_complete_result_.error);
+  EXPECT_TRUE(download_complete_result_.response.empty());
+  EXPECT_EQ(0, num_progress_calls_);
+}
+
+// Tests that starting a download without providing a hash results in an error.
+TEST_F(CrxDownloaderTest, NoHash) {
+  std::vector<GURL> urls(1, GURL("http://somehost/somefile"));
+
+  crx_downloader_->StartDownload(urls, std::string(), std::move(callback_));
+  RunThreadsUntilIdle();
+
+  EXPECT_EQ(1, num_download_complete_calls_);
+  EXPECT_EQ(kExpectedContext, crx_context_);
+  EXPECT_EQ(static_cast<int>(CrxDownloaderError::NO_HASH),
+            download_complete_result_.error);
+  EXPECT_TRUE(download_complete_result_.response.empty());
+  EXPECT_EQ(0, num_progress_calls_);
+}
+
+// Tests that downloading from one url is successful.
+TEST_F(CrxDownloaderTest, OneUrl) {
+  const GURL expected_crx_url =
+      GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+
+  const base::FilePath test_file(MakeTestFilePath(kTestFileName));
+  AddResponse(expected_crx_url, test_file, net::OK);
+
+  crx_downloader_->StartDownloadFromUrl(
+      expected_crx_url, std::string(hash_jebg), std::move(callback_));
+  RunThreads();
+
+  EXPECT_EQ(1, GetInterceptorCount());
+
+  EXPECT_EQ(1, num_download_complete_calls_);
+  EXPECT_EQ(kExpectedContext, crx_context_);
+  EXPECT_EQ(0, download_complete_result_.error);
+  EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
+
+  EXPECT_TRUE(
+      DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
+
+  EXPECT_LE(1, num_progress_calls_);
+}
+
+// Tests that downloading from one url fails if the actual hash of the file
+// does not match the expected hash.
+TEST_F(CrxDownloaderTest, OneUrlBadHash) {
+  const GURL expected_crx_url =
+      GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+
+  const base::FilePath test_file(MakeTestFilePath(kTestFileName));
+  AddResponse(expected_crx_url, test_file, net::OK);
+
+  crx_downloader_->StartDownloadFromUrl(
+      expected_crx_url,
+      std::string(
+          "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9"),
+      std::move(callback_));
+  RunThreads();
+
+  EXPECT_EQ(1, GetInterceptorCount());
+
+  EXPECT_EQ(1, num_download_complete_calls_);
+  EXPECT_EQ(kExpectedContext, crx_context_);
+  EXPECT_EQ(static_cast<int>(CrxDownloaderError::BAD_HASH),
+            download_complete_result_.error);
+  EXPECT_TRUE(download_complete_result_.response.empty());
+
+  EXPECT_LE(1, num_progress_calls_);
+}
+
+// Tests that specifying two urls has no side effects. Expect a successful
+// download, and only one download request be made.
+TEST_F(CrxDownloaderTest, TwoUrls) {
+  const GURL expected_crx_url =
+      GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+
+  const base::FilePath test_file(MakeTestFilePath(kTestFileName));
+  AddResponse(expected_crx_url, test_file, net::OK);
+
+  std::vector<GURL> urls;
+  urls.push_back(expected_crx_url);
+  urls.push_back(expected_crx_url);
+
+  crx_downloader_->StartDownload(urls, std::string(hash_jebg),
+                                 std::move(callback_));
+  RunThreads();
+
+  EXPECT_EQ(1, GetInterceptorCount());
+
+  EXPECT_EQ(1, num_download_complete_calls_);
+  EXPECT_EQ(kExpectedContext, crx_context_);
+  EXPECT_EQ(0, download_complete_result_.error);
+  EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
+
+  EXPECT_TRUE(
+      DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
+
+  EXPECT_LE(1, num_progress_calls_);
+}
+
+// Tests that the fallback to a valid url is successful.
+TEST_F(CrxDownloaderTest, TwoUrls_FirstInvalid) {
+  const GURL expected_crx_url =
+      GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+  const GURL no_file_url =
+      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc.crx");
+
+  const base::FilePath test_file(MakeTestFilePath(kTestFileName));
+  AddResponse(expected_crx_url, test_file, net::OK);
+  AddResponse(no_file_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
+
+  std::vector<GURL> urls;
+  urls.push_back(no_file_url);
+  urls.push_back(expected_crx_url);
+
+  crx_downloader_->StartDownload(urls, std::string(hash_jebg),
+                                 std::move(callback_));
+  RunThreads();
+
+  EXPECT_EQ(2, GetInterceptorCount());
+
+  EXPECT_EQ(1, num_download_complete_calls_);
+  EXPECT_EQ(kExpectedContext, crx_context_);
+  EXPECT_EQ(0, download_complete_result_.error);
+  EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
+
+  EXPECT_TRUE(
+      DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
+
+  // Expect at least some progress reported by the loader.
+  EXPECT_LE(1, num_progress_calls_);
+
+  const auto download_metrics = crx_downloader_->download_metrics();
+  ASSERT_EQ(2u, download_metrics.size());
+  EXPECT_EQ(no_file_url, download_metrics[0].url);
+  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[0].error);
+  EXPECT_EQ(-1, download_metrics[0].downloaded_bytes);
+  EXPECT_EQ(-1, download_metrics[0].total_bytes);
+  EXPECT_EQ(expected_crx_url, download_metrics[1].url);
+  EXPECT_EQ(0, download_metrics[1].error);
+  EXPECT_EQ(1843, download_metrics[1].downloaded_bytes);
+  EXPECT_EQ(1843, download_metrics[1].total_bytes);
+}
+
+// Tests that the download succeeds if the first url is correct and the
+// second bad url does not have a side-effect.
+TEST_F(CrxDownloaderTest, TwoUrls_SecondInvalid) {
+  const GURL expected_crx_url =
+      GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+  const GURL no_file_url =
+      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc.crx");
+
+  const base::FilePath test_file(MakeTestFilePath(kTestFileName));
+  AddResponse(expected_crx_url, test_file, net::OK);
+  AddResponse(no_file_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
+
+  std::vector<GURL> urls;
+  urls.push_back(expected_crx_url);
+  urls.push_back(no_file_url);
+
+  crx_downloader_->StartDownload(urls, std::string(hash_jebg),
+                                 std::move(callback_));
+  RunThreads();
+
+  EXPECT_EQ(1, GetInterceptorCount());
+
+  EXPECT_EQ(1, num_download_complete_calls_);
+  EXPECT_EQ(kExpectedContext, crx_context_);
+  EXPECT_EQ(0, download_complete_result_.error);
+  EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
+
+  EXPECT_TRUE(
+      DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
+
+  EXPECT_LE(1, num_progress_calls_);
+
+  EXPECT_EQ(1u, crx_downloader_->download_metrics().size());
+}
+
+// Tests that the download fails if both urls don't serve content.
+TEST_F(CrxDownloaderTest, TwoUrls_BothInvalid) {
+  const GURL expected_crx_url =
+      GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+
+  AddResponse(expected_crx_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
+
+  std::vector<GURL> urls;
+  urls.push_back(expected_crx_url);
+  urls.push_back(expected_crx_url);
+
+  crx_downloader_->StartDownload(urls, std::string(hash_jebg),
+                                 std::move(callback_));
+  RunThreads();
+
+  EXPECT_EQ(2, GetInterceptorCount());
+
+  EXPECT_EQ(1, num_download_complete_calls_);
+  EXPECT_EQ(kExpectedContext, crx_context_);
+  EXPECT_NE(0, download_complete_result_.error);
+  EXPECT_TRUE(download_complete_result_.response.empty());
+
+  const auto download_metrics = crx_downloader_->download_metrics();
+  ASSERT_EQ(2u, download_metrics.size());
+  EXPECT_EQ(expected_crx_url, download_metrics[0].url);
+  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[0].error);
+  EXPECT_EQ(-1, download_metrics[0].downloaded_bytes);
+  EXPECT_EQ(-1, download_metrics[0].total_bytes);
+  EXPECT_EQ(expected_crx_url, download_metrics[1].url);
+  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[1].error);
+  EXPECT_EQ(-1, download_metrics[1].downloaded_bytes);
+  EXPECT_EQ(-1, download_metrics[1].total_bytes);
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/crx_update_item.h b/src/components/update_client/crx_update_item.h
new file mode 100644
index 0000000..ac645b5
--- /dev/null
+++ b/src/components/update_client/crx_update_item.h
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_CRX_UPDATE_ITEM_H_
+#define COMPONENTS_UPDATE_CLIENT_CRX_UPDATE_ITEM_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "base/version.h"
+#include "components/update_client/crx_downloader.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+
+namespace update_client {
+
+struct CrxUpdateItem {
+  CrxUpdateItem();
+  CrxUpdateItem(const CrxUpdateItem& other);
+  ~CrxUpdateItem();
+
+  ComponentState state;
+
+  std::string id;
+
+  // The value of this data member is provided to the |UpdateClient| by the
+  // caller by responding to the |CrxDataCallback|. If the caller can't
+  // provide this value, for instance, in cases where the CRX was uninstalled,
+  // then the |component| member will not be present.
+  base::Optional<CrxComponent> component;
+
+  // Time when an update check for this CRX has happened.
+  base::TimeTicks last_check;
+
+  base::Version next_version;
+  std::string next_fp;
+
+  ErrorCategory error_category = ErrorCategory::kNone;
+  int error_code = 0;
+  int extra_code1 = 0;
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_CRX_UPDATE_ITEM_H_
diff --git a/src/components/update_client/net/network_chromium.h b/src/components/update_client/net/network_chromium.h
new file mode 100644
index 0000000..e8ac9bb
--- /dev/null
+++ b/src/components/update_client/net/network_chromium.h
@@ -0,0 +1,39 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_NET_NETWORK_CHROMIUM_H_
+#define COMPONENTS_UPDATE_CLIENT_NET_NETWORK_CHROMIUM_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/network.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace update_client {
+
+class NetworkFetcherChromiumFactory : public NetworkFetcherFactory {
+ public:
+  explicit NetworkFetcherChromiumFactory(
+      scoped_refptr<network::SharedURLLoaderFactory>
+          shared_url_network_factory);
+
+  std::unique_ptr<NetworkFetcher> Create() const override;
+
+ protected:
+  ~NetworkFetcherChromiumFactory() override;
+
+ private:
+  scoped_refptr<network::SharedURLLoaderFactory> shared_url_network_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkFetcherChromiumFactory);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_NET_NETWORK_CHROMIUM_H_
diff --git a/src/components/update_client/net/network_impl.cc b/src/components/update_client/net/network_impl.cc
new file mode 100644
index 0000000..c78853e
--- /dev/null
+++ b/src/components/update_client/net/network_impl.cc
@@ -0,0 +1,207 @@
+// Copyright 2019 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 "components/update_client/net/network_impl.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/numerics/safe_conversions.h"
+#include "components/update_client/net/network_chromium.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_response_headers.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_response.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "url/gurl.h"
+
+namespace {
+
+const net::NetworkTrafficAnnotationTag traffic_annotation =
+    net::DefineNetworkTrafficAnnotation("update_client", R"(
+        semantics {
+          sender: "Component Updater and Extension Updater"
+          description:
+            "This network module is used by both the component and the "
+            "extension updaters in Chrome. "
+            "The component updater is responsible for updating code and data "
+            "modules such as Flash, CrlSet, Origin Trials, etc. These modules "
+            "are updated on cycles independent of the Chrome release tracks. "
+            "It runs in the browser process and communicates with a set of "
+            "servers using the Omaha protocol to find the latest versions of "
+            "components, download them, and register them with the rest of "
+            "Chrome. "
+            "The extension updater works similarly, but it updates user "
+            "extensions instead of Chrome components. "
+          trigger: "Manual or automatic software updates."
+          data:
+            "Various OS and Chrome parameters such as version, bitness, "
+            "release tracks, etc. The component and the extension ids are also "
+            "present in both the request and the response from the servers. "
+            "The URL that refers to a component CRX payload is obfuscated for "
+            "most components."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: NO
+          setting: "This feature cannot be disabled."
+          chrome_policy {
+            ComponentUpdatesEnabled {
+              policy_options {mode: MANDATORY}
+              ComponentUpdatesEnabled: false
+            }
+          }
+        })");
+
+// Returns the string value of a header of the server response or an empty
+// string if the header is not available. Only the first header is returned
+// if multiple instances of the same header are present.
+std::string GetStringHeader(const network::SimpleURLLoader* simple_url_loader,
+                            const char* header_name) {
+  DCHECK(simple_url_loader);
+
+  const auto* response_info = simple_url_loader->ResponseInfo();
+  if (!response_info || !response_info->headers)
+    return {};
+
+  std::string header_value;
+  return response_info->headers->EnumerateHeader(nullptr, header_name,
+                                                 &header_value)
+             ? header_value
+             : std::string{};
+}
+
+// Returns the integral value of a header of the server response or -1 if
+// if the header is not available or a conversion error has occured.
+int64_t GetInt64Header(const network::SimpleURLLoader* simple_url_loader,
+                       const char* header_name) {
+  DCHECK(simple_url_loader);
+
+  const auto* response_info = simple_url_loader->ResponseInfo();
+  if (!response_info || !response_info->headers)
+    return -1;
+
+  return response_info->headers->GetInt64HeaderValue(header_name);
+}
+
+}  // namespace
+
+namespace update_client {
+
+NetworkFetcherImpl::NetworkFetcherImpl(
+    scoped_refptr<network::SharedURLLoaderFactory> shared_url_network_factory)
+    : shared_url_network_factory_(shared_url_network_factory) {}
+NetworkFetcherImpl::~NetworkFetcherImpl() = default;
+
+void NetworkFetcherImpl::PostRequest(
+    const GURL& url,
+    const std::string& post_data,
+    const base::flat_map<std::string, std::string>& post_additional_headers,
+    ResponseStartedCallback response_started_callback,
+    ProgressCallback progress_callback,
+    PostRequestCompleteCallback post_request_complete_callback) {
+  DCHECK(!simple_url_loader_);
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = url;
+  resource_request->method = "POST";
+  resource_request->load_flags = net::LOAD_DO_NOT_SEND_COOKIES |
+                                 net::LOAD_DO_NOT_SAVE_COOKIES |
+                                 net::LOAD_DISABLE_CACHE;
+  for (const auto& header : post_additional_headers)
+    resource_request->headers.SetHeader(header.first, header.second);
+  simple_url_loader_ = network::SimpleURLLoader::Create(
+      std::move(resource_request), traffic_annotation);
+  simple_url_loader_->SetRetryOptions(
+      kMaxRetriesOnNetworkChange,
+      network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
+  simple_url_loader_->AttachStringForUpload(post_data, "application/json");
+  simple_url_loader_->SetOnResponseStartedCallback(base::BindOnce(
+      &NetworkFetcherImpl::OnResponseStartedCallback, base::Unretained(this),
+      std::move(response_started_callback)));
+  simple_url_loader_->SetOnDownloadProgressCallback(base::BindRepeating(
+      &NetworkFetcherImpl::OnProgressCallback, base::Unretained(this),
+      std::move(progress_callback)));
+  constexpr size_t kMaxResponseSize = 1024 * 1024;
+  simple_url_loader_->DownloadToString(
+      shared_url_network_factory_.get(),
+      base::BindOnce(
+          [](const network::SimpleURLLoader* simple_url_loader,
+             PostRequestCompleteCallback post_request_complete_callback,
+             std::unique_ptr<std::string> response_body) {
+            std::move(post_request_complete_callback)
+                .Run(std::move(response_body), simple_url_loader->NetError(),
+                     GetStringHeader(simple_url_loader, kHeaderEtag),
+                     GetInt64Header(simple_url_loader, kHeaderXRetryAfter));
+          },
+          simple_url_loader_.get(), std::move(post_request_complete_callback)),
+      kMaxResponseSize);
+}
+
+void NetworkFetcherImpl::DownloadToFile(
+    const GURL& url,
+    const base::FilePath& file_path,
+    ResponseStartedCallback response_started_callback,
+    ProgressCallback progress_callback,
+    DownloadToFileCompleteCallback download_to_file_complete_callback) {
+  DCHECK(!simple_url_loader_);
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = url;
+  resource_request->method = "GET";
+  resource_request->load_flags = net::LOAD_DO_NOT_SEND_COOKIES |
+                                 net::LOAD_DO_NOT_SAVE_COOKIES |
+                                 net::LOAD_DISABLE_CACHE;
+  simple_url_loader_ = network::SimpleURLLoader::Create(
+      std::move(resource_request), traffic_annotation);
+  simple_url_loader_->SetRetryOptions(
+      kMaxRetriesOnNetworkChange,
+      network::SimpleURLLoader::RetryMode::RETRY_ON_NETWORK_CHANGE);
+  simple_url_loader_->SetAllowPartialResults(true);
+  simple_url_loader_->SetOnResponseStartedCallback(base::BindOnce(
+      &NetworkFetcherImpl::OnResponseStartedCallback, base::Unretained(this),
+      std::move(response_started_callback)));
+  simple_url_loader_->SetOnDownloadProgressCallback(base::BindRepeating(
+      &NetworkFetcherImpl::OnProgressCallback, base::Unretained(this),
+      std::move(progress_callback)));
+  simple_url_loader_->DownloadToFile(
+      shared_url_network_factory_.get(),
+      base::BindOnce(
+          [](const network::SimpleURLLoader* simple_url_loader,
+             DownloadToFileCompleteCallback download_to_file_complete_callback,
+             base::FilePath file_path) {
+            std::move(download_to_file_complete_callback)
+                .Run(file_path, simple_url_loader->NetError(),
+                     simple_url_loader->GetContentSize());
+          },
+          simple_url_loader_.get(),
+          std::move(download_to_file_complete_callback)),
+      file_path);
+}
+
+void NetworkFetcherImpl::OnResponseStartedCallback(
+    ResponseStartedCallback response_started_callback,
+    const GURL& final_url,
+    const network::ResourceResponseHead& response_head) {
+  std::move(response_started_callback)
+      .Run(final_url,
+           response_head.headers ? response_head.headers->response_code() : -1,
+           response_head.content_length);
+}
+
+void NetworkFetcherImpl::OnProgressCallback(ProgressCallback progress_callback,
+                                            uint64_t current) {
+  progress_callback.Run(base::saturated_cast<int64_t>(current));
+}
+
+NetworkFetcherChromiumFactory::NetworkFetcherChromiumFactory(
+    scoped_refptr<network::SharedURLLoaderFactory> shared_url_network_factory)
+    : shared_url_network_factory_(shared_url_network_factory) {}
+
+NetworkFetcherChromiumFactory::~NetworkFetcherChromiumFactory() = default;
+
+std::unique_ptr<NetworkFetcher> NetworkFetcherChromiumFactory::Create() const {
+  return std::make_unique<NetworkFetcherImpl>(shared_url_network_factory_);
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/net/network_impl.h b/src/components/update_client/net/network_impl.h
new file mode 100644
index 0000000..f28cf2f
--- /dev/null
+++ b/src/components/update_client/net/network_impl.h
@@ -0,0 +1,65 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_NET_NETWORK_IMPL_H_
+#define COMPONENTS_UPDATE_CLIENT_NET_NETWORK_IMPL_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/network.h"
+
+namespace network {
+struct ResourceResponseHead;
+class SharedURLLoaderFactory;
+class SimpleURLLoader;
+}  // namespace network
+
+namespace update_client {
+
+class NetworkFetcherImpl : public NetworkFetcher {
+ public:
+  explicit NetworkFetcherImpl(scoped_refptr<network::SharedURLLoaderFactory>
+                                  shared_url_network_factory);
+  ~NetworkFetcherImpl() override;
+
+  // NetworkFetcher overrides.
+  void PostRequest(
+      const GURL& url,
+      const std::string& post_data,
+      const base::flat_map<std::string, std::string>& post_additional_headers,
+      ResponseStartedCallback response_started_callback,
+      ProgressCallback progress_callback,
+      PostRequestCompleteCallback post_request_complete_callback) override;
+  void DownloadToFile(const GURL& url,
+                      const base::FilePath& file_path,
+                      ResponseStartedCallback response_started_callback,
+                      ProgressCallback progress_callback,
+                      DownloadToFileCompleteCallback
+                          download_to_file_complete_callback) override;
+
+ private:
+  void OnResponseStartedCallback(
+      ResponseStartedCallback response_started_callback,
+      const GURL& final_url,
+      const network::ResourceResponseHead& response_head);
+
+  void OnProgressCallback(ProgressCallback response_started_callback,
+                          uint64_t current);
+
+  static constexpr int kMaxRetriesOnNetworkChange = 3;
+
+  scoped_refptr<network::SharedURLLoaderFactory> shared_url_network_factory_;
+  std::unique_ptr<network::SimpleURLLoader> simple_url_loader_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkFetcherImpl);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_NET_NETWORK_IMPL_H_
diff --git a/src/components/update_client/net/url_loader_post_interceptor.cc b/src/components/update_client/net/url_loader_post_interceptor.cc
new file mode 100644
index 0000000..11a2af8
--- /dev/null
+++ b/src/components/update_client/net/url_loader_post_interceptor.cc
@@ -0,0 +1,260 @@
+// Copyright 2018 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 "components/update_client/net/url_loader_post_interceptor.h"
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/bind_test_util.h"
+#include "components/update_client/test_configurator.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+URLLoaderPostInterceptor::URLLoaderPostInterceptor(
+    network::TestURLLoaderFactory* url_loader_factory)
+    : url_loader_factory_(url_loader_factory) {
+  filtered_urls_.push_back(
+      GURL(base::StringPrintf("%s://%s%s", POST_INTERCEPT_SCHEME,
+                              POST_INTERCEPT_HOSTNAME, POST_INTERCEPT_PATH)));
+  InitializeWithInterceptor();
+}
+
+URLLoaderPostInterceptor::URLLoaderPostInterceptor(
+    std::vector<GURL> supported_urls,
+    network::TestURLLoaderFactory* url_loader_factory)
+    : url_loader_factory_(url_loader_factory) {
+  DCHECK_LT(0u, supported_urls.size());
+  filtered_urls_.swap(supported_urls);
+  InitializeWithInterceptor();
+}
+
+URLLoaderPostInterceptor::URLLoaderPostInterceptor(
+    std::vector<GURL> supported_urls,
+    net::test_server::EmbeddedTestServer* embedded_test_server)
+    : embedded_test_server_(embedded_test_server) {
+  DCHECK_LT(0u, supported_urls.size());
+  filtered_urls_.swap(supported_urls);
+  InitializeWithRequestHandler();
+}
+
+URLLoaderPostInterceptor::~URLLoaderPostInterceptor() {}
+
+bool URLLoaderPostInterceptor::ExpectRequest(
+    std::unique_ptr<RequestMatcher> request_matcher) {
+  return ExpectRequest(std::move(request_matcher), net::HTTP_OK);
+}
+
+bool URLLoaderPostInterceptor::ExpectRequest(
+    std::unique_ptr<RequestMatcher> request_matcher,
+    net::HttpStatusCode response_code) {
+  expectations_.push(
+      {std::move(request_matcher), ExpectationResponse(response_code, "")});
+  return true;
+}
+
+bool URLLoaderPostInterceptor::ExpectRequest(
+    std::unique_ptr<RequestMatcher> request_matcher,
+    const base::FilePath& filepath) {
+  std::string response;
+  if (filepath.empty() || !base::ReadFileToString(filepath, &response))
+    return false;
+
+  expectations_.push({std::move(request_matcher),
+                      ExpectationResponse(net::HTTP_OK, response)});
+  return true;
+}
+
+// Returns how many requests have been intercepted and matched by
+// an expectation. One expectation can only be matched by one request.
+int URLLoaderPostInterceptor::GetHitCount() const {
+  return hit_count_;
+}
+
+// Returns how many requests in total have been captured by the interceptor.
+int URLLoaderPostInterceptor::GetCount() const {
+  return static_cast<int>(requests_.size());
+}
+
+// Returns all requests that have been intercepted, matched or not.
+std::vector<URLLoaderPostInterceptor::InterceptedRequest>
+URLLoaderPostInterceptor::GetRequests() const {
+  return requests_;
+}
+
+// Return the body of the n-th request, zero-based.
+std::string URLLoaderPostInterceptor::GetRequestBody(size_t n) const {
+  return std::get<0>(requests_[n]);
+}
+
+// Returns the joined bodies of all requests for debugging purposes.
+std::string URLLoaderPostInterceptor::GetRequestsAsString() const {
+  const std::vector<InterceptedRequest> requests = GetRequests();
+  std::string s = "Requests are:";
+  int i = 0;
+  for (auto it = requests.cbegin(); it != requests.cend(); ++it)
+    s.append(base::StringPrintf("\n  [%d]: %s", ++i, std::get<0>(*it).c_str()));
+  return s;
+}
+
+// Resets the state of the interceptor so that new expectations can be set.
+void URLLoaderPostInterceptor::Reset() {
+  hit_count_ = 0;
+  requests_.clear();
+  base::queue<Expectation>().swap(expectations_);
+}
+
+void URLLoaderPostInterceptor::Pause() {
+  is_paused_ = true;
+}
+
+void URLLoaderPostInterceptor::Resume() {
+  is_paused_ = false;
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindLambdaForTesting([&]() {
+        if (!pending_expectations_.size())
+          return;
+
+        PendingExpectation expectation =
+            std::move(pending_expectations_.front());
+        pending_expectations_.pop();
+        url_loader_factory_->AddResponse(expectation.first.spec(),
+                                         expectation.second.response_body,
+                                         expectation.second.response_code);
+      }));
+}
+
+void URLLoaderPostInterceptor::url_job_request_ready_callback(
+    UrlJobRequestReadyCallback url_job_request_ready_callback) {
+  url_job_request_ready_callback_ = std::move(url_job_request_ready_callback);
+}
+
+int URLLoaderPostInterceptor::GetHitCountForURL(const GURL& url) {
+  int hit_count = 0;
+  const std::vector<InterceptedRequest> requests = GetRequests();
+  for (auto it = requests.cbegin(); it != requests.cend(); ++it) {
+    GURL url_no_query = std::get<2>(*it);
+    if (url_no_query.has_query()) {
+      GURL::Replacements replacements;
+      replacements.ClearQuery();
+      url_no_query = url_no_query.ReplaceComponents(replacements);
+    }
+    if (url_no_query == url)
+      hit_count++;
+  }
+  return hit_count;
+}
+
+void URLLoaderPostInterceptor::InitializeWithInterceptor() {
+  DCHECK(url_loader_factory_);
+  url_loader_factory_->SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        GURL url = request.url;
+        if (url.has_query()) {
+          GURL::Replacements replacements;
+          replacements.ClearQuery();
+          url = url.ReplaceComponents(replacements);
+        }
+        auto it = std::find_if(
+            filtered_urls_.begin(), filtered_urls_.end(),
+            [url](const GURL& filtered_url) { return filtered_url == url; });
+        if (it == filtered_urls_.end())
+          return;
+
+        std::string request_body = network::GetUploadData(request);
+        requests_.push_back({request_body, request.headers, request.url});
+        if (expectations_.empty())
+          return;
+        const auto& expectation = expectations_.front();
+        if (expectation.first->Match(request_body)) {
+          const net::HttpStatusCode response_code(
+              expectation.second.response_code);
+          const std::string response_body(expectation.second.response_body);
+
+          if (url_job_request_ready_callback_) {
+            base::ThreadTaskRunnerHandle::Get()->PostTask(
+                FROM_HERE, std::move(url_job_request_ready_callback_));
+          }
+
+          if (!is_paused_) {
+            url_loader_factory_->AddResponse(request.url.spec(), response_body,
+                                             response_code);
+          } else {
+            pending_expectations_.push({request.url, expectation.second});
+          }
+          expectations_.pop();
+          ++hit_count_;
+        }
+      }));
+}
+
+void URLLoaderPostInterceptor::InitializeWithRequestHandler() {
+  DCHECK(embedded_test_server_);
+  DCHECK(!url_loader_factory_);
+  embedded_test_server_->RegisterRequestHandler(base::BindRepeating(
+      &URLLoaderPostInterceptor::RequestHandler, base::Unretained(this)));
+}
+
+std::unique_ptr<net::test_server::HttpResponse>
+URLLoaderPostInterceptor::RequestHandler(
+    const net::test_server::HttpRequest& request) {
+  // Only intercepts POST.
+  if (request.method != net::test_server::METHOD_POST)
+    return nullptr;
+
+  GURL url = request.GetURL();
+  if (url.has_query()) {
+    GURL::Replacements replacements;
+    replacements.ClearQuery();
+    url = url.ReplaceComponents(replacements);
+  }
+  auto it = std::find_if(
+      filtered_urls_.begin(), filtered_urls_.end(),
+      [url](const GURL& filtered_url) { return filtered_url == url; });
+  if (it == filtered_urls_.end())
+    return nullptr;
+
+  std::string request_body = request.content;
+  net::HttpRequestHeaders headers;
+  for (auto it : request.headers)
+    headers.SetHeader(it.first, it.second);
+  requests_.push_back({request_body, headers, url});
+  if (expectations_.empty())
+    return nullptr;
+
+  const auto& expectation = expectations_.front();
+  if (expectation.first->Match(request_body)) {
+    const net::HttpStatusCode response_code(expectation.second.response_code);
+    const std::string response_body(expectation.second.response_body);
+    expectations_.pop();
+    ++hit_count_;
+
+    std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+        new net::test_server::BasicHttpResponse);
+    http_response->set_code(response_code);
+    http_response->set_content(response_body);
+    return http_response;
+  }
+
+  return nullptr;
+}
+
+bool PartialMatch::Match(const std::string& actual) const {
+  return actual.find(expected_) != std::string::npos;
+}
+
+bool AnyMatch::Match(const std::string& actual) const {
+  return true;
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/net/url_loader_post_interceptor.h b/src/components/update_client/net/url_loader_post_interceptor.h
new file mode 100644
index 0000000..9907f49
--- /dev/null
+++ b/src/components/update_client/net/url_loader_post_interceptor.h
@@ -0,0 +1,181 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_NET_URL_LOADER_POST_INTERCEPTOR_H_
+#define COMPONENTS_UPDATE_CLIENT_NET_URL_LOADER_POST_INTERCEPTOR_H_
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/queue.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_status_code.h"
+#include "url/gurl.h"
+
+namespace network {
+class TestURLLoaderFactory;
+}
+
+namespace net {
+namespace test_server {
+class EmbeddedTestServer;
+class HttpResponse;
+struct HttpRequest;
+}  // namespace test_server
+}  // namespace net
+
+namespace update_client {
+
+// Intercepts requests to a file path, counts them, and captures the body of
+// the requests. Optionally, for each request, it can return a canned response
+// from a given file. The class maintains a queue of expectations, and returns
+// one and only one response for each request that matches the expectation.
+// Then, the expectation is removed from the queue.
+class URLLoaderPostInterceptor {
+ public:
+  using InterceptedRequest =
+      std::tuple<std::string, net::HttpRequestHeaders, GURL>;
+
+  // Called when the load associated with the url request is intercepted
+  // by this object:.
+  using UrlJobRequestReadyCallback = base::OnceCallback<void()>;
+
+  // Allows a generic string maching interface when setting up expectations.
+  class RequestMatcher {
+   public:
+    virtual bool Match(const std::string& actual) const = 0;
+    virtual ~RequestMatcher() {}
+  };
+
+  explicit URLLoaderPostInterceptor(
+      network::TestURLLoaderFactory* url_loader_factory);
+  URLLoaderPostInterceptor(std::vector<GURL> supported_urls,
+                           network::TestURLLoaderFactory* url_loader_factory);
+  URLLoaderPostInterceptor(std::vector<GURL> supported_urls,
+                           net::test_server::EmbeddedTestServer*);
+
+  ~URLLoaderPostInterceptor();
+
+  // Sets an expection for the body of the POST request and optionally,
+  // provides a canned response identified by a |file_path| to be returned when
+  // the expectation is met. If no |file_path| is provided, then an empty
+  // response body is served. If |response_code| is provided, then an empty
+  // response body with that response code is returned.
+  // Returns |true| if the expectation was set.
+  bool ExpectRequest(std::unique_ptr<RequestMatcher> request_matcher);
+
+  bool ExpectRequest(std::unique_ptr<RequestMatcher> request_matcher,
+                     net::HttpStatusCode response_code);
+
+  bool ExpectRequest(std::unique_ptr<RequestMatcher> request_matcher,
+                     const base::FilePath& filepath);
+
+  // Returns how many requests have been intercepted and matched by
+  // an expectation. One expectation can only be matched by one request.
+  int GetHitCount() const;
+
+  // Returns how many requests in total have been captured by the interceptor.
+  int GetCount() const;
+
+  // Returns all requests that have been intercepted, matched or not.
+  std::vector<InterceptedRequest> GetRequests() const;
+
+  // Return the body of the n-th request, zero-based.
+  std::string GetRequestBody(size_t n) const;
+
+  // Returns the joined bodies of all requests for debugging purposes.
+  std::string GetRequestsAsString() const;
+
+  // Resets the state of the interceptor so that new expectations can be set.
+  void Reset();
+
+  // Prevents the intercepted request from starting, as a way to simulate
+  // the effects of a very slow network. Call this function before the actual
+  // network request occurs.
+  void Pause();
+
+  // Allows a previously paused request to continue.
+  void Resume();
+
+  // Sets a callback to be invoked when the request job associated with
+  // an intercepted request is created. This allows the test execution to
+  // synchronize with network tasks running on the IO thread and avoid polling
+  // using idle run loops. A paused request can be resumed after this callback
+  // has been invoked.
+  void url_job_request_ready_callback(
+      UrlJobRequestReadyCallback url_job_request_ready_callback);
+
+  int GetHitCountForURL(const GURL& url);
+
+ private:
+  void InitializeWithInterceptor();
+  void InitializeWithRequestHandler();
+
+  std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+      const net::test_server::HttpRequest& request);
+
+  struct ExpectationResponse {
+    ExpectationResponse(net::HttpStatusCode code, const std::string& body)
+        : response_code(code), response_body(body) {}
+    const net::HttpStatusCode response_code;
+    const std::string response_body;
+  };
+  using Expectation =
+      std::pair<std::unique_ptr<RequestMatcher>, ExpectationResponse>;
+
+  using PendingExpectation = std::pair<GURL, ExpectationResponse>;
+
+  // Contains the count of the request matching expectations.
+  int hit_count_ = 0;
+
+  // Contains the request body and the extra headers of the intercepted
+  // requests.
+  std::vector<InterceptedRequest> requests_;
+
+  // Contains the expectations which this interceptor tries to match.
+  base::queue<Expectation> expectations_;
+
+  base::queue<PendingExpectation> pending_expectations_;
+
+  network::TestURLLoaderFactory* url_loader_factory_ = nullptr;
+  net::test_server::EmbeddedTestServer* embedded_test_server_ = nullptr;
+
+  bool is_paused_ = false;
+
+  std::vector<GURL> filtered_urls_;
+
+  UrlJobRequestReadyCallback url_job_request_ready_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLLoaderPostInterceptor);
+};
+
+class PartialMatch : public URLLoaderPostInterceptor::RequestMatcher {
+ public:
+  explicit PartialMatch(const std::string& expected) : expected_(expected) {}
+  bool Match(const std::string& actual) const override;
+
+ private:
+  const std::string expected_;
+
+  DISALLOW_COPY_AND_ASSIGN(PartialMatch);
+};
+
+class AnyMatch : public URLLoaderPostInterceptor::RequestMatcher {
+ public:
+  AnyMatch() = default;
+  bool Match(const std::string& actual) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AnyMatch);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_NET_URL_LOADER_POST_INTERCEPTOR_H_
diff --git a/src/components/update_client/network.cc b/src/components/update_client/network.cc
new file mode 100644
index 0000000..cb8e151
--- /dev/null
+++ b/src/components/update_client/network.cc
@@ -0,0 +1,12 @@
+// Copyright 2019 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 "components/update_client/network.h"
+
+namespace update_client {
+
+constexpr char NetworkFetcher::kHeaderEtag[];
+constexpr char NetworkFetcher::kHeaderXRetryAfter[];
+
+}  // namespace update_client
diff --git a/src/components/update_client/network.h b/src/components/update_client/network.h
new file mode 100644
index 0000000..daed162
--- /dev/null
+++ b/src/components/update_client/network.h
@@ -0,0 +1,89 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_NETWORK_H_
+#define COMPONENTS_UPDATE_CLIENT_NETWORK_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}  // namespace base
+
+namespace update_client {
+
+class NetworkFetcher {
+ public:
+  using PostRequestCompleteCallback =
+      base::OnceCallback<void(std::unique_ptr<std::string> response_body,
+                              int net_error,
+                              const std::string& header_etag,
+                              int64_t xheader_retry_after_sec)>;
+  using DownloadToFileCompleteCallback = base::OnceCallback<
+      void(base::FilePath path, int net_error, int64_t content_size)>;
+  using ResponseStartedCallback = base::OnceCallback<
+      void(const GURL& final_url, int response_code, int64_t content_length)>;
+  using ProgressCallback = base::RepeatingCallback<void(int64_t current)>;
+
+  // The ETag header carries the ECSDA signature of the POST response, if
+  // signing has been used.
+  static constexpr char kHeaderEtag[] = "ETag";
+
+  // The server uses the optional X-Retry-After header to indicate that the
+  // current request should not be attempted again.
+  //
+  // The value of the header is the number of seconds to wait before trying to
+  // do a subsequent update check. Only the values retrieved over HTTPS are
+  // trusted.
+  static constexpr char kHeaderXRetryAfter[] = "X-Retry-After";
+
+  virtual ~NetworkFetcher() = default;
+
+  virtual void PostRequest(
+      const GURL& url,
+      const std::string& post_data,
+      const base::flat_map<std::string, std::string>& post_additional_headers,
+      ResponseStartedCallback response_started_callback,
+      ProgressCallback progress_callback,
+      PostRequestCompleteCallback post_request_complete_callback) = 0;
+  virtual void DownloadToFile(
+      const GURL& url,
+      const base::FilePath& file_path,
+      ResponseStartedCallback response_started_callback,
+      ProgressCallback progress_callback,
+      DownloadToFileCompleteCallback download_to_file_complete_callback) = 0;
+
+ protected:
+  NetworkFetcher() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NetworkFetcher);
+};
+
+class NetworkFetcherFactory : public base::RefCounted<NetworkFetcherFactory> {
+ public:
+  virtual std::unique_ptr<NetworkFetcher> Create() const = 0;
+
+ protected:
+  friend class base::RefCounted<NetworkFetcherFactory>;
+  NetworkFetcherFactory() = default;
+  virtual ~NetworkFetcherFactory() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NetworkFetcherFactory);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_NETWORK_H_
diff --git a/src/components/update_client/patch/patch_impl.cc b/src/components/update_client/patch/patch_impl.cc
new file mode 100644
index 0000000..599101f
--- /dev/null
+++ b/src/components/update_client/patch/patch_impl.cc
@@ -0,0 +1,53 @@
+// Copyright 2019 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 "components/update_client/patch/patch_impl.h"
+
+#include "components/services/patch/public/cpp/patch.h"
+#include "components/update_client/component_patcher_operation.h"
+
+namespace update_client {
+
+namespace {
+
+class PatcherImpl : public Patcher {
+ public:
+  explicit PatcherImpl(PatchChromiumFactory::Callback callback)
+      : callback_(std::move(callback)) {}
+
+  void PatchBsdiff(const base::FilePath& old_file,
+                   const base::FilePath& patch_file,
+                   const base::FilePath& destination,
+                   PatchCompleteCallback callback) const override {
+    patch::Patch(callback_.Run(), update_client::kBsdiff, old_file, patch_file,
+                 destination, std::move(callback));
+  }
+
+  void PatchCourgette(const base::FilePath& old_file,
+                      const base::FilePath& patch_file,
+                      const base::FilePath& destination,
+                      PatchCompleteCallback callback) const override {
+    patch::Patch(callback_.Run(), update_client::kCourgette, old_file,
+                 patch_file, destination, std::move(callback));
+  }
+
+ protected:
+  ~PatcherImpl() override = default;
+
+ private:
+  const PatchChromiumFactory::Callback callback_;
+};
+
+}  // namespace
+
+PatchChromiumFactory::PatchChromiumFactory(Callback callback)
+    : callback_(std::move(callback)) {}
+
+scoped_refptr<Patcher> PatchChromiumFactory::Create() const {
+  return base::MakeRefCounted<PatcherImpl>(callback_);
+}
+
+PatchChromiumFactory::~PatchChromiumFactory() = default;
+
+}  // namespace update_client
diff --git a/src/components/update_client/patch/patch_impl.h b/src/components/update_client/patch/patch_impl.h
new file mode 100644
index 0000000..e76666d
--- /dev/null
+++ b/src/components/update_client/patch/patch_impl.h
@@ -0,0 +1,38 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_PATCH_PATCH_IMPL_H_
+#define COMPONENTS_UPDATE_CLIENT_PATCH_PATCH_IMPL_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/services/patch/public/mojom/file_patcher.mojom.h"
+#include "components/update_client/patcher.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+
+namespace update_client {
+
+class PatchChromiumFactory : public PatcherFactory {
+ public:
+  using Callback =
+      base::RepeatingCallback<mojo::PendingRemote<patch::mojom::FilePatcher>()>;
+  explicit PatchChromiumFactory(Callback callback);
+
+  scoped_refptr<Patcher> Create() const override;
+
+ protected:
+  ~PatchChromiumFactory() override;
+
+ private:
+  const Callback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(PatchChromiumFactory);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PATCH_PATCH_IMPL_H_
diff --git a/src/components/update_client/patcher.h b/src/components/update_client/patcher.h
new file mode 100644
index 0000000..1316d26
--- /dev/null
+++ b/src/components/update_client/patcher.h
@@ -0,0 +1,56 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_PATCHER_H_
+#define COMPONENTS_UPDATE_CLIENT_PATCHER_H_
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace base {
+class FilePath;
+}  // namespace base
+
+namespace update_client {
+
+class Patcher : public base::RefCountedThreadSafe<Patcher> {
+ public:
+  using PatchCompleteCallback = base::OnceCallback<void(int result)>;
+
+  virtual void PatchBsdiff(const base::FilePath& input_file,
+                           const base::FilePath& patch_file,
+                           const base::FilePath& destination,
+                           PatchCompleteCallback callback) const = 0;
+
+  virtual void PatchCourgette(const base::FilePath& input_file,
+                              const base::FilePath& patch_file,
+                              const base::FilePath& destination,
+                              PatchCompleteCallback callback) const = 0;
+
+ protected:
+  friend class base::RefCountedThreadSafe<Patcher>;
+  Patcher() = default;
+  virtual ~Patcher() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Patcher);
+};
+
+class PatcherFactory : public base::RefCountedThreadSafe<PatcherFactory> {
+ public:
+  virtual scoped_refptr<Patcher> Create() const = 0;
+
+ protected:
+  friend class base::RefCountedThreadSafe<PatcherFactory>;
+  PatcherFactory() = default;
+  virtual ~PatcherFactory() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PatcherFactory);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PATCHER_H_
diff --git a/src/components/update_client/persisted_data.cc b/src/components/update_client/persisted_data.cc
new file mode 100644
index 0000000..c14bb03
--- /dev/null
+++ b/src/components/update_client/persisted_data.cc
@@ -0,0 +1,195 @@
+// Copyright 2016 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 "components/update_client/persisted_data.h"
+
+#include <string>
+#include <vector>
+
+#include "base/guid.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_checker.h"
+#include "base/values.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/update_client/activity_data_service.h"
+
+const char kPersistedDataPreference[] = "updateclientdata";
+
+namespace update_client {
+
+PersistedData::PersistedData(PrefService* pref_service,
+                             ActivityDataService* activity_data_service)
+    : pref_service_(pref_service),
+      activity_data_service_(activity_data_service) {}
+
+PersistedData::~PersistedData() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+int PersistedData::GetInt(const std::string& id,
+                          const std::string& key,
+                          int fallback) const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // We assume ids do not contain '.' characters.
+  DCHECK_EQ(std::string::npos, id.find('.'));
+  if (!pref_service_)
+    return fallback;
+  const base::DictionaryValue* dict =
+      pref_service_->GetDictionary(kPersistedDataPreference);
+  if (!dict)
+    return fallback;
+  int result = 0;
+  return dict->GetInteger(
+             base::StringPrintf("apps.%s.%s", id.c_str(), key.c_str()), &result)
+             ? result
+             : fallback;
+}
+
+std::string PersistedData::GetString(const std::string& id,
+                                     const std::string& key) const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // We assume ids do not contain '.' characters.
+  DCHECK_EQ(std::string::npos, id.find('.'));
+  if (!pref_service_)
+    return std::string();
+  const base::DictionaryValue* dict =
+      pref_service_->GetDictionary(kPersistedDataPreference);
+  if (!dict)
+    return std::string();
+  std::string result;
+  return dict->GetString(
+             base::StringPrintf("apps.%s.%s", id.c_str(), key.c_str()), &result)
+             ? result
+             : std::string();
+}
+
+int PersistedData::GetDateLastRollCall(const std::string& id) const {
+  return GetInt(id, "dlrc", kDateUnknown);
+}
+
+int PersistedData::GetDateLastActive(const std::string& id) const {
+  return GetInt(id, "dla", kDateUnknown);
+}
+
+std::string PersistedData::GetPingFreshness(const std::string& id) const {
+  std::string result = GetString(id, "pf");
+  return !result.empty() ? base::StringPrintf("{%s}", result.c_str()) : result;
+}
+
+#if defined(OS_STARBOARD)
+std::string PersistedData::GetLastUnpackedVersion(const std::string& id) const {
+  return GetString(id, "version");
+}
+std::string PersistedData::GetUpdaterChannel(const std::string& id) const {
+  return GetString(id, "updaterchannel");
+}
+#endif
+
+std::string PersistedData::GetCohort(const std::string& id) const {
+  return GetString(id, "cohort");
+}
+
+std::string PersistedData::GetCohortName(const std::string& id) const {
+  return GetString(id, "cohortname");
+}
+
+std::string PersistedData::GetCohortHint(const std::string& id) const {
+  return GetString(id, "cohorthint");
+}
+
+void PersistedData::SetDateLastRollCall(const std::vector<std::string>& ids,
+                                        int datenum) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!pref_service_ || datenum < 0)
+    return;
+  DictionaryPrefUpdate update(pref_service_, kPersistedDataPreference);
+  for (const auto& id : ids) {
+    // We assume ids do not contain '.' characters.
+    DCHECK_EQ(std::string::npos, id.find('.'));
+    update->SetInteger(base::StringPrintf("apps.%s.dlrc", id.c_str()), datenum);
+    update->SetString(base::StringPrintf("apps.%s.pf", id.c_str()),
+                      base::GenerateGUID());
+  }
+}
+
+void PersistedData::SetDateLastActive(const std::vector<std::string>& ids,
+                                      int datenum) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!pref_service_ || datenum < 0)
+    return;
+  DictionaryPrefUpdate update(pref_service_, kPersistedDataPreference);
+  for (const auto& id : ids) {
+    if (GetActiveBit(id)) {
+      // We assume ids do not contain '.' characters.
+      DCHECK_EQ(std::string::npos, id.find('.'));
+      update->SetInteger(base::StringPrintf("apps.%s.dla", id.c_str()),
+                         datenum);
+      activity_data_service_->ClearActiveBit(id);
+    }
+  }
+}
+
+void PersistedData::SetString(const std::string& id,
+                              const std::string& key,
+                              const std::string& value) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!pref_service_)
+    return;
+  DictionaryPrefUpdate update(pref_service_, kPersistedDataPreference);
+  update->SetString(base::StringPrintf("apps.%s.%s", id.c_str(), key.c_str()),
+                    value);
+}
+
+#if defined(OS_STARBOARD)
+void PersistedData::SetLastUnpackedVersion(const std::string& id,
+                                           const std::string& version) {
+  SetString(id, "version", version);
+}
+void PersistedData::SetUpdaterChannel(const std::string& id,
+                                      const std::string& channel) {
+  SetString(id, "updaterchannel", channel);
+}
+#endif
+
+void PersistedData::SetCohort(const std::string& id,
+                              const std::string& cohort) {
+  SetString(id, "cohort", cohort);
+}
+
+void PersistedData::SetCohortName(const std::string& id,
+                                  const std::string& cohort_name) {
+  SetString(id, "cohortname", cohort_name);
+}
+
+void PersistedData::SetCohortHint(const std::string& id,
+                                  const std::string& cohort_hint) {
+  SetString(id, "cohorthint", cohort_hint);
+}
+
+bool PersistedData::GetActiveBit(const std::string& id) const {
+  return activity_data_service_ && activity_data_service_->GetActiveBit(id);
+}
+
+int PersistedData::GetDaysSinceLastRollCall(const std::string& id) const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  return activity_data_service_
+             ? activity_data_service_->GetDaysSinceLastRollCall(id)
+             : kDaysUnknown;
+}
+
+int PersistedData::GetDaysSinceLastActive(const std::string& id) const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  return activity_data_service_
+             ? activity_data_service_->GetDaysSinceLastActive(id)
+             : kDaysUnknown;
+}
+
+void PersistedData::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(kPersistedDataPreference);
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/persisted_data.h b/src/components/update_client/persisted_data.h
new file mode 100644
index 0000000..4853d39
--- /dev/null
+++ b/src/components/update_client/persisted_data.h
@@ -0,0 +1,139 @@
+// Copyright 2016 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 COMPONENTS_UPDATE_CLIENT_PERSISTED_DATA_H_
+#define COMPONENTS_UPDATE_CLIENT_PERSISTED_DATA_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "base/values.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace update_client {
+
+class ActivityDataService;
+
+// A PersistedData is a wrapper layer around a PrefService, designed to maintain
+// update data that outlives the browser process and isn't exposed outside of
+// update_client.
+//
+// The public methods of this class should be called only on the thread that
+// initializes it - which also has to match the thread the PrefService has been
+// initialized on.
+class PersistedData {
+ public:
+  // Constructs a provider using the specified |pref_service| and
+  // |activity_data_service|.
+  // The associated preferences are assumed to already be registered.
+  // The |pref_service| and |activity_data_service| must outlive the entire
+  // update_client.
+  PersistedData(PrefService* pref_service,
+                ActivityDataService* activity_data_service);
+
+  ~PersistedData();
+
+  // Returns the DateLastRollCall (the server-localized calendar date number the
+  // |id| was last checked by this client on) for the specified |id|.
+  // -2 indicates that there is no recorded date number for the |id|.
+  int GetDateLastRollCall(const std::string& id) const;
+
+  // Returns the DateLastActive (the server-localized calendar date number the
+  // |id| was last active by this client on) for the specified |id|.
+  // -1 indicates that there is no recorded date for the |id| (i.e. this is the
+  // first time the |id| is active).
+  // -2 indicates that the |id| has an unknown value of last active date.
+  int GetDateLastActive(const std::string& id) const;
+
+#if defined(OS_STARBOARD)
+  // Returns the version of the update that was last successfully unpacked for
+  // the specified |id|. "" indicates that there is no recorded version value
+  // for the |id|.
+  std::string GetLastUnpackedVersion(const std::string& id) const;
+
+  // Returns the updater channel that is set for the specified |id|. ""
+  // indicates that there is no recorded updater channel value for the |id|.
+  std::string GetUpdaterChannel(const std::string& id) const;
+#endif
+
+  // Returns the PingFreshness (a random token that is written into the profile
+  // data whenever the DateLastRollCall it is modified) for the specified |id|.
+  // "" indicates that there is no recorded freshness value for the |id|.
+  std::string GetPingFreshness(const std::string& id) const;
+
+  // Records the DateLastRollCall for the specified |ids|. |datenum| must be a
+  // non-negative integer: calls with a negative |datenum| are simply ignored.
+  // Calls to SetDateLastRollCall that occur prior to the persisted data store
+  // has been fully initialized are ignored. Also sets the PingFreshness.
+  void SetDateLastRollCall(const std::vector<std::string>& ids, int datenum);
+
+  // Records the DateLastActive for the specified |ids|. |datenum| must be a
+  // non-negative integer: calls with a negative |datenum| are simply ignored.
+  // Calls to SetDateLastActive that occur prior to the persisted data store
+  // has been fully initialized or the active bit of the |ids| are not set
+  // are ignored.
+  // This function also clears the active bits of the specified |ids| if they
+  // are set.
+  void SetDateLastActive(const std::vector<std::string>& ids, int datenum);
+
+#if defined(OS_STARBOARD)
+  // Records the version of the update that is successfully unpacked for
+  // the specified |id|.
+  void SetLastUnpackedVersion(const std::string& id,
+                              const std::string& version);
+
+  // Records the updater channel that is set for the specified |id|.
+  void SetUpdaterChannel(const std::string& id, const std::string& channel);
+#endif
+
+  // This is called only via update_client's RegisterUpdateClientPreferences.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  // These functions return cohort data for the specified |id|. "Cohort"
+  // indicates the membership of the client in any release channels components
+  // have set up in a machine-readable format, while "CohortName" does so in a
+  // human-readable form. "CohortHint" indicates the client's channel selection
+  // preference.
+  std::string GetCohort(const std::string& id) const;
+  std::string GetCohortHint(const std::string& id) const;
+  std::string GetCohortName(const std::string& id) const;
+
+  // These functions set cohort data for the specified |id|.
+  void SetCohort(const std::string& id, const std::string& cohort);
+  void SetCohortHint(const std::string& id, const std::string& cohort_hint);
+  void SetCohortName(const std::string& id, const std::string& cohort_name);
+
+  // Returns true if the active bit of the specified |id| is set.
+  bool GetActiveBit(const std::string& id) const;
+
+  // The following two functions returns the number of days since the last
+  // time the client checked for update/was active.
+  // -1 indicates that this is the first time the client reports
+  // an update check/active for the specified |id|.
+  // -2 indicates that the client has no information about the
+  // update check/last active of the specified |id|.
+  int GetDaysSinceLastRollCall(const std::string& id) const;
+  int GetDaysSinceLastActive(const std::string& id) const;
+
+ private:
+  int GetInt(const std::string& id, const std::string& key, int fallback) const;
+  std::string GetString(const std::string& id, const std::string& key) const;
+  void SetString(const std::string& id,
+                 const std::string& key,
+                 const std::string& value);
+
+  base::ThreadChecker thread_checker_;
+  PrefService* pref_service_;
+  ActivityDataService* activity_data_service_;
+
+  DISALLOW_COPY_AND_ASSIGN(PersistedData);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PERSISTED_DATA_H_
diff --git a/src/components/update_client/persisted_data_unittest.cc b/src/components/update_client/persisted_data_unittest.cc
new file mode 100644
index 0000000..7f7ae6e
--- /dev/null
+++ b/src/components/update_client/persisted_data_unittest.cc
@@ -0,0 +1,250 @@
+// Copyright 2016 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 <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "components/prefs/testing_pref_service.h"
+#include "components/update_client/activity_data_service.h"
+#include "components/update_client/persisted_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+TEST(PersistedDataTest, Simple) {
+  auto pref = std::make_unique<TestingPrefServiceSimple>();
+  PersistedData::RegisterPrefs(pref->registry());
+  auto metadata = std::make_unique<PersistedData>(pref.get(), nullptr);
+  EXPECT_EQ(-2, metadata->GetDateLastRollCall("someappid"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("someappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someappid"));
+  std::vector<std::string> items;
+  items.push_back("someappid");
+  metadata->SetDateLastRollCall(items, 3383);
+  metadata->SetDateLastActive(items, 3383);
+  EXPECT_EQ(3383, metadata->GetDateLastRollCall("someappid"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("someappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someappid"));
+  EXPECT_EQ(-2, metadata->GetDateLastRollCall("someotherappid"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("someotherappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someotherappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someotherappid"));
+  const std::string pf1 = metadata->GetPingFreshness("someappid");
+  EXPECT_FALSE(pf1.empty());
+  metadata->SetDateLastRollCall(items, 3386);
+  metadata->SetDateLastActive(items, 3386);
+  EXPECT_EQ(3386, metadata->GetDateLastRollCall("someappid"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("someappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someappid"));
+  EXPECT_EQ(-2, metadata->GetDateLastRollCall("someotherappid"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("someotherappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someotherappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someotherappid"));
+  const std::string pf2 = metadata->GetPingFreshness("someappid");
+  EXPECT_FALSE(pf2.empty());
+  // The following has a 1 / 2^128 chance of being flaky.
+  EXPECT_NE(pf1, pf2);
+}
+
+TEST(PersistedDataTest, SharedPref) {
+  auto pref = std::make_unique<TestingPrefServiceSimple>();
+  PersistedData::RegisterPrefs(pref->registry());
+  auto metadata = std::make_unique<PersistedData>(pref.get(), nullptr);
+  EXPECT_EQ(-2, metadata->GetDateLastRollCall("someappid"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("someappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someappid"));
+  std::vector<std::string> items;
+  items.push_back("someappid");
+  metadata->SetDateLastRollCall(items, 3383);
+  metadata->SetDateLastActive(items, 3383);
+
+  // Now, create a new PersistedData reading from the same path, verify
+  // that it loads the value.
+  metadata = std::make_unique<PersistedData>(pref.get(), nullptr);
+  EXPECT_EQ(3383, metadata->GetDateLastRollCall("someappid"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("someappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someappid"));
+  EXPECT_EQ(-2, metadata->GetDateLastRollCall("someotherappid"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("someotherappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someotherappid"));
+  EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someotherappid"));
+}
+
+TEST(PersistedDataTest, SimpleCohort) {
+  auto pref = std::make_unique<TestingPrefServiceSimple>();
+  PersistedData::RegisterPrefs(pref->registry());
+  auto metadata = std::make_unique<PersistedData>(pref.get(), nullptr);
+  EXPECT_EQ("", metadata->GetCohort("someappid"));
+  EXPECT_EQ("", metadata->GetCohort("someotherappid"));
+  EXPECT_EQ("", metadata->GetCohortHint("someappid"));
+  EXPECT_EQ("", metadata->GetCohortHint("someotherappid"));
+  EXPECT_EQ("", metadata->GetCohortName("someappid"));
+  EXPECT_EQ("", metadata->GetCohortName("someotherappid"));
+  metadata->SetCohort("someappid", "c1");
+  metadata->SetCohort("someotherappid", "c2");
+  metadata->SetCohortHint("someappid", "ch1");
+  metadata->SetCohortHint("someotherappid", "ch2");
+  metadata->SetCohortName("someappid", "cn1");
+  metadata->SetCohortName("someotherappid", "cn2");
+  EXPECT_EQ("c1", metadata->GetCohort("someappid"));
+  EXPECT_EQ("c2", metadata->GetCohort("someotherappid"));
+  EXPECT_EQ("ch1", metadata->GetCohortHint("someappid"));
+  EXPECT_EQ("ch2", metadata->GetCohortHint("someotherappid"));
+  EXPECT_EQ("cn1", metadata->GetCohortName("someappid"));
+  EXPECT_EQ("cn2", metadata->GetCohortName("someotherappid"));
+  metadata->SetCohort("someappid", "oc1");
+  metadata->SetCohort("someotherappid", "oc2");
+  metadata->SetCohortHint("someappid", "och1");
+  metadata->SetCohortHint("someotherappid", "och2");
+  metadata->SetCohortName("someappid", "ocn1");
+  metadata->SetCohortName("someotherappid", "ocn2");
+  EXPECT_EQ("oc1", metadata->GetCohort("someappid"));
+  EXPECT_EQ("oc2", metadata->GetCohort("someotherappid"));
+  EXPECT_EQ("och1", metadata->GetCohortHint("someappid"));
+  EXPECT_EQ("och2", metadata->GetCohortHint("someotherappid"));
+  EXPECT_EQ("ocn1", metadata->GetCohortName("someappid"));
+  EXPECT_EQ("ocn2", metadata->GetCohortName("someotherappid"));
+}
+
+TEST(PersistedDataTest, ActivityData) {
+  class TestActivityDataService : public ActivityDataService {
+   public:
+    bool GetActiveBit(const std::string& id) const override {
+      auto it = active_.find(id);
+      return it != active_.end() ? it->second : false;
+    }
+
+    int GetDaysSinceLastActive(const std::string& id) const override {
+      auto it = days_last_active_.find(id);
+      return it != days_last_active_.end() ? it->second : -1;
+    }
+
+    int GetDaysSinceLastRollCall(const std::string& id) const override {
+      auto it = days_last_roll_call_.find(id);
+      return it != days_last_roll_call_.end() ? it->second : -1;
+    }
+
+    void ClearActiveBit(const std::string& id) override { active_[id] = false; }
+
+    void SetDaysSinceLastActive(const std::string& id, int numdays) {
+      days_last_active_[id] = numdays;
+    }
+
+    void SetDaysSinceLastRollCall(const std::string& id, int numdays) {
+      days_last_roll_call_[id] = numdays;
+    }
+
+    void SetActiveBit(const std::string& id) { active_[id] = true; }
+
+   private:
+    std::map<std::string, bool> active_;
+    std::map<std::string, int> days_last_active_;
+    std::map<std::string, int> days_last_roll_call_;
+  };
+
+  auto pref = std::make_unique<TestingPrefServiceSimple>();
+  auto activity_service = std::make_unique<TestActivityDataService>();
+  PersistedData::RegisterPrefs(pref->registry());
+  auto metadata =
+      std::make_unique<PersistedData>(pref.get(), activity_service.get());
+
+  std::vector<std::string> items({"id1", "id2", "id3"});
+
+  for (const auto& item : items) {
+    EXPECT_EQ(-2, metadata->GetDateLastActive(item));
+    EXPECT_EQ(-2, metadata->GetDateLastRollCall(item));
+    EXPECT_EQ(-1, metadata->GetDaysSinceLastActive(item));
+    EXPECT_EQ(-1, metadata->GetDaysSinceLastRollCall(item));
+    EXPECT_EQ(false, metadata->GetActiveBit(item));
+  }
+
+  metadata->SetDateLastActive(items, 1234);
+  metadata->SetDateLastRollCall(items, 1234);
+  for (const auto& item : items) {
+    EXPECT_EQ(false, metadata->GetActiveBit(item));
+    EXPECT_EQ(-2, metadata->GetDateLastActive(item));
+    EXPECT_EQ(1234, metadata->GetDateLastRollCall(item));
+    EXPECT_EQ(-1, metadata->GetDaysSinceLastActive(item));
+    EXPECT_EQ(-1, metadata->GetDaysSinceLastRollCall(item));
+  }
+
+  activity_service->SetActiveBit("id1");
+  activity_service->SetDaysSinceLastActive("id1", 3);
+  activity_service->SetDaysSinceLastRollCall("id1", 2);
+  activity_service->SetDaysSinceLastRollCall("id2", 3);
+  activity_service->SetDaysSinceLastRollCall("id3", 4);
+  EXPECT_EQ(true, metadata->GetActiveBit("id1"));
+  EXPECT_EQ(false, metadata->GetActiveBit("id2"));
+  EXPECT_EQ(false, metadata->GetActiveBit("id2"));
+  EXPECT_EQ(3, metadata->GetDaysSinceLastActive("id1"));
+  EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id2"));
+  EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id3"));
+  EXPECT_EQ(2, metadata->GetDaysSinceLastRollCall("id1"));
+  EXPECT_EQ(3, metadata->GetDaysSinceLastRollCall("id2"));
+  EXPECT_EQ(4, metadata->GetDaysSinceLastRollCall("id3"));
+
+  metadata->SetDateLastActive(items, 3384);
+  metadata->SetDateLastRollCall(items, 3383);
+  EXPECT_EQ(false, metadata->GetActiveBit("id1"));
+  EXPECT_EQ(false, metadata->GetActiveBit("id2"));
+  EXPECT_EQ(false, metadata->GetActiveBit("id3"));
+  EXPECT_EQ(3, metadata->GetDaysSinceLastActive("id1"));
+  EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id2"));
+  EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id3"));
+  EXPECT_EQ(2, metadata->GetDaysSinceLastRollCall("id1"));
+  EXPECT_EQ(3, metadata->GetDaysSinceLastRollCall("id2"));
+  EXPECT_EQ(4, metadata->GetDaysSinceLastRollCall("id3"));
+  EXPECT_EQ(3384, metadata->GetDateLastActive("id1"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("id2"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("id3"));
+  EXPECT_EQ(3383, metadata->GetDateLastRollCall("id1"));
+  EXPECT_EQ(3383, metadata->GetDateLastRollCall("id2"));
+  EXPECT_EQ(3383, metadata->GetDateLastRollCall("id3"));
+
+  metadata->SetDateLastActive(items, 5000);
+  metadata->SetDateLastRollCall(items, 3390);
+  EXPECT_EQ(false, metadata->GetActiveBit("id1"));
+  EXPECT_EQ(false, metadata->GetActiveBit("id2"));
+  EXPECT_EQ(false, metadata->GetActiveBit("id3"));
+  EXPECT_EQ(3, metadata->GetDaysSinceLastActive("id1"));
+  EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id2"));
+  EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id3"));
+  EXPECT_EQ(2, metadata->GetDaysSinceLastRollCall("id1"));
+  EXPECT_EQ(3, metadata->GetDaysSinceLastRollCall("id2"));
+  EXPECT_EQ(4, metadata->GetDaysSinceLastRollCall("id3"));
+  EXPECT_EQ(3384, metadata->GetDateLastActive("id1"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("id2"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("id3"));
+  EXPECT_EQ(3390, metadata->GetDateLastRollCall("id1"));
+  EXPECT_EQ(3390, metadata->GetDateLastRollCall("id2"));
+  EXPECT_EQ(3390, metadata->GetDateLastRollCall("id3"));
+
+  activity_service->SetActiveBit("id2");
+  metadata->SetDateLastActive(items, 5678);
+  metadata->SetDateLastRollCall(items, 6789);
+  EXPECT_EQ(false, metadata->GetActiveBit("id1"));
+  EXPECT_EQ(false, metadata->GetActiveBit("id2"));
+  EXPECT_EQ(false, metadata->GetActiveBit("id3"));
+  EXPECT_EQ(3, metadata->GetDaysSinceLastActive("id1"));
+  EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id2"));
+  EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id3"));
+  EXPECT_EQ(2, metadata->GetDaysSinceLastRollCall("id1"));
+  EXPECT_EQ(3, metadata->GetDaysSinceLastRollCall("id2"));
+  EXPECT_EQ(4, metadata->GetDaysSinceLastRollCall("id3"));
+  EXPECT_EQ(3384, metadata->GetDateLastActive("id1"));
+  EXPECT_EQ(5678, metadata->GetDateLastActive("id2"));
+  EXPECT_EQ(-2, metadata->GetDateLastActive("id3"));
+  EXPECT_EQ(6789, metadata->GetDateLastRollCall("id1"));
+  EXPECT_EQ(6789, metadata->GetDateLastRollCall("id2"));
+  EXPECT_EQ(6789, metadata->GetDateLastRollCall("id3"));
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/ping_manager.cc b/src/components/update_client/ping_manager.cc
new file mode 100644
index 0000000..3c2a60c
--- /dev/null
+++ b/src/components/update_client/ping_manager.cc
@@ -0,0 +1,129 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/ping_manager.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/protocol_definition.h"
+#include "components/update_client/protocol_handler.h"
+#include "components/update_client/protocol_serializer.h"
+#include "components/update_client/request_sender.h"
+#include "components/update_client/utils.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+namespace {
+
+const int kErrorNoEvents = -1;
+const int kErrorNoUrl = -2;
+
+// An instance of this class can send only one ping.
+class PingSender : public base::RefCountedThreadSafe<PingSender> {
+ public:
+  using Callback = PingManager::Callback;
+  explicit PingSender(scoped_refptr<Configurator> config);
+  void SendPing(const Component& component, Callback callback);
+
+ protected:
+  virtual ~PingSender();
+
+ private:
+  friend class base::RefCountedThreadSafe<PingSender>;
+  void SendPingComplete(int error,
+                        const std::string& response,
+                        int retry_after_sec);
+
+  THREAD_CHECKER(thread_checker_);
+
+  const scoped_refptr<Configurator> config_;
+  Callback callback_;
+  std::unique_ptr<RequestSender> request_sender_;
+
+  DISALLOW_COPY_AND_ASSIGN(PingSender);
+};
+
+PingSender::PingSender(scoped_refptr<Configurator> config) : config_(config) {}
+
+PingSender::~PingSender() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+void PingSender::SendPing(const Component& component, Callback callback) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (component.events().empty()) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), kErrorNoEvents, ""));
+    return;
+  }
+
+  DCHECK(component.crx_component());
+
+  auto urls(config_->PingUrl());
+  if (component.crx_component()->requires_network_encryption)
+    RemoveUnsecureUrls(&urls);
+
+  if (urls.empty()) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), kErrorNoUrl, ""));
+    return;
+  }
+
+  callback_ = std::move(callback);
+
+  std::vector<protocol_request::App> apps;
+  apps.push_back(MakeProtocolApp(component.id(),
+                                 component.crx_component()->version,
+                                 component.GetEvents()));
+  request_sender_ = std::make_unique<RequestSender>(config_);
+  request_sender_->Send(
+      urls, {},
+      config_->GetProtocolHandlerFactory()->CreateSerializer()->Serialize(
+          MakeProtocolRequest(
+              component.session_id(), config_->GetProdId(),
+              config_->GetBrowserVersion().GetString(), config_->GetLang(),
+              config_->GetChannel(), config_->GetOSLongName(),
+              config_->GetDownloadPreference(), config_->ExtraRequestParams(),
+              nullptr, std::move(apps))),
+      false, base::BindOnce(&PingSender::SendPingComplete, this));
+}
+
+void PingSender::SendPingComplete(int error,
+                                  const std::string& response,
+                                  int retry_after_sec) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  std::move(callback_).Run(error, response);
+}
+
+}  // namespace
+
+PingManager::PingManager(scoped_refptr<Configurator> config)
+    : config_(config) {}
+
+PingManager::~PingManager() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+void PingManager::SendPing(const Component& component, Callback callback) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  auto ping_sender = base::MakeRefCounted<PingSender>(config_);
+  ping_sender->SendPing(component, std::move(callback));
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/ping_manager.h b/src/components/update_client/ping_manager.h
new file mode 100644
index 0000000..24040e6
--- /dev/null
+++ b/src/components/update_client/ping_manager.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_PING_MANAGER_H_
+#define COMPONENTS_UPDATE_CLIENT_PING_MANAGER_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+
+namespace update_client {
+
+class Configurator;
+class Component;
+
+class PingManager : public base::RefCountedThreadSafe<PingManager> {
+ public:
+  // |error| is 0 if the ping was sent successfully, otherwise |error| contains
+  // a value with no particular meaning for the caller.
+  using Callback =
+      base::OnceCallback<void(int error, const std::string& response)>;
+
+  explicit PingManager(scoped_refptr<Configurator> config);
+
+  // Sends a ping for the |item|. |callback| is invoked after the ping is sent
+  // or an error has occured. The ping itself is not persisted and it will
+  // be discarded if it has not been sent for any reason.
+  virtual void SendPing(const Component& component, Callback callback);
+
+ protected:
+  virtual ~PingManager();
+
+ private:
+  friend class base::RefCountedThreadSafe<PingManager>;
+
+  THREAD_CHECKER(thread_checker_);
+  const scoped_refptr<Configurator> config_;
+
+  DISALLOW_COPY_AND_ASSIGN(PingManager);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PING_MANAGER_H_
diff --git a/src/components/update_client/ping_manager_unittest.cc b/src/components/update_client/ping_manager_unittest.cc
new file mode 100644
index 0000000..34583d7
--- /dev/null
+++ b/src/components/update_client/ping_manager_unittest.cc
@@ -0,0 +1,443 @@
+// Copyright 2013 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 "components/update_client/ping_manager.h"
+
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/version.h"
+#include "components/update_client/component.h"
+#include "components/update_client/net/url_loader_post_interceptor.h"
+#include "components/update_client/protocol_definition.h"
+#include "components/update_client/protocol_serializer.h"
+#include "components/update_client/test_configurator.h"
+#include "components/update_client/update_engine.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/re2/src/re2/re2.h"
+
+using std::string;
+
+namespace update_client {
+
+class PingManagerTest : public testing::Test,
+                        public testing::WithParamInterface<bool> {
+ public:
+  PingManagerTest();
+  ~PingManagerTest() override {}
+
+  PingManager::Callback MakePingCallback();
+  scoped_refptr<UpdateContext> MakeMockUpdateContext() const;
+
+  // Overrides from testing::Test.
+  void SetUp() override;
+  void TearDown() override;
+
+  void PingSentCallback(int error, const std::string& response);
+
+ protected:
+  void Quit();
+  void RunThreads();
+
+  scoped_refptr<TestConfigurator> config_;
+  scoped_refptr<PingManager> ping_manager_;
+
+  int error_ = -1;
+  std::string response_;
+
+ private:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  base::OnceClosure quit_closure_;
+};
+
+PingManagerTest::PingManagerTest()
+    : scoped_task_environment_(
+          base::test::ScopedTaskEnvironment::MainThreadType::IO) {
+  config_ = base::MakeRefCounted<TestConfigurator>();
+}
+
+void PingManagerTest::SetUp() {
+  ping_manager_ = base::MakeRefCounted<PingManager>(config_);
+}
+
+void PingManagerTest::TearDown() {
+  // Run the threads until they are idle to allow the clean up
+  // of the network interceptors on the IO thread.
+  scoped_task_environment_.RunUntilIdle();
+  ping_manager_ = nullptr;
+}
+
+void PingManagerTest::RunThreads() {
+  base::RunLoop runloop;
+  quit_closure_ = runloop.QuitClosure();
+  runloop.Run();
+}
+
+void PingManagerTest::Quit() {
+  if (!quit_closure_.is_null())
+    std::move(quit_closure_).Run();
+}
+
+PingManager::Callback PingManagerTest::MakePingCallback() {
+  return base::BindOnce(&PingManagerTest::PingSentCallback,
+                        base::Unretained(this));
+}
+
+void PingManagerTest::PingSentCallback(int error, const std::string& response) {
+  error_ = error;
+  response_ = response;
+  Quit();
+}
+
+scoped_refptr<UpdateContext> PingManagerTest::MakeMockUpdateContext() const {
+  return base::MakeRefCounted<UpdateContext>(
+      config_, false, std::vector<std::string>(),
+      UpdateClient::CrxDataCallback(), UpdateEngine::NotifyObserversCallback(),
+      UpdateEngine::Callback(), nullptr);
+}
+
+// This test is parameterized for using JSON or XML serialization. |true| means
+// JSON serialization is used.
+INSTANTIATE_TEST_SUITE_P(Parameterized, PingManagerTest, testing::Bool());
+
+TEST_P(PingManagerTest, SendPing) {
+  auto interceptor = std::make_unique<URLLoaderPostInterceptor>(
+      config_->test_url_loader_factory());
+  EXPECT_TRUE(interceptor);
+
+  const auto update_context = MakeMockUpdateContext();
+  {
+    // Test eventresult="1" is sent for successful updates.
+    Component component(*update_context, "abc");
+    component.crx_component_ = CrxComponent();
+    component.crx_component_->version = base::Version("1.0");
+    component.state_ = std::make_unique<Component::StateUpdated>(&component);
+    component.previous_version_ = base::Version("1.0");
+    component.next_version_ = base::Version("2.0");
+    component.AppendEvent(component.MakeEventUpdateComplete());
+
+    EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+    ping_manager_->SendPing(component, MakePingCallback());
+    RunThreads();
+
+    EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+    const auto msg = interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(msg);
+    ASSERT_TRUE(root);
+    const auto* request = root->FindKey("request");
+    ASSERT_TRUE(request);
+    EXPECT_TRUE(request->FindKey("@os"));
+    EXPECT_EQ("fake_prodid", request->FindKey("@updater")->GetString());
+    EXPECT_EQ("crx2,crx3", request->FindKey("acceptformat")->GetString());
+    EXPECT_TRUE(request->FindKey("arch"));
+    EXPECT_EQ("cr", request->FindKey("dedup")->GetString());
+    EXPECT_LT(0, request->FindPath({"hw", "physmemory"})->GetInt());
+    EXPECT_EQ("fake_lang", request->FindKey("lang")->GetString());
+    EXPECT_TRUE(request->FindKey("nacl_arch"));
+    EXPECT_EQ("fake_channel_string",
+              request->FindKey("prodchannel")->GetString());
+    EXPECT_EQ("30.0", request->FindKey("prodversion")->GetString());
+    EXPECT_EQ("3.1", request->FindKey("protocol")->GetString());
+    EXPECT_TRUE(request->FindKey("requestid"));
+    EXPECT_TRUE(request->FindKey("sessionid"));
+    EXPECT_EQ("fake_channel_string",
+              request->FindKey("updaterchannel")->GetString());
+    EXPECT_EQ("30.0", request->FindKey("updaterversion")->GetString());
+
+    EXPECT_TRUE(request->FindPath({"os", "arch"})->is_string());
+    EXPECT_EQ("Fake Operating System",
+              request->FindPath({"os", "platform"})->GetString());
+    EXPECT_TRUE(request->FindPath({"os", "version"})->is_string());
+
+    const auto& app = request->FindKey("app")->GetList()[0];
+    EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+    EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+    const auto& event = app.FindKey("event")->GetList()[0];
+    EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+    EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+    EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+    EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+
+    // Check the ping request does not carry the specific extra request headers.
+    const auto headers = std::get<1>(interceptor->GetRequests()[0]);
+    EXPECT_FALSE(headers.HasHeader("X-Goog-Update-Interactivity"));
+    EXPECT_FALSE(headers.HasHeader("X-Goog-Update-Updater"));
+    EXPECT_FALSE(headers.HasHeader("X-Goog-Update-AppId"));
+    interceptor->Reset();
+  }
+
+  {
+    // Test eventresult="0" is sent for failed updates.
+    Component component(*update_context, "abc");
+    component.crx_component_ = CrxComponent();
+    component.crx_component_->version = base::Version("1.0");
+    component.state_ =
+        std::make_unique<Component::StateUpdateError>(&component);
+    component.previous_version_ = base::Version("1.0");
+    component.next_version_ = base::Version("2.0");
+    component.AppendEvent(component.MakeEventUpdateComplete());
+
+    EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+    ping_manager_->SendPing(component, MakePingCallback());
+    RunThreads();
+
+    EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+    const auto msg = interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(msg);
+    ASSERT_TRUE(root);
+    const auto* request = root->FindKey("request");
+    const auto& app = request->FindKey("app")->GetList()[0];
+    EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+    EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+    const auto& event = app.FindKey("event")->GetList()[0];
+    EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+    EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+    EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+    EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+    interceptor->Reset();
+  }
+
+  {
+    // Test the error values and the fingerprints.
+    Component component(*update_context, "abc");
+    component.crx_component_ = CrxComponent();
+    component.crx_component_->version = base::Version("1.0");
+    component.state_ =
+        std::make_unique<Component::StateUpdateError>(&component);
+    component.previous_version_ = base::Version("1.0");
+    component.next_version_ = base::Version("2.0");
+    component.previous_fp_ = "prev fp";
+    component.next_fp_ = "next fp";
+    component.error_category_ = ErrorCategory::kDownload;
+    component.error_code_ = 2;
+    component.extra_code1_ = -1;
+    component.diff_error_category_ = ErrorCategory::kService;
+    component.diff_error_code_ = 20;
+    component.diff_extra_code1_ = -10;
+    component.crx_diffurls_.push_back(GURL("http://host/path"));
+    component.AppendEvent(component.MakeEventUpdateComplete());
+
+    EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+    ping_manager_->SendPing(component, MakePingCallback());
+    RunThreads();
+
+    EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+    const auto msg = interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(msg);
+    ASSERT_TRUE(root);
+    const auto* request = root->FindKey("request");
+    const auto& app = request->FindKey("app")->GetList()[0];
+    EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+    EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+    const auto& event = app.FindKey("event")->GetList()[0];
+    EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+    EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+    EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+    EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+    EXPECT_EQ(4, event.FindKey("differrorcat")->GetInt());
+    EXPECT_EQ(20, event.FindKey("differrorcode")->GetInt());
+    EXPECT_EQ(-10, event.FindKey("diffextracode1")->GetInt());
+    EXPECT_EQ(0, event.FindKey("diffresult")->GetInt());
+    EXPECT_EQ(1, event.FindKey("errorcat")->GetInt());
+    EXPECT_EQ(2, event.FindKey("errorcode")->GetInt());
+    EXPECT_EQ(-1, event.FindKey("extracode1")->GetInt());
+    EXPECT_EQ("next fp", event.FindKey("nextfp")->GetString());
+    EXPECT_EQ("prev fp", event.FindKey("previousfp")->GetString());
+    interceptor->Reset();
+  }
+
+  {
+    // Test an invalid |next_version| is not serialized.
+    Component component(*update_context, "abc");
+    component.crx_component_ = CrxComponent();
+    component.crx_component_->version = base::Version("1.0");
+    component.state_ =
+        std::make_unique<Component::StateUpdateError>(&component);
+    component.previous_version_ = base::Version("1.0");
+
+    component.AppendEvent(component.MakeEventUpdateComplete());
+
+    EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+    ping_manager_->SendPing(component, MakePingCallback());
+    RunThreads();
+
+    EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+    const auto msg = interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(msg);
+    ASSERT_TRUE(root);
+    const auto* request = root->FindKey("request");
+    const auto& app = request->FindKey("app")->GetList()[0];
+    EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+    EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+    const auto& event = app.FindKey("event")->GetList()[0];
+    EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+    EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+    EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+    interceptor->Reset();
+  }
+
+  {
+    // Test a valid |previouversion| and |next_version| = base::Version("0")
+    // are serialized correctly under <event...> for uninstall.
+    Component component(*update_context, "abc");
+    component.crx_component_ = CrxComponent();
+    component.Uninstall(base::Version("1.2.3.4"), 0);
+    component.AppendEvent(component.MakeEventUninstalled());
+
+    EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+    ping_manager_->SendPing(component, MakePingCallback());
+    RunThreads();
+
+    EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+    const auto msg = interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(msg);
+    ASSERT_TRUE(root);
+    const auto* request = root->FindKey("request");
+    const auto& app = request->FindKey("app")->GetList()[0];
+    EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+    EXPECT_EQ("1.2.3.4", app.FindKey("version")->GetString());
+    const auto& event = app.FindKey("event")->GetList()[0];
+    EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+    EXPECT_EQ(4, event.FindKey("eventtype")->GetInt());
+    EXPECT_EQ("1.2.3.4", event.FindKey("previousversion")->GetString());
+    EXPECT_EQ("0", event.FindKey("nextversion")->GetString());
+    interceptor->Reset();
+  }
+
+  {
+    // Test the download metrics.
+    Component component(*update_context, "abc");
+    component.crx_component_ = CrxComponent();
+    component.crx_component_->version = base::Version("1.0");
+    component.state_ = std::make_unique<Component::StateUpdated>(&component);
+    component.previous_version_ = base::Version("1.0");
+    component.next_version_ = base::Version("2.0");
+    component.AppendEvent(component.MakeEventUpdateComplete());
+
+    CrxDownloader::DownloadMetrics download_metrics;
+    download_metrics.url = GURL("http://host1/path1");
+    download_metrics.downloader = CrxDownloader::DownloadMetrics::kUrlFetcher;
+    download_metrics.error = -1;
+    download_metrics.downloaded_bytes = 123;
+    download_metrics.total_bytes = 456;
+    download_metrics.download_time_ms = 987;
+    component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
+
+    download_metrics = CrxDownloader::DownloadMetrics();
+    download_metrics.url = GURL("http://host2/path2");
+    download_metrics.downloader = CrxDownloader::DownloadMetrics::kBits;
+    download_metrics.error = 0;
+    download_metrics.downloaded_bytes = 1230;
+    download_metrics.total_bytes = 4560;
+    download_metrics.download_time_ms = 9870;
+    component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
+
+    download_metrics = CrxDownloader::DownloadMetrics();
+    download_metrics.url = GURL("http://host3/path3");
+    download_metrics.downloader = CrxDownloader::DownloadMetrics::kBits;
+    download_metrics.error = 0;
+    download_metrics.downloaded_bytes = kProtocolMaxInt;
+    download_metrics.total_bytes = kProtocolMaxInt - 1;
+    download_metrics.download_time_ms = kProtocolMaxInt - 2;
+    component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
+
+    EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+    ping_manager_->SendPing(component, MakePingCallback());
+    RunThreads();
+
+    EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+    const auto msg = interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(msg);
+    ASSERT_TRUE(root);
+    const auto* request = root->FindKey("request");
+    const auto& app = request->FindKey("app")->GetList()[0];
+    EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+    EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+    EXPECT_EQ(4u, app.FindKey("event")->GetList().size());
+    {
+      const auto& event = app.FindKey("event")->GetList()[0];
+      EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+      EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+      EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+      EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+    }
+    {
+      const auto& event = app.FindKey("event")->GetList()[1];
+      EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+      EXPECT_EQ(14, event.FindKey("eventtype")->GetInt());
+      EXPECT_EQ(987, event.FindKey("download_time_ms")->GetDouble());
+      EXPECT_EQ(123, event.FindKey("downloaded")->GetDouble());
+      EXPECT_EQ("direct", event.FindKey("downloader")->GetString());
+      EXPECT_EQ(-1, event.FindKey("errorcode")->GetInt());
+      EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+      EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+      EXPECT_EQ(456, event.FindKey("total")->GetDouble());
+      EXPECT_EQ("http://host1/path1", event.FindKey("url")->GetString());
+    }
+    {
+      const auto& event = app.FindKey("event")->GetList()[2];
+      EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+      EXPECT_EQ(14, event.FindKey("eventtype")->GetInt());
+      EXPECT_EQ(9870, event.FindKey("download_time_ms")->GetDouble());
+      EXPECT_EQ(1230, event.FindKey("downloaded")->GetDouble());
+      EXPECT_EQ("bits", event.FindKey("downloader")->GetString());
+      EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+      EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+      EXPECT_EQ(4560, event.FindKey("total")->GetDouble());
+      EXPECT_EQ("http://host2/path2", event.FindKey("url")->GetString());
+    }
+    {
+      const auto& event = app.FindKey("event")->GetList()[3];
+      EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+      EXPECT_EQ(14, event.FindKey("eventtype")->GetInt());
+      EXPECT_EQ(9007199254740990,
+                event.FindKey("download_time_ms")->GetDouble());
+      EXPECT_EQ(9007199254740992, event.FindKey("downloaded")->GetDouble());
+      EXPECT_EQ("bits", event.FindKey("downloader")->GetString());
+      EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+      EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+      EXPECT_EQ(9007199254740991, event.FindKey("total")->GetDouble());
+      EXPECT_EQ("http://host3/path3", event.FindKey("url")->GetString());
+    }
+    interceptor->Reset();
+  }
+}
+
+// Tests that sending the ping fails when the component requires encryption but
+// the ping URL is unsecure.
+TEST_P(PingManagerTest, RequiresEncryption) {
+  config_->SetPingUrl(GURL("http:\\foo\bar"));
+
+  const auto update_context = MakeMockUpdateContext();
+
+  Component component(*update_context, "abc");
+  component.crx_component_ = CrxComponent();
+  component.crx_component_->version = base::Version("1.0");
+
+  // The default value for |requires_network_encryption| is true.
+  EXPECT_TRUE(component.crx_component_->requires_network_encryption);
+
+  component.state_ = std::make_unique<Component::StateUpdated>(&component);
+  component.previous_version_ = base::Version("1.0");
+  component.next_version_ = base::Version("2.0");
+  component.AppendEvent(component.MakeEventUpdateComplete());
+
+  ping_manager_->SendPing(component, MakePingCallback());
+  RunThreads();
+
+  EXPECT_EQ(-2, error_);
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/protocol_definition.cc b/src/components/update_client/protocol_definition.cc
new file mode 100644
index 0000000..2dd3cc6
--- /dev/null
+++ b/src/components/update_client/protocol_definition.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2018 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 "components/update_client/protocol_definition.h"
+
+#include "base/values.h"
+
+namespace update_client {
+
+namespace protocol_request {
+
+OS::OS() = default;
+OS::OS(OS&&) = default;
+OS::~OS() = default;
+
+Updater::Updater() = default;
+Updater::Updater(const Updater&) = default;
+Updater::~Updater() = default;
+
+UpdateCheck::UpdateCheck() = default;
+UpdateCheck::~UpdateCheck() = default;
+
+Ping::Ping() = default;
+Ping::Ping(const Ping&) = default;
+Ping::~Ping() = default;
+
+App::App() = default;
+App::App(App&&) = default;
+App::~App() = default;
+
+Request::Request() = default;
+Request::Request(Request&&) = default;
+Request::~Request() = default;
+
+}  // namespace protocol_request
+
+}  // namespace update_client
diff --git a/src/components/update_client/protocol_definition.h b/src/components/update_client/protocol_definition.h
new file mode 100644
index 0000000..aaacc76
--- /dev/null
+++ b/src/components/update_client/protocol_definition.h
@@ -0,0 +1,177 @@
+// Copyright (c) 2018 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_DEFINITION_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_DEFINITION_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/values.h"
+#include "build/build_config.h"
+
+namespace update_client {
+
+// The protocol versions so far are:
+// * Version 3.1: it changes how the run actions are serialized.
+// * Version 3.0: it is the version implemented by the desktop updaters.
+constexpr char kProtocolVersion[] = "3.1";
+
+// Due to implementation constraints of the JSON parser and serializer,
+// precision of integer numbers greater than 2^53 is lost.
+constexpr int64_t kProtocolMaxInt = 1LL << 53;
+
+namespace protocol_request {
+
+struct HW {
+  uint32_t physmemory = 0;  // Physical memory rounded down to the closest GB.
+};
+
+struct OS {
+  OS();
+  OS(OS&&);
+  ~OS();
+
+  std::string platform;
+  std::string version;
+  std::string service_pack;
+  std::string arch;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OS);
+};
+
+struct Updater {
+  Updater();
+  Updater(const Updater&);
+  ~Updater();
+
+  std::string name;
+  std::string version;
+  bool is_machine = false;
+  bool autoupdate_check_enabled = false;
+  base::Optional<int> last_started;
+  base::Optional<int> last_checked;
+  int update_policy = 0;
+};
+
+struct UpdateCheck {
+  UpdateCheck();
+  ~UpdateCheck();
+
+  bool is_update_disabled = false;
+};
+
+// didrun element. The element is named "ping" for legacy reasons.
+struct Ping {
+  Ping();
+  Ping(const Ping&);
+  ~Ping();
+
+  // Preferred user count metrics ("ad" and "rd").
+  base::Optional<int> date_last_active;
+  base::Optional<int> date_last_roll_call;
+
+  // Legacy user count metrics ("a" and "r").
+  base::Optional<int> days_since_last_active_ping;
+  int days_since_last_roll_call = 0;
+
+  std::string ping_freshness;
+};
+
+struct App {
+  App();
+  App(App&&);
+  ~App();
+
+  std::string app_id;
+  std::string version;
+  base::flat_map<std::string, std::string> installer_attributes;
+  std::string lang;
+  std::string brand_code;
+  std::string install_source;
+  std::string install_location;
+  std::string fingerprint;
+
+  std::string cohort;       // Opaque string.
+  std::string cohort_hint;  // Server may use to move the app to a new cohort.
+  std::string cohort_name;  // Human-readable interpretation of the cohort.
+
+  base::Optional<bool> enabled;
+  base::Optional<std::vector<int>> disabled_reasons;
+
+  // Optional update check.
+  base::Optional<UpdateCheck> update_check;
+
+  // Optional 'did run' ping.
+  base::Optional<Ping> ping;
+
+  // Progress/result pings.
+  base::Optional<std::vector<base::Value>> events;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(App);
+};
+
+struct Request {
+  Request();
+  Request(Request&&);
+  ~Request();
+
+  std::string protocol_version;
+
+  // Unique identifier for this session, used to correlate multiple requests
+  // associated with a single update operation.
+  std::string session_id;
+
+  // Unique identifier for this request, used to associate the same request
+  // received multiple times on the server.
+  std::string request_id;
+
+  std::string updatername;
+  std::string updaterversion;
+  std::string prodversion;
+  std::string lang;
+  std::string updaterchannel;
+  std::string prodchannel;
+  std::string operating_system;
+  std::string arch;
+  std::string nacl_arch;
+
+#if defined(OS_WIN)
+  bool is_wow64 = false;
+#endif
+
+  // Provides a hint for what download urls should be returned by server.
+  // This data member is controlled by group policy settings.
+  // The only group policy value supported so far is |cacheable|.
+  std::string dlpref;
+
+  // True if this machine is part of a managed enterprise domain.
+  base::Optional<bool> domain_joined;
+
+  base::flat_map<std::string, std::string> additional_attributes;
+
+  HW hw;
+
+  OS os;
+
+  base::Optional<Updater> updater;
+
+  std::vector<App> apps;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Request);
+};
+
+}  // namespace protocol_request
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PROTOCOL_DEFINITION_H_
diff --git a/src/components/update_client/protocol_handler.cc b/src/components/update_client/protocol_handler.cc
new file mode 100644
index 0000000..aa547b4
--- /dev/null
+++ b/src/components/update_client/protocol_handler.cc
@@ -0,0 +1,21 @@
+// Copyright 2018 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 "components/update_client/protocol_handler.h"
+#include "components/update_client/protocol_parser_json.h"
+#include "components/update_client/protocol_serializer_json.h"
+
+namespace update_client {
+
+std::unique_ptr<ProtocolParser> ProtocolHandlerFactoryJSON::CreateParser()
+    const {
+  return std::make_unique<ProtocolParserJSON>();
+}
+
+std::unique_ptr<ProtocolSerializer>
+ProtocolHandlerFactoryJSON::CreateSerializer() const {
+  return std::make_unique<ProtocolSerializerJSON>();
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/protocol_handler.h b/src/components/update_client/protocol_handler.h
new file mode 100644
index 0000000..57c8cfd
--- /dev/null
+++ b/src/components/update_client/protocol_handler.h
@@ -0,0 +1,39 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_HANDLER_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_HANDLER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+
+namespace update_client {
+
+class ProtocolParser;
+class ProtocolSerializer;
+
+class ProtocolHandlerFactory {
+ public:
+  virtual ~ProtocolHandlerFactory() = default;
+  virtual std::unique_ptr<ProtocolParser> CreateParser() const = 0;
+  virtual std::unique_ptr<ProtocolSerializer> CreateSerializer() const = 0;
+
+ protected:
+  ProtocolHandlerFactory() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProtocolHandlerFactory);
+};
+
+class ProtocolHandlerFactoryJSON final : public ProtocolHandlerFactory {
+ public:
+  // Overrides for ProtocolHandlerFactory.
+  std::unique_ptr<ProtocolParser> CreateParser() const override;
+  std::unique_ptr<ProtocolSerializer> CreateSerializer() const override;
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PROTOCOL_HANDLER_H_
diff --git a/src/components/update_client/protocol_parser.cc b/src/components/update_client/protocol_parser.cc
new file mode 100644
index 0000000..7f280c5
--- /dev/null
+++ b/src/components/update_client/protocol_parser.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/protocol_parser.h"
+#include "base/strings/stringprintf.h"
+
+namespace update_client {
+
+const char ProtocolParser::Result::kCohort[] = "cohort";
+const char ProtocolParser::Result::kCohortHint[] = "cohorthint";
+const char ProtocolParser::Result::kCohortName[] = "cohortname";
+
+ProtocolParser::ProtocolParser() = default;
+ProtocolParser::~ProtocolParser() = default;
+
+ProtocolParser::Results::Results() = default;
+ProtocolParser::Results::Results(const Results& other) = default;
+ProtocolParser::Results::~Results() = default;
+
+ProtocolParser::Result::Result() = default;
+ProtocolParser::Result::Result(const Result& other) = default;
+ProtocolParser::Result::~Result() = default;
+
+ProtocolParser::Result::Manifest::Manifest() = default;
+ProtocolParser::Result::Manifest::Manifest(const Manifest& other) = default;
+ProtocolParser::Result::Manifest::~Manifest() = default;
+
+ProtocolParser::Result::Manifest::Package::Package() = default;
+ProtocolParser::Result::Manifest::Package::Package(const Package& other) =
+    default;
+ProtocolParser::Result::Manifest::Package::~Package() = default;
+
+void ProtocolParser::ParseError(const char* details, ...) {
+  va_list args;
+  va_start(args, details);
+
+  if (!errors_.empty()) {
+    errors_ += "\r\n";
+  }
+
+  base::StringAppendV(&errors_, details, args);
+  va_end(args);
+}
+
+bool ProtocolParser::Parse(const std::string& response) {
+  results_.daystart_elapsed_seconds = kNoDaystart;
+  results_.daystart_elapsed_days = kNoDaystart;
+  results_.list.clear();
+  errors_.clear();
+
+  return DoParse(response, &results_);
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/protocol_parser.h b/src/components/update_client/protocol_parser.h
new file mode 100644
index 0000000..471fd6d
--- /dev/null
+++ b/src/components/update_client/protocol_parser.h
@@ -0,0 +1,128 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_H_
+
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+class ProtocolParser {
+ public:
+  // The result of parsing one |app| entity in an update check response.
+  struct Result {
+    struct Manifest {
+      struct Package {
+        Package();
+        Package(const Package& other);
+        ~Package();
+
+        // |fingerprint| is optional. It identifies the package, preferably
+        // with a modified sha256 hash of the package in hex format.
+        std::string fingerprint;
+
+        // Attributes for the full update.
+        std::string name;
+        std::string hash_sha256;
+        int64_t size = 0;
+
+        // Attributes for the differential update.
+        std::string namediff;
+        std::string hashdiff_sha256;
+        int64_t sizediff = 0;
+      };
+
+      Manifest();
+      Manifest(const Manifest& other);
+      ~Manifest();
+
+      std::string version;
+      std::string browser_min_version;
+      std::vector<Package> packages;
+    };
+
+    Result();
+    Result(const Result& other);
+    ~Result();
+
+    std::string extension_id;
+
+    // The updatecheck response status.
+    std::string status;
+
+    // The list of fallback urls, for full and diff updates respectively.
+    // These urls are base urls; they don't include the filename.
+    std::vector<GURL> crx_urls;
+    std::vector<GURL> crx_diffurls;
+
+    Manifest manifest;
+
+    // The server has instructed the client to set its [key] to [value] for each
+    // key-value pair in this string.
+    std::map<std::string, std::string> cohort_attrs;
+
+    // The following are the only allowed keys in |cohort_attrs|.
+    static const char kCohort[];
+    static const char kCohortHint[];
+    static const char kCohortName[];
+
+    // Contains the run action returned by the server as part of an update
+    // check response.
+    std::string action_run;
+  };
+
+  static const int kNoDaystart = -1;
+  struct Results {
+    Results();
+    Results(const Results& other);
+    ~Results();
+
+    // This will be >= 0, or kNoDaystart if the <daystart> tag was not present.
+    int daystart_elapsed_seconds = kNoDaystart;
+
+    // This will be >= 0, or kNoDaystart if the <daystart> tag was not present.
+    int daystart_elapsed_days = kNoDaystart;
+    std::vector<Result> list;
+  };
+
+  static std::unique_ptr<ProtocolParser> Create();
+
+  virtual ~ProtocolParser();
+
+  // Parses an update response string into Result data. Returns a bool
+  // indicating success or failure. On success, the results are available by
+  // calling results(). In case of success, only results corresponding to
+  // the update check status |ok| or |noupdate| are included.
+  // The details for any failures are available by calling errors().
+  bool Parse(const std::string& response);
+
+  const Results& results() const { return results_; }
+  const std::string& errors() const { return errors_; }
+
+ protected:
+  ProtocolParser();
+
+  // Appends parse error details to |errors_| string.
+  void ParseError(const char* details, ...);
+
+ private:
+  virtual bool DoParse(const std::string& response, Results* results) = 0;
+
+  Results results_;
+  std::string errors_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProtocolParser);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_H_
diff --git a/src/components/update_client/protocol_parser_json.cc b/src/components/update_client/protocol_parser_json.cc
new file mode 100644
index 0000000..eb022e4
--- /dev/null
+++ b/src/components/update_client/protocol_parser_json.cc
@@ -0,0 +1,337 @@
+// Copyright 2018 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 "components/update_client/protocol_parser_json.h"
+
+#include <utility>
+
+#include "base/json/json_reader.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "components/update_client/protocol_definition.h"
+
+namespace update_client {
+
+namespace {
+
+bool ParseManifest(const base::Value& manifest_node,
+                   ProtocolParser::Result* result,
+                   std::string* error) {
+  if (!manifest_node.is_dict()) {
+    *error = "'manifest' is not a dictionary.";
+  }
+  const auto* version = manifest_node.FindKey("version");
+  if (!version || !version->is_string()) {
+    *error = "Missing version for manifest.";
+    return false;
+  }
+
+  result->manifest.version = version->GetString();
+  if (!base::Version(result->manifest.version).IsValid()) {
+    *error =
+        base::StrCat({"Invalid version: '", result->manifest.version, "'."});
+    return false;
+  }
+
+  // Get the optional minimum browser version.
+  const auto* browser_min_version = manifest_node.FindKey("prodversionmin");
+  if (browser_min_version && browser_min_version->is_string()) {
+    result->manifest.browser_min_version = browser_min_version->GetString();
+    if (!base::Version(result->manifest.browser_min_version).IsValid()) {
+      *error = base::StrCat({"Invalid prodversionmin: '",
+                             result->manifest.browser_min_version, "'."});
+      return false;
+    }
+  }
+
+  const auto* packages_node = manifest_node.FindKey("packages");
+  if (!packages_node || !packages_node->is_dict()) {
+    *error = "Missing packages in manifest or 'packages' is not a dictionary.";
+    return false;
+  }
+  const auto* package_node = packages_node->FindKey("package");
+  if (!package_node || !package_node->is_list()) {
+    *error = "Missing package in packages.";
+    return false;
+  }
+
+  for (const auto& package : package_node->GetList()) {
+    if (!package.is_dict()) {
+      *error = "'package' is not a dictionary.";
+      return false;
+    }
+    ProtocolParser::Result::Manifest::Package p;
+    const auto* name = package.FindKey("name");
+    if (!name || !name->is_string()) {
+      *error = "Missing name for package.";
+      return false;
+    }
+    p.name = name->GetString();
+
+    const auto* namediff = package.FindKey("namediff");
+    if (namediff && namediff->is_string())
+      p.namediff = namediff->GetString();
+
+    const auto* fingerprint = package.FindKey("fp");
+    if (fingerprint && fingerprint->is_string())
+      p.fingerprint = fingerprint->GetString();
+
+    const auto* hash_sha256 = package.FindKey("hash_sha256");
+    if (hash_sha256 && hash_sha256->is_string())
+      p.hash_sha256 = hash_sha256->GetString();
+
+    const auto* size = package.FindKey("size");
+    if (size && (size->is_int() || size->is_double())) {
+      const auto val = size->GetDouble();
+      if (0 <= val && val < kProtocolMaxInt)
+        p.size = size->GetDouble();
+    }
+
+    const auto* hashdiff_sha256 = package.FindKey("hashdiff_sha256");
+    if (hashdiff_sha256 && hashdiff_sha256->is_string())
+      p.hashdiff_sha256 = hashdiff_sha256->GetString();
+
+    const auto* sizediff = package.FindKey("sizediff");
+    if (sizediff && (sizediff->is_int() || sizediff->is_double())) {
+      const auto val = sizediff->GetDouble();
+      if (0 <= val && val < kProtocolMaxInt)
+        p.sizediff = sizediff->GetDouble();
+    }
+
+    result->manifest.packages.push_back(std::move(p));
+  }
+
+  return true;
+}
+
+void ParseActions(const base::Value& actions_node,
+                  ProtocolParser::Result* result) {
+  if (!actions_node.is_dict())
+    return;
+
+  const auto* action_node = actions_node.FindKey("action");
+  if (!action_node || !action_node->is_list())
+    return;
+
+  const auto& action_list = action_node->GetList();
+  if (action_list.empty() || !action_list[0].is_dict())
+    return;
+
+  const auto* run = action_list[0].FindKey("run");
+  if (run && run->is_string())
+    result->action_run = run->GetString();
+}
+
+bool ParseUrls(const base::Value& urls_node,
+               ProtocolParser::Result* result,
+               std::string* error) {
+  if (!urls_node.is_dict()) {
+    *error = "'urls' is not a dictionary.";
+    return false;
+  }
+  const auto* url_node = urls_node.FindKey("url");
+  if (!url_node || !url_node->is_list()) {
+    *error = "Missing url on urls.";
+    return false;
+  }
+
+  for (const auto& url : url_node->GetList()) {
+    if (!url.is_dict())
+      continue;
+    const auto* codebase = url.FindKey("codebase");
+    if (codebase && codebase->is_string()) {
+      GURL crx_url(codebase->GetString());
+      if (crx_url.is_valid())
+        result->crx_urls.push_back(std::move(crx_url));
+    }
+    const auto* codebasediff = url.FindKey("codebasediff");
+    if (codebasediff && codebasediff->is_string()) {
+      GURL crx_diffurl(codebasediff->GetString());
+      if (crx_diffurl.is_valid())
+        result->crx_diffurls.push_back(std::move(crx_diffurl));
+    }
+  }
+
+  // Expect at least one url for full update.
+  if (result->crx_urls.empty()) {
+    *error = "Missing valid url for full update.";
+    return false;
+  }
+
+  return true;
+}
+
+bool ParseUpdateCheck(const base::Value& updatecheck_node,
+                      ProtocolParser::Result* result,
+                      std::string* error) {
+  if (!updatecheck_node.is_dict()) {
+    *error = "'updatecheck' is not a dictionary.";
+    return false;
+  }
+  const auto* status = updatecheck_node.FindKey("status");
+  if (!status || !status->is_string()) {
+    *error = "Missing status on updatecheck node";
+    return false;
+  }
+
+  result->status = status->GetString();
+  if (result->status == "noupdate") {
+    const auto* actions_node = updatecheck_node.FindKey("actions");
+    if (actions_node)
+      ParseActions(*actions_node, result);
+    return true;
+  }
+
+  if (result->status == "ok") {
+    const auto* actions_node = updatecheck_node.FindKey("actions");
+    if (actions_node)
+      ParseActions(*actions_node, result);
+
+    const auto* urls_node = updatecheck_node.FindKey("urls");
+    if (!urls_node) {
+      *error = "Missing urls on updatecheck.";
+      return false;
+    }
+
+    if (!ParseUrls(*urls_node, result, error))
+      return false;
+
+    const auto* manifest_node = updatecheck_node.FindKey("manifest");
+    if (!manifest_node) {
+      *error = "Missing manifest on updatecheck.";
+      return false;
+    }
+    return ParseManifest(*manifest_node, result, error);
+  }
+
+  // Return the |updatecheck| element status as a parsing error.
+  *error = result->status;
+  return false;
+}
+
+bool ParseApp(const base::Value& app_node,
+              ProtocolParser::Result* result,
+              std::string* error) {
+  if (!app_node.is_dict()) {
+    *error = "'app' is not a dictionary.";
+    return false;
+  }
+  for (const auto* cohort_key :
+       {ProtocolParser::Result::kCohort, ProtocolParser::Result::kCohortHint,
+        ProtocolParser::Result::kCohortName}) {
+    const auto* cohort_value = app_node.FindKey(cohort_key);
+    if (cohort_value && cohort_value->is_string())
+      result->cohort_attrs[cohort_key] = cohort_value->GetString();
+  }
+  const auto* appid = app_node.FindKey("appid");
+  if (appid && appid->is_string())
+    result->extension_id = appid->GetString();
+  if (result->extension_id.empty()) {
+    *error = "Missing appid on app node";
+    return false;
+  }
+
+  // Read the |status| attribute for the app.
+  // If the status is one of the defined app status error literals, then return
+  // it in the result as if it were an updatecheck status, then stop parsing,
+  // and return success.
+  const auto* status = app_node.FindKey("status");
+  if (status && status->is_string()) {
+    result->status = status->GetString();
+    if (result->status == "restricted" ||
+        result->status == "error-unknownApplication" ||
+        result->status == "error-invalidAppId")
+      return true;
+
+    // If the status was not handled above and the status is not "ok", then
+    // this must be a status literal that that the parser does not know about.
+    if (!result->status.empty() && result->status != "ok") {
+      *error = "Unknown app status";
+      return false;
+    }
+  }
+
+  DCHECK(result->status.empty() || result->status == "ok");
+  const auto* updatecheck_node = app_node.FindKey("updatecheck");
+  if (!updatecheck_node) {
+    *error = "Missing updatecheck on app.";
+    return false;
+  }
+
+  return ParseUpdateCheck(*updatecheck_node, result, error);
+}
+
+}  // namespace
+
+bool ProtocolParserJSON::DoParse(const std::string& response_json,
+                                 Results* results) {
+  DCHECK(results);
+
+  if (response_json.empty()) {
+    ParseError("Empty JSON.");
+    return false;
+  }
+
+  // The JSON response contains a prefix to prevent XSSI.
+  constexpr char kJSONPrefix[] = ")]}'";
+  if (!base::StartsWith(response_json, kJSONPrefix,
+                        base::CompareCase::SENSITIVE)) {
+    ParseError("Missing secure JSON prefix.");
+    return false;
+  }
+  const auto doc = base::JSONReader::Read(
+      {response_json.begin() + std::char_traits<char>::length(kJSONPrefix),
+       response_json.end()});
+  if (!doc) {
+    ParseError("JSON read error.");
+    return false;
+  }
+  if (!doc->is_dict()) {
+    ParseError("JSON document is not a dictionary.");
+    return false;
+  }
+  const auto* response_node = doc->FindKey("response");
+  if (!response_node || !response_node->is_dict()) {
+    ParseError("Missing 'response' element or 'response' is not a dictionary.");
+    return false;
+  }
+  const auto* protocol = response_node->FindKey("protocol");
+  if (!protocol || !protocol->is_string()) {
+    ParseError("Missing/non-string protocol.");
+    return false;
+  }
+  if (protocol->GetString() != kProtocolVersion) {
+    ParseError("Incorrect protocol. (expected '%s', found '%s')",
+               kProtocolVersion, protocol->GetString().c_str());
+    return false;
+  }
+
+  const auto* daystart_node = response_node->FindKey("daystart");
+  if (daystart_node && daystart_node->is_dict()) {
+    const auto* elapsed_seconds = daystart_node->FindKey("elapsed_seconds");
+    if (elapsed_seconds && elapsed_seconds->is_int())
+      results->daystart_elapsed_seconds = elapsed_seconds->GetInt();
+    const auto* elapsed_days = daystart_node->FindKey("elapsed_days");
+    if (elapsed_days && elapsed_days->is_int())
+      results->daystart_elapsed_days = elapsed_days->GetInt();
+  }
+
+  const auto* app_node = response_node->FindKey("app");
+  if (app_node && app_node->is_list()) {
+    for (const auto& app : app_node->GetList()) {
+      Result result;
+      std::string error;
+      if (ParseApp(app, &result, &error))
+        results->list.push_back(result);
+      else
+        ParseError("%s", error.c_str());
+    }
+  }
+
+  return true;
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/protocol_parser_json.h b/src/components/update_client/protocol_parser_json.h
new file mode 100644
index 0000000..71f96f4
--- /dev/null
+++ b/src/components/update_client/protocol_parser_json.h
@@ -0,0 +1,30 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_JSON_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_JSON_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/update_client/protocol_parser.h"
+
+namespace update_client {
+
+// Parses responses for the update protocol version 3.
+// (https://github.com/google/omaha/blob/wiki/ServerProtocolV3.md)
+class ProtocolParserJSON final : public ProtocolParser {
+ public:
+  ProtocolParserJSON() = default;
+
+ private:
+  // Overrides for ProtocolParser.
+  bool DoParse(const std::string& response_json, Results* results) override;
+
+  DISALLOW_COPY_AND_ASSIGN(ProtocolParserJSON);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_JSON_H_
diff --git a/src/components/update_client/protocol_parser_json_unittest.cc b/src/components/update_client/protocol_parser_json_unittest.cc
new file mode 100644
index 0000000..5bf114a
--- /dev/null
+++ b/src/components/update_client/protocol_parser_json_unittest.cc
@@ -0,0 +1,506 @@
+// Copyright 2018 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 "components/update_client/protocol_parser_json.h"
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+const char* kJSONValid = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"},
+                    {"codebasediff":"http://diff.example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONHash = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx",
+                              "hash_sha256":"1234",
+                              "hashdiff_sha256":"5678"}]}}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONInvalidSizes = R"()]}'
+ {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"1","size":1234},
+                             {"name":"2","size":9007199254740991},
+                             {"name":"3","size":-1234},
+                             {"name":"4"},
+                             {"name":"5","size":"-a"},
+                             {"name":"6","size":-123467890123456789},
+                             {"name":"7","size":123467890123456789},
+                             {"name":"8","sizediff":1234},
+                             {"name":"9","sizediff":9007199254740991},
+                             {"name":"10","sizediff":-1234},
+                             {"name":"11","sizediff":"-a"},
+                             {"name":"12","sizediff":-123467890123456789},
+                             {"name":"13","sizediff":123467890123456789}]}}
+      }
+     }
+    ]
+   }})";
+
+const char* kJSONInvalidMissingCodebase = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebasediff":"http://diff.example.com"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"namediff":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONInvalidMissingManifest = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://localhost/download/"}]}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONMissingAppId = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://localhost/download/"}]}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONInvalidCodebase = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"example.com/extension_1.2.3.4.crx",
+                     "version":"1.2.3.4"}]}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONMissingVersion = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://localhost/download/"}]},
+     "manifest":{
+      "packages":{"package":[{"name":"jebgalgnebhfojomionfpkfelancnnkf.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONInvalidVersion = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://localhost/download/"}]},
+     "manifest":{
+      "version":"1.2.3.a",
+      "packages":{"package":[{"name":"jebgalgnebhfojomionfpkfelancnnkf.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+// Includes a <daystart> tag.
+const char* kJSONWithDaystart = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "daystart":{"elapsed_seconds":456},
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"},
+                    {"codebasediff":"http://diff.example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+// Indicates no updates available.
+const char* kJSONNoUpdate = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"noupdate"
+     }
+    }
+   ]
+  }})";
+
+// Includes two app objects, one app with an error.
+const char* kJSONTwoAppsOneError = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "daystart":{"elapsed_seconds":456},
+   "app":[
+    {"appid":"aaaaaaaa",
+     "status":"error-unknownApplication",
+     "updatecheck":{"status":"error-internal"}
+    },
+    {"appid":"bbbbbbbb",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+// Includes two <app> tags, both of which set the cohort.
+const char* kJSONTwoAppsSetCohort = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "daystart":{"elapsed_seconds":456},
+   "app":[
+    {"appid":"aaaaaaaa",
+     "cohort":"1:2q3/",
+     "updatecheck":{"status":"noupdate"}
+    },
+    {"appid":"bbbbbbbb",
+     "cohort":"1:33z@0.33",
+     "cohortname":"cname",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+// Includes a run action for an update check with status='ok'.
+const char* kJSONUpdateCheckStatusOkWithRunAction = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "updatecheck":{
+     "status":"ok",
+     "actions":{"action":[{"run":"this"}]},
+     "urls":{"url":[{"codebase":"http://example.com/"},
+                    {"codebasediff":"http://diff.example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+// Includes a run action for an update check with status='noupdate'.
+const char* kJSONUpdateCheckStatusNoUpdateWithRunAction = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "updatecheck":{
+     "status":"noupdate",
+     "actions":{"action":[{"run":"this"}]}
+     }
+    }
+   ]
+  }})";
+
+// Includes a run action for an update check with status='error'.
+const char* kJSONUpdateCheckStatusErrorWithRunAction = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "updatecheck":{
+     "status":"error-osnotsupported",
+     "actions":{"action":[{"run":"this"}]}
+     }
+    }
+   ]
+  }})";
+
+// Includes four app objects with status different than 'ok'.
+const char* kJSONAppsStatusError = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"aaaaaaaa",
+     "status":"error-unknownApplication",
+     "updatecheck":{"status":"error-internal"}
+    },
+    {"appid":"bbbbbbbb",
+     "status":"restricted",
+     "updatecheck":{"status":"error-internal"}
+    },
+    {"appid":"cccccccc",
+     "status":"error-invalidAppId",
+     "updatecheck":{"status":"error-internal"}
+    },
+    {"appid":"dddddddd",
+     "status":"foobar",
+     "updatecheck":{"status":"error-internal"}
+    }
+   ]
+  }})";
+
+TEST(UpdateClientProtocolParserJSONTest, Parse) {
+  const auto parser = std::make_unique<ProtocolParserJSON>();
+
+  // Test parsing of a number of invalid JSON cases
+  EXPECT_FALSE(parser->Parse(std::string()));
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONMissingAppId));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONInvalidCodebase));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONMissingVersion));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONInvalidVersion));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONInvalidMissingCodebase));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONInvalidMissingManifest));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  {
+    // Parse some valid XML, and check that all params came out as expected.
+    EXPECT_TRUE(parser->Parse(kJSONValid));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_EQ(1u, parser->results().list.size());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_STREQ("ok", first_result->status.c_str());
+    EXPECT_EQ(1u, first_result->crx_urls.size());
+    EXPECT_EQ(GURL("http://example.com/"), first_result->crx_urls[0]);
+    EXPECT_EQ(GURL("http://diff.example.com/"), first_result->crx_diffurls[0]);
+    EXPECT_EQ("1.2.3.4", first_result->manifest.version);
+    EXPECT_EQ("2.0.143.0", first_result->manifest.browser_min_version);
+    EXPECT_EQ(1u, first_result->manifest.packages.size());
+    EXPECT_EQ("extension_1_2_3_4.crx", first_result->manifest.packages[0].name);
+  }
+  {
+    // Parse xml with hash value.
+    EXPECT_TRUE(parser->Parse(kJSONHash));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_FALSE(first_result->manifest.packages.empty());
+    EXPECT_EQ("1234", first_result->manifest.packages[0].hash_sha256);
+    EXPECT_EQ("5678", first_result->manifest.packages[0].hashdiff_sha256);
+  }
+  {
+    // Parse xml with package size value.
+    EXPECT_TRUE(parser->Parse(kJSONInvalidSizes));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_FALSE(first_result->manifest.packages.empty());
+    EXPECT_EQ(1234, first_result->manifest.packages[0].size);
+    EXPECT_EQ(9007199254740991, first_result->manifest.packages[1].size);
+    EXPECT_EQ(0, first_result->manifest.packages[2].size);
+    EXPECT_EQ(0, first_result->manifest.packages[3].size);
+    EXPECT_EQ(0, first_result->manifest.packages[4].size);
+    EXPECT_EQ(0, first_result->manifest.packages[5].size);
+    EXPECT_EQ(0, first_result->manifest.packages[6].size);
+    EXPECT_EQ(1234, first_result->manifest.packages[7].sizediff);
+    EXPECT_EQ(9007199254740991, first_result->manifest.packages[8].sizediff);
+    EXPECT_EQ(0, first_result->manifest.packages[9].sizediff);
+    EXPECT_EQ(0, first_result->manifest.packages[10].sizediff);
+    EXPECT_EQ(0, first_result->manifest.packages[11].sizediff);
+    EXPECT_EQ(0, first_result->manifest.packages[12].sizediff);
+  }
+  {
+    // Parse xml with a <daystart> element.
+    EXPECT_TRUE(parser->Parse(kJSONWithDaystart));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    EXPECT_EQ(parser->results().daystart_elapsed_seconds, 456);
+  }
+  {
+    // Parse a no-update response.
+    EXPECT_TRUE(parser->Parse(kJSONNoUpdate));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_STREQ("noupdate", first_result->status.c_str());
+    EXPECT_EQ(first_result->extension_id, "12345");
+    EXPECT_EQ(first_result->manifest.version, "");
+  }
+  {
+    // Parse xml with one error and one success <app> tag.
+    EXPECT_TRUE(parser->Parse(kJSONTwoAppsOneError));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_EQ(2u, parser->results().list.size());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
+    EXPECT_STREQ("error-unknownApplication", first_result->status.c_str());
+    EXPECT_TRUE(first_result->manifest.version.empty());
+    const auto* second_result = &parser->results().list[1];
+    EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
+    EXPECT_STREQ("ok", second_result->status.c_str());
+    EXPECT_EQ("1.2.3.4", second_result->manifest.version);
+  }
+  {
+    // Parse xml with two apps setting the cohort info.
+    EXPECT_TRUE(parser->Parse(kJSONTwoAppsSetCohort));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_EQ(2u, parser->results().list.size());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
+    EXPECT_NE(first_result->cohort_attrs.find("cohort"),
+              first_result->cohort_attrs.end());
+    EXPECT_EQ(first_result->cohort_attrs.find("cohort")->second, "1:2q3/");
+    EXPECT_EQ(first_result->cohort_attrs.find("cohortname"),
+              first_result->cohort_attrs.end());
+    EXPECT_EQ(first_result->cohort_attrs.find("cohorthint"),
+              first_result->cohort_attrs.end());
+    const auto* second_result = &parser->results().list[1];
+    EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
+    EXPECT_NE(second_result->cohort_attrs.find("cohort"),
+              second_result->cohort_attrs.end());
+    EXPECT_EQ(second_result->cohort_attrs.find("cohort")->second, "1:33z@0.33");
+    EXPECT_NE(second_result->cohort_attrs.find("cohortname"),
+              second_result->cohort_attrs.end());
+    EXPECT_EQ(second_result->cohort_attrs.find("cohortname")->second, "cname");
+    EXPECT_EQ(second_result->cohort_attrs.find("cohorthint"),
+              second_result->cohort_attrs.end());
+  }
+  {
+    EXPECT_TRUE(parser->Parse(kJSONUpdateCheckStatusOkWithRunAction));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_STREQ("ok", first_result->status.c_str());
+    EXPECT_EQ(first_result->extension_id, "12345");
+    EXPECT_STREQ("this", first_result->action_run.c_str());
+  }
+  {
+    EXPECT_TRUE(parser->Parse(kJSONUpdateCheckStatusNoUpdateWithRunAction));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_STREQ("noupdate", first_result->status.c_str());
+    EXPECT_EQ(first_result->extension_id, "12345");
+    EXPECT_STREQ("this", first_result->action_run.c_str());
+  }
+  {
+    EXPECT_TRUE(parser->Parse(kJSONUpdateCheckStatusErrorWithRunAction));
+    EXPECT_FALSE(parser->errors().empty());
+    EXPECT_TRUE(parser->results().list.empty());
+  }
+  {
+    EXPECT_TRUE(parser->Parse(kJSONAppsStatusError));
+    EXPECT_STREQ("Unknown app status", parser->errors().c_str());
+    EXPECT_EQ(3u, parser->results().list.size());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
+    EXPECT_STREQ("error-unknownApplication", first_result->status.c_str());
+    EXPECT_TRUE(first_result->manifest.version.empty());
+    const auto* second_result = &parser->results().list[1];
+    EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
+    EXPECT_STREQ("restricted", second_result->status.c_str());
+    EXPECT_TRUE(second_result->manifest.version.empty());
+    const auto* third_result = &parser->results().list[2];
+    EXPECT_EQ(third_result->extension_id, "cccccccc");
+    EXPECT_STREQ("error-invalidAppId", third_result->status.c_str());
+    EXPECT_TRUE(third_result->manifest.version.empty());
+  }
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/protocol_serializer.cc b/src/components/update_client/protocol_serializer.cc
new file mode 100644
index 0000000..a03ff79
--- /dev/null
+++ b/src/components/update_client/protocol_serializer.cc
@@ -0,0 +1,254 @@
+// Copyright 2018 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 "components/update_client/protocol_serializer.h"
+
+#include <utility>
+
+#include "base/containers/flat_map.h"
+#include "base/guid.h"
+#include "base/logging.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "base/version.h"
+#include "build/build_config.h"
+#include "components/update_client/activity_data_service.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/update_query_params.h"
+#include "components/update_client/updater_state.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace update_client {
+
+namespace {
+
+// Returns the amount of physical memory in GB, rounded to the nearest GB.
+int GetPhysicalMemoryGB() {
+  const double kOneGB = 1024 * 1024 * 1024;
+  const int64_t phys_mem = base::SysInfo::AmountOfPhysicalMemory();
+  return static_cast<int>(std::floor(0.5 + phys_mem / kOneGB));
+}
+
+std::string GetOSVersion() {
+#if defined(OS_WIN)
+  const auto ver = base::win::OSInfo::GetInstance()->version_number();
+  return base::StringPrintf("%d.%d.%d.%d", ver.major, ver.minor, ver.build,
+                            ver.patch);
+#elif defined(OS_STARBOARD)
+  // TODO: fill in OS version.
+  return "";
+#else
+  return base::SysInfo().OperatingSystemVersion();
+#endif
+}
+
+std::string GetServicePack() {
+#if defined(OS_WIN)
+  return base::win::OSInfo::GetInstance()->service_pack_str();
+#else
+  return {};
+#endif
+}
+
+}  // namespace
+
+base::flat_map<std::string, std::string> BuildUpdateCheckExtraRequestHeaders(
+    const std::string& prod_id,
+    const base::Version& browser_version,
+    const std::vector<std::string>& ids,
+    bool is_foreground) {
+  // This number of extension ids results in an HTTP header length of about 1KB.
+  constexpr size_t maxIdsCount = 30;
+  const std::vector<std::string>& app_ids =
+      ids.size() <= maxIdsCount
+          ? ids
+          : std::vector<std::string>(ids.cbegin(), ids.cbegin() + maxIdsCount);
+  return {
+      {"X-Goog-Update-Updater",
+       base::StrCat({prod_id, "-", browser_version.GetString()})},
+      {"X-Goog-Update-Interactivity", is_foreground ? "fg" : "bg"},
+      {"X-Goog-Update-AppId", base::JoinString(app_ids, ",")},
+  };
+}
+
+protocol_request::Request MakeProtocolRequest(
+    const std::string& session_id,
+    const std::string& prod_id,
+    const std::string& browser_version,
+    const std::string& lang,
+    const std::string& channel,
+    const std::string& os_long_name,
+    const std::string& download_preference,
+    const base::flat_map<std::string, std::string>& additional_attributes,
+    const std::map<std::string, std::string>* updater_state_attributes,
+    std::vector<protocol_request::App> apps) {
+  protocol_request::Request request;
+  request.protocol_version = kProtocolVersion;
+
+  // Session id and request id.
+  DCHECK(!session_id.empty());
+  DCHECK(base::StartsWith(session_id, "{", base::CompareCase::SENSITIVE));
+  DCHECK(base::EndsWith(session_id, "}", base::CompareCase::SENSITIVE));
+  request.session_id = session_id;
+  request.request_id = base::StrCat({"{", base::GenerateGUID(), "}"});
+
+  request.updatername = prod_id;
+  request.updaterversion = browser_version;
+  request.prodversion = browser_version;
+  request.lang = lang;
+  request.updaterchannel = channel;
+  request.prodchannel = channel;
+  request.operating_system = UpdateQueryParams::GetOS();
+  request.arch = UpdateQueryParams::GetArch();
+  request.nacl_arch = UpdateQueryParams::GetNaclArch();
+  request.dlpref = download_preference;
+  request.additional_attributes = additional_attributes;
+
+#if defined(OS_WIN)
+  if (base::win::OSInfo::GetInstance()->wow64_status() ==
+      base::win::OSInfo::WOW64_ENABLED)
+    request.is_wow64 = true;
+#endif
+
+  if (updater_state_attributes &&
+      updater_state_attributes->count(UpdaterState::kIsEnterpriseManaged)) {
+    request.domain_joined =
+        updater_state_attributes->at(UpdaterState::kIsEnterpriseManaged) == "1";
+  }
+
+  // HW platform information.
+  request.hw.physmemory = GetPhysicalMemoryGB();
+
+  // OS version and platform information.
+  request.os.platform = os_long_name;
+  request.os.version = GetOSVersion();
+  request.os.service_pack = GetServicePack();
+#if defined(OS_STARBOARD)
+  // TODO: fill in arch.
+  request.os.arch = "";
+#else
+  request.os.arch = base::SysInfo().OperatingSystemArchitecture();
+#endif
+
+  if (updater_state_attributes) {
+    request.updater = base::make_optional<protocol_request::Updater>();
+    auto it = updater_state_attributes->find("name");
+    if (it != updater_state_attributes->end())
+      request.updater->name = it->second;
+    it = updater_state_attributes->find("version");
+    if (it != updater_state_attributes->end())
+      request.updater->version = it->second;
+    it = updater_state_attributes->find("ismachine");
+    if (it != updater_state_attributes->end()) {
+      DCHECK(it->second == "0" || it->second == "1");
+      request.updater->is_machine = it->second != "0";
+    }
+    it = updater_state_attributes->find("autoupdatecheckenabled");
+    if (it != updater_state_attributes->end()) {
+      DCHECK(it->second == "0" || it->second == "1");
+      request.updater->autoupdate_check_enabled = it->second != "0";
+    }
+    it = updater_state_attributes->find("laststarted");
+    if (it != updater_state_attributes->end()) {
+      int last_started = 0;
+      if (base::StringToInt(it->second, &last_started))
+        request.updater->last_started = last_started;
+    }
+    it = updater_state_attributes->find("lastchecked");
+    if (it != updater_state_attributes->end()) {
+      int last_checked = 0;
+      if (base::StringToInt(it->second, &last_checked))
+        request.updater->last_checked = last_checked;
+    }
+    it = updater_state_attributes->find("updatepolicy");
+    if (it != updater_state_attributes->end()) {
+      int update_policy = 0;
+      if (base::StringToInt(it->second, &update_policy))
+        request.updater->update_policy = update_policy;
+    }
+  }
+
+  request.apps = std::move(apps);
+  return request;
+}
+
+protocol_request::App MakeProtocolApp(
+    const std::string& app_id,
+    const base::Version& version,
+    base::Optional<std::vector<base::Value>> events) {
+  protocol_request::App app;
+  app.app_id = app_id;
+  app.version = version.GetString();
+  app.events = std::move(events);
+  return app;
+}
+
+protocol_request::App MakeProtocolApp(
+    const std::string& app_id,
+    const base::Version& version,
+    const std::string& brand_code,
+    const std::string& install_source,
+    const std::string& install_location,
+    const std::string& fingerprint,
+    const base::flat_map<std::string, std::string>& installer_attributes,
+    const std::string& cohort,
+    const std::string& cohort_hint,
+    const std::string& cohort_name,
+    const std::vector<int>& disabled_reasons,
+    base::Optional<protocol_request::UpdateCheck> update_check,
+    base::Optional<protocol_request::Ping> ping) {
+  auto app = MakeProtocolApp(app_id, version, base::nullopt);
+  app.brand_code = brand_code;
+  app.install_source = install_source;
+  app.install_location = install_location;
+  app.fingerprint = fingerprint;
+  app.installer_attributes = installer_attributes;
+  app.cohort = cohort;
+  app.cohort_hint = cohort_hint;
+  app.cohort_name = cohort_name;
+  app.enabled = disabled_reasons.empty();
+  app.disabled_reasons = disabled_reasons;
+  app.update_check = std::move(update_check);
+  app.ping = std::move(ping);
+  return app;
+}
+
+protocol_request::UpdateCheck MakeProtocolUpdateCheck(bool is_update_disabled) {
+  protocol_request::UpdateCheck update_check;
+  update_check.is_update_disabled = is_update_disabled;
+  return update_check;
+}
+
+protocol_request::Ping MakeProtocolPing(const std::string& app_id,
+                                        const PersistedData* metadata) {
+  DCHECK(metadata);
+  protocol_request::Ping ping;
+
+  if (metadata->GetActiveBit(app_id)) {
+    const int date_last_active = metadata->GetDateLastActive(app_id);
+    if (date_last_active != kDateUnknown) {
+      ping.date_last_active = date_last_active;
+    } else {
+      ping.days_since_last_active_ping =
+          metadata->GetDaysSinceLastActive(app_id);
+    }
+  }
+  const int date_last_roll_call = metadata->GetDateLastRollCall(app_id);
+  if (date_last_roll_call != kDateUnknown) {
+    ping.date_last_roll_call = date_last_roll_call;
+  } else {
+    ping.days_since_last_roll_call = metadata->GetDaysSinceLastRollCall(app_id);
+  }
+  ping.ping_freshness = metadata->GetPingFreshness(app_id);
+
+  return ping;
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/protocol_serializer.h b/src/components/update_client/protocol_serializer.h
new file mode 100644
index 0000000..47c212a
--- /dev/null
+++ b/src/components/update_client/protocol_serializer.h
@@ -0,0 +1,84 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/protocol_definition.h"
+
+namespace base {
+class Version;
+}
+
+namespace update_client {
+
+class PersistedData;
+
+// Creates the values for the DDOS extra request headers sent with the update
+// check. These headers include "X-Goog-Update-Updater",
+// "X-Goog-Update-AppId", and  "X-Goog-Update-Interactivity".
+base::flat_map<std::string, std::string> BuildUpdateCheckExtraRequestHeaders(
+    const std::string& prod_id,
+    const base::Version& browser_version,
+    const std::vector<std::string>& ids_checked,
+    bool is_foreground);
+
+protocol_request::Request MakeProtocolRequest(
+    const std::string& session_id,
+    const std::string& prod_id,
+    const std::string& browser_version,
+    const std::string& lang,
+    const std::string& channel,
+    const std::string& os_long_name,
+    const std::string& download_preference,
+    const base::flat_map<std::string, std::string>& additional_attributes,
+    const std::map<std::string, std::string>* updater_state_attributes,
+    std::vector<protocol_request::App> apps);
+
+protocol_request::App MakeProtocolApp(
+    const std::string& app_id,
+    const base::Version& version,
+    base::Optional<std::vector<base::Value>> events);
+
+protocol_request::App MakeProtocolApp(
+    const std::string& app_id,
+    const base::Version& version,
+    const std::string& brand_code,
+    const std::string& install_source,
+    const std::string& install_location,
+    const std::string& fingerprint,
+    const base::flat_map<std::string, std::string>& installer_attributes,
+    const std::string& cohort,
+    const std::string& cohort_hint,
+    const std::string& cohort_name,
+    const std::vector<int>& disabled_reasons,
+    base::Optional<protocol_request::UpdateCheck> update_check,
+    base::Optional<protocol_request::Ping> ping);
+
+protocol_request::UpdateCheck MakeProtocolUpdateCheck(bool is_update_disabled);
+
+protocol_request::Ping MakeProtocolPing(const std::string& app_id,
+                                        const PersistedData* metadata);
+
+class ProtocolSerializer {
+ public:
+  virtual ~ProtocolSerializer() = default;
+  virtual std::string Serialize(
+      const protocol_request::Request& request) const = 0;
+
+ protected:
+  ProtocolSerializer() = default;
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_H_
diff --git a/src/components/update_client/protocol_serializer_json.cc b/src/components/update_client/protocol_serializer_json.cc
new file mode 100644
index 0000000..b94d2c0
--- /dev/null
+++ b/src/components/update_client/protocol_serializer_json.cc
@@ -0,0 +1,183 @@
+// Copyright 2018 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 "components/update_client/protocol_serializer_json.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/json/json_writer.h"
+#include "base/values.h"
+#include "build/build_config.h"
+
+#include "components/update_client/updater_state.h"
+
+namespace update_client {
+
+using Value = base::Value;
+
+std::string ProtocolSerializerJSON::Serialize(
+    const protocol_request::Request& request) const {
+  Value root_node(Value::Type::DICTIONARY);
+  auto* request_node =
+      root_node.SetKey("request", Value(Value::Type::DICTIONARY));
+  request_node->SetKey("protocol", Value(request.protocol_version));
+  request_node->SetKey("dedup", Value("cr"));
+  request_node->SetKey("acceptformat", Value("crx2,crx3"));
+  if (!request.additional_attributes.empty()) {
+    for (const auto& attr : request.additional_attributes)
+      request_node->SetKey(attr.first, Value(attr.second));
+  }
+  request_node->SetKey("sessionid", Value(request.session_id));
+  request_node->SetKey("requestid", Value(request.request_id));
+  request_node->SetKey("@updater", Value(request.updatername));
+  request_node->SetKey("prodversion", Value(request.updaterversion));
+  request_node->SetKey("updaterversion", Value(request.prodversion));
+  request_node->SetKey("lang", Value(request.lang));
+  request_node->SetKey("@os", Value(request.operating_system));
+  request_node->SetKey("arch", Value(request.arch));
+  request_node->SetKey("nacl_arch", Value(request.nacl_arch));
+#if defined(OS_WIN)
+  if (request.is_wow64)
+    request_node->SetKey("wow64", Value(request.is_wow64));
+#endif  // OS_WIN
+  if (!request.updaterchannel.empty())
+    request_node->SetKey("updaterchannel", Value(request.updaterchannel));
+  if (!request.prodchannel.empty())
+    request_node->SetKey("prodchannel", Value(request.prodchannel));
+  if (!request.dlpref.empty())
+    request_node->SetKey("dlpref", Value(request.dlpref));
+  if (request.domain_joined) {
+    request_node->SetKey(UpdaterState::kIsEnterpriseManaged,
+                         Value(*request.domain_joined));
+  }
+
+  // HW platform information.
+  auto* hw_node = request_node->SetKey("hw", Value(Value::Type::DICTIONARY));
+  hw_node->SetKey("physmemory", Value(static_cast<int>(request.hw.physmemory)));
+
+  // OS version and platform information.
+  auto* os_node = request_node->SetKey("os", Value(Value::Type::DICTIONARY));
+  os_node->SetKey("platform", Value(request.os.platform));
+  os_node->SetKey("arch", Value(request.os.arch));
+  if (!request.os.version.empty())
+    os_node->SetKey("version", Value(request.os.version));
+  if (!request.os.service_pack.empty())
+    os_node->SetKey("sp", Value(request.os.service_pack));
+
+#if defined(GOOGLE_CHROME_BUILD)
+  if (request.updater) {
+    const auto& updater = *request.updater;
+    auto* updater_node =
+        request_node->SetKey("updater", Value(Value::Type::DICTIONARY));
+    updater_node->SetKey("name", Value(updater.name));
+    updater_node->SetKey("ismachine", Value(updater.is_machine));
+    updater_node->SetKey("autoupdatecheckenabled",
+                         Value(updater.autoupdate_check_enabled));
+    updater_node->SetKey("updatepolicy", Value(updater.update_policy));
+    if (!updater.version.empty())
+      updater_node->SetKey("version", Value(updater.version));
+    if (updater.last_checked)
+      updater_node->SetKey("lastchecked", Value(*updater.last_checked));
+    if (updater.last_started)
+      updater_node->SetKey("laststarted", Value(*updater.last_started));
+  }
+#endif
+
+  std::vector<Value> app_nodes;
+  for (const auto& app : request.apps) {
+    Value app_node(Value::Type::DICTIONARY);
+    app_node.SetKey("appid", Value(app.app_id));
+    app_node.SetKey("version", Value(app.version));
+    if (!app.brand_code.empty())
+      app_node.SetKey("brand", Value(app.brand_code));
+    if (!app.install_source.empty())
+      app_node.SetKey("installsource", Value(app.install_source));
+    if (!app.install_location.empty())
+      app_node.SetKey("installedby", Value(app.install_location));
+    if (!app.cohort.empty())
+      app_node.SetKey("cohort", Value(app.cohort));
+    if (!app.cohort_name.empty())
+      app_node.SetKey("cohortname", Value(app.cohort_name));
+    if (!app.cohort_hint.empty())
+      app_node.SetKey("cohorthint", Value(app.cohort_hint));
+    if (app.enabled)
+      app_node.SetKey("enabled", Value(*app.enabled));
+
+    if (app.disabled_reasons && !app.disabled_reasons->empty()) {
+      std::vector<Value> disabled_nodes;
+      for (const int disabled_reason : *app.disabled_reasons) {
+        Value disabled_node(Value::Type::DICTIONARY);
+        disabled_node.SetKey("reason", Value(disabled_reason));
+        disabled_nodes.push_back(std::move(disabled_node));
+      }
+      app_node.SetKey("disabled", Value(disabled_nodes));
+    }
+
+    for (const auto& attr : app.installer_attributes)
+      app_node.SetKey(attr.first, Value(attr.second));
+
+    if (app.update_check) {
+      auto* update_check_node =
+          app_node.SetKey("updatecheck", Value(Value::Type::DICTIONARY));
+      if (app.update_check->is_update_disabled)
+        update_check_node->SetKey("updatedisabled", Value(true));
+    }
+
+    if (app.ping) {
+      const auto& ping = *app.ping;
+      auto* ping_node = app_node.SetKey("ping", Value(Value::Type::DICTIONARY));
+      if (!ping.ping_freshness.empty())
+        ping_node->SetKey("ping_freshness", Value(ping.ping_freshness));
+
+      // Output "ad" or "a" only if the this app has been seen 'active'.
+      if (ping.date_last_active) {
+        ping_node->SetKey("ad", Value(*ping.date_last_active));
+      } else if (ping.days_since_last_active_ping) {
+        ping_node->SetKey("a", Value(*ping.days_since_last_active_ping));
+      }
+
+      // Output "rd" if valid or "r" as a last resort roll call metric.
+      if (ping.date_last_roll_call)
+        ping_node->SetKey("rd", Value(*ping.date_last_roll_call));
+      else
+        ping_node->SetKey("r", Value(ping.days_since_last_roll_call));
+    }
+
+    if (!app.fingerprint.empty()) {
+      std::vector<Value> package_nodes;
+      Value package(Value::Type::DICTIONARY);
+      package.SetKey("fp", Value(app.fingerprint));
+      package_nodes.push_back(std::move(package));
+      auto* packages_node =
+          app_node.SetKey("packages", Value(Value::Type::DICTIONARY));
+      packages_node->SetKey("package", Value(package_nodes));
+    }
+
+    if (app.events) {
+      std::vector<Value> event_nodes;
+      for (const auto& event : *app.events) {
+        DCHECK(event.is_dict());
+        DCHECK(!event.DictEmpty());
+        event_nodes.push_back(event.Clone());
+      }
+      app_node.SetKey("event", Value(event_nodes));
+    }
+
+    app_nodes.push_back(std::move(app_node));
+  }
+
+  if (!app_nodes.empty())
+    request_node->SetKey("app", Value(std::move(app_nodes)));
+
+  std::string msg;
+  return base::JSONWriter::WriteWithOptions(
+             root_node, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION,
+             &msg)
+             ? msg
+             : std::string();
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/protocol_serializer_json.h b/src/components/update_client/protocol_serializer_json.h
new file mode 100644
index 0000000..71f2a99
--- /dev/null
+++ b/src/components/update_client/protocol_serializer_json.h
@@ -0,0 +1,28 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_JSON_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_JSON_H_
+
+#include <string>
+
+#include "components/update_client/protocol_serializer.h"
+
+namespace update_client {
+
+class ProtocolSerializerJSON final : public ProtocolSerializer {
+ public:
+  ProtocolSerializerJSON() = default;
+
+  // Overrides for ProtocolSerializer.
+  std::string Serialize(
+      const protocol_request::Request& request) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProtocolSerializerJSON);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_JSON_H_
diff --git a/src/components/update_client/protocol_serializer_json_unittest.cc b/src/components/update_client/protocol_serializer_json_unittest.cc
new file mode 100644
index 0000000..1f4ce4a
--- /dev/null
+++ b/src/components/update_client/protocol_serializer_json_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright 2018 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 <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/optional.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/update_client/activity_data_service.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/protocol_definition.h"
+#include "components/update_client/protocol_serializer.h"
+#include "components/update_client/protocol_serializer_json.h"
+#include "components/update_client/updater_state.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/re2/src/re2/re2.h"
+
+using base::Value;
+using std::string;
+
+namespace update_client {
+
+TEST(SerializeRequestJSON, Serialize) {
+  // When no updater state is provided, then check that the elements and
+  // attributes related to the updater state are not serialized.
+
+  std::vector<base::Value> events;
+  events.push_back(Value(Value::Type::DICTIONARY));
+  events.push_back(Value(Value::Type::DICTIONARY));
+  events[0].SetKey("a", Value(1));
+  events[0].SetKey("b", Value("2"));
+  events[1].SetKey("error", Value(0));
+
+  auto pref = std::make_unique<TestingPrefServiceSimple>();
+  PersistedData::RegisterPrefs(pref->registry());
+  auto metadata = std::make_unique<PersistedData>(pref.get(), nullptr);
+  std::vector<std::string> items = {"id1"};
+  metadata->SetDateLastRollCall(items, 1234);
+
+  std::vector<protocol_request::App> apps;
+  apps.push_back(MakeProtocolApp(
+      "id1", base::Version("1.0"), "brand1", "source1", "location1", "fp1",
+      {{"attr1", "1"}, {"attr2", "2"}}, "c1", "ch1", "cn1", {0, 1},
+      MakeProtocolUpdateCheck(true), MakeProtocolPing("id1", metadata.get())));
+  apps.push_back(
+      MakeProtocolApp("id2", base::Version("2.0"), std::move(events)));
+
+  const auto request = std::make_unique<ProtocolSerializerJSON>()->Serialize(
+      MakeProtocolRequest("{15160585-8ADE-4D3C-839B-1281A6035D1F}", "prod_id",
+                          "1.0", "lang", "channel", "OS", "cacheable",
+                          {{"extra", "params"}}, nullptr, std::move(apps)));
+  constexpr char regex[] =
+      R"({"request":{"@os":"\w+","@updater":"prod_id",)"
+      R"("acceptformat":"crx2,crx3",)"
+      R"("app":\[{"appid":"id1","attr1":"1","attr2":"2","brand":"brand1",)"
+      R"("cohort":"c1","cohorthint":"ch1","cohortname":"cn1",)"
+      R"("disabled":\[{"reason":0},{"reason":1}],"enabled":false,)"
+      R"("installedby":"location1","installsource":"source1",)"
+      R"("packages":{"package":\[{"fp":"fp1"}]},)"
+      R"("ping":{"ping_freshness":"{[-\w]{36}}","rd":1234},)"
+      R"("updatecheck":{"updatedisabled":true},"version":"1.0"},)"
+      R"({"appid":"id2","event":\[{"a":1,"b":"2"},{"error":0}],)"
+      R"("version":"2.0"}],"arch":"\w+","dedup":"cr","dlpref":"cacheable",)"
+      R"("extra":"params","hw":{"physmemory":\d+},"lang":"lang",)"
+      R"("nacl_arch":"[-\w]+","os":{"arch":"[_,-.\w]+","platform":"OS",)"
+      R"(("sp":"[\s\w]+",)?"version":"[-.\w]+"},"prodchannel":"channel",)"
+      R"("prodversion":"1.0","protocol":"3.1","requestid":"{[-\w]{36}}",)"
+      R"("sessionid":"{[-\w]{36}}","updaterchannel":"channel",)"
+      R"("updaterversion":"1.0"(,"wow64":true)?}})";
+  EXPECT_TRUE(RE2::FullMatch(request, regex)) << request;
+}
+
+TEST(SerializeRequestJSON, DownloadPreference) {
+  // Verifies that an empty |download_preference| is not serialized.
+  const auto serializer = std::make_unique<ProtocolSerializerJSON>();
+  auto request = serializer->Serialize(
+      MakeProtocolRequest("{15160585-8ADE-4D3C-839B-1281A6035D1F}", "", "", "",
+                          "", "", "", {}, nullptr, {}));
+  EXPECT_FALSE(RE2::PartialMatch(request, R"("dlpref":)")) << request;
+
+  // Verifies that |download_preference| is serialized.
+  request = serializer->Serialize(
+      MakeProtocolRequest("{15160585-8ADE-4D3C-839B-1281A6035D1F}", "", "", "",
+                          "", "", "cacheable", {}, nullptr, {}));
+  EXPECT_TRUE(RE2::PartialMatch(request, R"("dlpref":"cacheable")")) << request;
+}
+
+// When present, updater state attributes are only serialized for Google builds,
+// except the |domainjoined| attribute, which is serialized in all cases.
+TEST(SerializeRequestJSON, UpdaterStateAttributes) {
+  const auto serializer = std::make_unique<ProtocolSerializerJSON>();
+  UpdaterState::Attributes attributes;
+  attributes["ismachine"] = "1";
+  attributes["domainjoined"] = "1";
+  attributes["name"] = "Omaha";
+  attributes["version"] = "1.2.3.4";
+  attributes["laststarted"] = "1";
+  attributes["lastchecked"] = "2";
+  attributes["autoupdatecheckenabled"] = "0";
+  attributes["updatepolicy"] = "-1";
+  const auto request = serializer->Serialize(MakeProtocolRequest(
+      "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "prod_id", "1.0", "lang",
+      "channel", "OS", "cacheable", {{"extra", "params"}}, &attributes, {}));
+  constexpr char regex[] =
+      R"({"request":{"@os":"\w+","@updater":"prod_id",)"
+      R"("acceptformat":"crx2,crx3","arch":"\w+","dedup":"cr",)"
+      R"("dlpref":"cacheable","domainjoined":true,"extra":"params",)"
+      R"("hw":{"physmemory":\d+},"lang":"lang","nacl_arch":"[-\w]+",)"
+      R"("os":{"arch":"[,-.\w]+","platform":"OS",("sp":"[\s\w]+",)?)"
+      R"("version":"[-.\w]+"},"prodchannel":"channel","prodversion":"1.0",)"
+      R"("protocol":"3.1","requestid":"{[-\w]{36}}","sessionid":"{[-\w]{36}}",)"
+#if defined(GOOGLE_CHROME_BUILD)
+      R"("updater":{"autoupdatecheckenabled":false,"ismachine":true,)"
+      R"("lastchecked":2,"laststarted":1,"name":"Omaha","updatepolicy":-1,)"
+      R"("version":"1\.2\.3\.4"},)"
+#endif  // GOOGLE_CHROME_BUILD
+      R"("updaterchannel":"channel","updaterversion":"1.0"(,"wow64":true)?}})";
+  EXPECT_TRUE(RE2::FullMatch(request, regex)) << request;
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/protocol_serializer_unittest.cc b/src/components/update_client/protocol_serializer_unittest.cc
new file mode 100644
index 0000000..f0c8a14
--- /dev/null
+++ b/src/components/update_client/protocol_serializer_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright 2016 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 <string>
+
+#include "base/strings/string_util.h"
+#include "base/version.h"
+#include "components/update_client/protocol_serializer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace update_client {
+
+TEST(BuildProtocolRequest, BuildUpdateCheckExtraRequestHeaders) {
+  auto headers = BuildUpdateCheckExtraRequestHeaders(
+      "fake_prodid", base::Version("30.0"), {}, true);
+  EXPECT_EQ("fake_prodid-30.0", headers["X-Goog-Update-Updater"]);
+  EXPECT_EQ("fg", headers["X-Goog-Update-Interactivity"]);
+  EXPECT_TRUE(headers["X-Goog-Update-AppId"].empty());
+
+  headers = BuildUpdateCheckExtraRequestHeaders(
+      "fake_prodid", base::Version("30.0"), {}, false);
+  EXPECT_EQ("fake_prodid-30.0", headers["X-Goog-Update-Updater"]);
+  EXPECT_EQ("bg", headers["X-Goog-Update-Interactivity"]);
+  EXPECT_TRUE(headers["X-Goog-Update-AppId"].empty());
+
+  headers = BuildUpdateCheckExtraRequestHeaders(
+      "fake_prodid", base::Version("30.0"),
+      {"jebgalgnebhfojomionfpkfelancnnkf"}, true);
+  EXPECT_EQ("fake_prodid-30.0", headers["X-Goog-Update-Updater"]);
+  EXPECT_EQ("fg", headers["X-Goog-Update-Interactivity"]);
+  EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", headers["X-Goog-Update-AppId"]);
+
+  headers = BuildUpdateCheckExtraRequestHeaders(
+      "fake_prodid", base::Version("30.0"),
+      {"jebgalgnebhfojomionfpkfelancnnkf", "ihfokbkgjpifbbojhneepfflplebdkc"},
+      true);
+  EXPECT_EQ("fake_prodid-30.0", headers["X-Goog-Update-Updater"]);
+  EXPECT_EQ("fg", headers["X-Goog-Update-Interactivity"]);
+  EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf,ihfokbkgjpifbbojhneepfflplebdkc",
+            headers["X-Goog-Update-AppId"]);
+
+  // Test that only 30 extension ids are joined in the headers.
+  headers = BuildUpdateCheckExtraRequestHeaders(
+      "fake_prodid", base::Version("30.0"),
+      std::vector<std::string>(40, "jebgalgnebhfojomionfpkfelancnnkf"), true);
+  EXPECT_EQ(base::JoinString(std::vector<std::string>(
+                                 30, "jebgalgnebhfojomionfpkfelancnnkf"),
+                             ","),
+            headers["X-Goog-Update-AppId"]);
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/request_sender.cc b/src/components/update_client/request_sender.cc
new file mode 100644
index 0000000..a68df98
--- /dev/null
+++ b/src/components/update_client/request_sender.cc
@@ -0,0 +1,204 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/request_sender.h"
+
+#include <utility>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/client_update_protocol/ecdsa.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/network.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+
+namespace update_client {
+
+namespace {
+
+// This is an ECDSA prime256v1 named-curve key.
+constexpr int kKeyVersion = 9;
+const char kKeyPubBytesBase64[] =
+    "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsVwVMmIJaWBjktSx9m1JrZWYBvMm"
+    "bsrGGQPhScDtao+DloD871YmEeunAaQvRMZgDh1nCaWkVG6wo75+yDbKDA==";
+
+}  // namespace
+
+RequestSender::RequestSender(scoped_refptr<Configurator> config)
+    : config_(config) {}
+
+RequestSender::~RequestSender() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void RequestSender::Send(
+    const std::vector<GURL>& urls,
+    const base::flat_map<std::string, std::string>& request_extra_headers,
+    const std::string& request_body,
+    bool use_signing,
+    RequestSenderCallback request_sender_callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  urls_ = urls;
+  request_extra_headers_ = request_extra_headers;
+  request_body_ = request_body;
+  use_signing_ = use_signing;
+  request_sender_callback_ = std::move(request_sender_callback);
+
+  if (urls_.empty()) {
+    return HandleSendError(static_cast<int>(ProtocolError::MISSING_URLS), 0);
+  }
+
+  cur_url_ = urls_.begin();
+
+  if (use_signing_) {
+    public_key_ = GetKey(kKeyPubBytesBase64);
+    if (public_key_.empty())
+      return HandleSendError(
+          static_cast<int>(ProtocolError::MISSING_PUBLIC_KEY), 0);
+  }
+
+  SendInternal();
+}
+
+void RequestSender::SendInternal() {
+  DCHECK(cur_url_ != urls_.end());
+  DCHECK(cur_url_->is_valid());
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  GURL url(*cur_url_);
+
+  if (use_signing_) {
+    DCHECK(!public_key_.empty());
+    signer_ = client_update_protocol::Ecdsa::Create(kKeyVersion, public_key_);
+    std::string request_query_string;
+    signer_->SignRequest(request_body_, &request_query_string);
+
+    url = BuildUpdateUrl(url, request_query_string);
+  }
+
+  network_fetcher_ = config_->GetNetworkFetcherFactory()->Create();
+  if (!network_fetcher_) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&RequestSender::SendInternalComplete,
+                       base::Unretained(this),
+                       static_cast<int>(ProtocolError::URL_FETCHER_FAILED),
+                       std::string(), std::string(), 0));
+  }
+  network_fetcher_->PostRequest(
+      url, request_body_, request_extra_headers_,
+      base::BindOnce(&RequestSender::OnResponseStarted, base::Unretained(this)),
+      base::BindRepeating([](int64_t current) {}),
+      base::BindOnce(&RequestSender::OnNetworkFetcherComplete,
+                     base::Unretained(this), url));
+}
+
+void RequestSender::SendInternalComplete(int error,
+                                         const std::string& response_body,
+                                         const std::string& response_etag,
+                                         int retry_after_sec) {
+  if (!error) {
+    if (!use_signing_) {
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(request_sender_callback_), 0,
+                                    response_body, retry_after_sec));
+      return;
+    }
+
+    DCHECK(use_signing_);
+    DCHECK(signer_);
+    if (signer_->ValidateResponse(response_body, response_etag)) {
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(request_sender_callback_), 0,
+                                    response_body, retry_after_sec));
+      return;
+    }
+
+    error = static_cast<int>(ProtocolError::RESPONSE_NOT_TRUSTED);
+  }
+
+  DCHECK(error);
+
+  // A positive |retry_after_sec| is a hint from the server that the client
+  // should not send further request until the cooldown has expired.
+  if (retry_after_sec <= 0 && ++cur_url_ != urls_.end() &&
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&RequestSender::SendInternal,
+                                    base::Unretained(this)))) {
+    return;
+  }
+
+  HandleSendError(error, retry_after_sec);
+}
+
+void RequestSender::OnResponseStarted(const GURL& final_url,
+                                      int response_code,
+                                      int64_t content_length) {
+  response_code_ = response_code;
+}
+
+void RequestSender::OnNetworkFetcherComplete(
+    const GURL& original_url,
+    std::unique_ptr<std::string> response_body,
+    int net_error,
+    const std::string& header_etag,
+    int64_t xheader_retry_after_sec) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  VLOG(1) << "request completed from url: " << original_url.spec();
+
+  int error = -1;
+  if (response_body && response_code_ == 200) {
+    DCHECK_EQ(0, net_error);
+    error = 0;
+  } else if (response_code_ != -1) {
+    error = response_code_;
+  } else {
+    error = net_error;
+  }
+
+  int retry_after_sec = -1;
+  if (original_url.SchemeIsCryptographic() && error > 0)
+    retry_after_sec = base::saturated_cast<int>(xheader_retry_after_sec);
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&RequestSender::SendInternalComplete,
+                                base::Unretained(this), error,
+                                response_body ? *response_body : std::string(),
+                                header_etag, retry_after_sec));
+}
+
+void RequestSender::HandleSendError(int error, int retry_after_sec) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(request_sender_callback_), error,
+                                std::string(), retry_after_sec));
+}
+
+std::string RequestSender::GetKey(const char* key_bytes_base64) {
+  std::string result;
+  return base::Base64Decode(std::string(key_bytes_base64), &result)
+             ? result
+             : std::string();
+}
+
+GURL RequestSender::BuildUpdateUrl(const GURL& url,
+                                   const std::string& query_params) {
+  const std::string query_string(
+      url.has_query() ? base::StringPrintf("%s&%s", url.query().c_str(),
+                                           query_params.c_str())
+                      : query_params);
+  GURL::Replacements replacements;
+  replacements.SetQueryStr(query_string);
+
+  return url.ReplaceComponents(replacements);
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/request_sender.h b/src/components/update_client/request_sender.h
new file mode 100644
index 0000000..7b9e21b
--- /dev/null
+++ b/src/components/update_client/request_sender.h
@@ -0,0 +1,113 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_REQUEST_SENDER_H_
+#define COMPONENTS_UPDATE_CLIENT_REQUEST_SENDER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "url/gurl.h"
+
+namespace client_update_protocol {
+class Ecdsa;
+}
+
+namespace update_client {
+
+class Configurator;
+class NetworkFetcher;
+
+// Sends a request to one of the urls provided. The class implements a chain
+// of responsibility design pattern, where the urls are tried in the order they
+// are specified, until the request to one of them succeeds or all have failed.
+// CUP signing is optional.
+class RequestSender {
+ public:
+  // If |error| is 0, then the response is provided in the |response| parameter.
+  // |retry_after_sec| contains the value of the X-Retry-After response header,
+  // when the response was received from a cryptographically secure URL. The
+  // range for this value is [-1, 86400]. If |retry_after_sec| is -1 it means
+  // that the header could not be found, or trusted, or had an invalid value.
+  // The upper bound represents a delay of one day.
+  using RequestSenderCallback = base::OnceCallback<
+      void(int error, const std::string& response, int retry_after_sec)>;
+
+  explicit RequestSender(scoped_refptr<Configurator> config);
+  ~RequestSender();
+
+  // |use_signing| enables CUP signing of protocol messages exchanged using
+  // this class. |is_foreground| controls the presence and the value for the
+  // X-GoogleUpdate-Interactvity header serialized in the protocol request.
+  // If this optional parameter is set, the values of "fg" or "bg" are sent
+  // for true or false values of this parameter. Otherwise the header is not
+  // sent at all.
+  void Send(
+      const std::vector<GURL>& urls,
+      const base::flat_map<std::string, std::string>& request_extra_headers,
+      const std::string& request_body,
+      bool use_signing,
+      RequestSenderCallback request_sender_callback);
+
+ private:
+  // Combines the |url| and |query_params| parameters.
+  static GURL BuildUpdateUrl(const GURL& url, const std::string& query_params);
+
+  // Decodes and returns the public key used by CUP.
+  static std::string GetKey(const char* key_bytes_base64);
+
+  void OnResponseStarted(const GURL& final_url,
+                         int response_code,
+                         int64_t content_length);
+
+  void OnNetworkFetcherComplete(const GURL& original_url,
+                                std::unique_ptr<std::string> response_body,
+                                int net_error,
+                                const std::string& header_etag,
+                                int64_t xheader_retry_after_sec);
+
+  // Implements the error handling and url fallback mechanism.
+  void SendInternal();
+
+  // Called when SendInternal completes. |response_body| and |response_etag|
+  // contain the body and the etag associated with the HTTP response.
+  void SendInternalComplete(int error,
+                            const std::string& response_body,
+                            const std::string& response_etag,
+                            int retry_after_sec);
+
+  // Helper function to handle a non-continuable error in Send.
+  void HandleSendError(int error, int retry_after_sec);
+
+  base::ThreadChecker thread_checker_;
+
+  const scoped_refptr<Configurator> config_;
+
+  std::vector<GURL> urls_;
+  base::flat_map<std::string, std::string> request_extra_headers_;
+  std::string request_body_;
+  bool use_signing_ = false;  // True if CUP signing is used.
+  RequestSenderCallback request_sender_callback_;
+
+  std::string public_key_;
+  std::vector<GURL>::const_iterator cur_url_;
+  std::unique_ptr<NetworkFetcher> network_fetcher_;
+  std::unique_ptr<client_update_protocol::Ecdsa> signer_;
+
+  int response_code_ = -1;
+
+  DISALLOW_COPY_AND_ASSIGN(RequestSender);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_REQUEST_SENDER_H_
diff --git a/src/components/update_client/request_sender_unittest.cc b/src/components/update_client/request_sender_unittest.cc
new file mode 100644
index 0000000..e1e16d8
--- /dev/null
+++ b/src/components/update_client/request_sender_unittest.cc
@@ -0,0 +1,262 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/request_sender.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/update_client/net/url_loader_post_interceptor.h"
+#include "components/update_client/test_configurator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+namespace {
+
+const char kUrl1[] = "https://localhost2/path1";
+const char kUrl2[] = "https://localhost2/path2";
+
+// TODO(sorin): refactor as a utility function for unit tests.
+base::FilePath test_file(const char* file) {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  return path.AppendASCII("components")
+      .AppendASCII("test")
+      .AppendASCII("data")
+      .AppendASCII("update_client")
+      .AppendASCII(file);
+}
+
+}  // namespace
+
+class RequestSenderTest : public testing::Test,
+                          public ::testing::WithParamInterface<bool> {
+ public:
+  RequestSenderTest();
+  ~RequestSenderTest() override;
+
+  // Overrides from testing::Test.
+  void SetUp() override;
+  void TearDown() override;
+
+  void RequestSenderComplete(int error,
+                             const std::string& response,
+                             int retry_after_sec);
+
+ protected:
+  void Quit();
+  void RunThreads();
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  scoped_refptr<TestConfigurator> config_;
+  std::unique_ptr<RequestSender> request_sender_;
+
+  std::unique_ptr<URLLoaderPostInterceptor> post_interceptor_;
+
+  int error_ = 0;
+  std::string response_;
+
+ private:
+  base::OnceClosure quit_closure_;
+
+  DISALLOW_COPY_AND_ASSIGN(RequestSenderTest);
+};
+
+INSTANTIATE_TEST_SUITE_P(IsForeground, RequestSenderTest, ::testing::Bool());
+
+RequestSenderTest::RequestSenderTest()
+    : scoped_task_environment_(
+          base::test::ScopedTaskEnvironment::MainThreadType::IO) {}
+
+RequestSenderTest::~RequestSenderTest() {}
+
+void RequestSenderTest::SetUp() {
+  config_ = base::MakeRefCounted<TestConfigurator>();
+  request_sender_ = std::make_unique<RequestSender>(config_);
+
+  std::vector<GURL> urls;
+  urls.push_back(GURL(kUrl1));
+  urls.push_back(GURL(kUrl2));
+
+  post_interceptor_ = std::make_unique<URLLoaderPostInterceptor>(
+      urls, config_->test_url_loader_factory());
+  EXPECT_TRUE(post_interceptor_);
+}
+
+void RequestSenderTest::TearDown() {
+  request_sender_ = nullptr;
+
+  post_interceptor_.reset();
+
+  // Run the threads until they are idle to allow the clean up
+  // of the network interceptors on the IO thread.
+  scoped_task_environment_.RunUntilIdle();
+  config_ = nullptr;
+}
+
+void RequestSenderTest::RunThreads() {
+  base::RunLoop runloop;
+  quit_closure_ = runloop.QuitClosure();
+  runloop.Run();
+}
+
+void RequestSenderTest::Quit() {
+  if (!quit_closure_.is_null())
+    std::move(quit_closure_).Run();
+}
+
+void RequestSenderTest::RequestSenderComplete(int error,
+                                              const std::string& response,
+                                              int retry_after_sec) {
+  error_ = error;
+  response_ = response;
+
+  Quit();
+}
+
+// Tests that when a request to the first url succeeds, the subsequent urls are
+// not tried.
+TEST_P(RequestSenderTest, RequestSendSuccess) {
+  EXPECT_TRUE(
+      post_interceptor_->ExpectRequest(std::make_unique<PartialMatch>("test"),
+                                       test_file("updatecheck_reply_1.json")));
+
+  const bool is_foreground = GetParam();
+  request_sender_->Send(
+      {GURL(kUrl1), GURL(kUrl2)},
+      {{"X-Goog-Update-Interactivity", is_foreground ? "fg" : "bg"}}, "test",
+      false,
+      base::BindOnce(&RequestSenderTest::RequestSenderComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_EQ(0, post_interceptor_->GetHitCountForURL(GURL(kUrl2)))
+      << post_interceptor_->GetRequestsAsString();
+
+  // Sanity check the request.
+  EXPECT_STREQ("test", post_interceptor_->GetRequestBody(0).c_str());
+
+  // Check the response post conditions.
+  EXPECT_EQ(0, error_);
+  EXPECT_EQ(419ul, response_.size());
+
+  // Check the interactivity header value.
+  const auto extra_request_headers =
+      std::get<1>(post_interceptor_->GetRequests()[0]);
+  EXPECT_TRUE(extra_request_headers.HasHeader("X-Goog-Update-Interactivity"));
+  std::string header;
+  extra_request_headers.GetHeader("X-Goog-Update-Interactivity", &header);
+  EXPECT_STREQ(is_foreground ? "fg" : "bg", header.c_str());
+}
+
+// Tests that the request succeeds using the second url after the first url
+// has failed.
+TEST_F(RequestSenderTest, RequestSendSuccessWithFallback) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("test"), net::HTTP_FORBIDDEN));
+  EXPECT_TRUE(
+      post_interceptor_->ExpectRequest(std::make_unique<PartialMatch>("test")));
+
+  request_sender_->Send(
+      {GURL(kUrl1), GURL(kUrl2)}, {}, "test", false,
+      base::BindOnce(&RequestSenderTest::RequestSenderComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(2, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(2, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetHitCountForURL(GURL(kUrl1)))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetHitCountForURL(GURL(kUrl2)))
+      << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_STREQ("test", post_interceptor_->GetRequestBody(0).c_str());
+  EXPECT_STREQ("test", post_interceptor_->GetRequestBody(1).c_str());
+  EXPECT_EQ(0, error_);
+}
+
+// Tests that the request fails when both urls have failed.
+TEST_F(RequestSenderTest, RequestSendFailed) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("test"), net::HTTP_FORBIDDEN));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("test"), net::HTTP_FORBIDDEN));
+
+  const std::vector<GURL> urls = {GURL(kUrl1), GURL(kUrl2)};
+  request_sender_ = std::make_unique<RequestSender>(config_);
+  request_sender_->Send(
+      urls, {}, "test", false,
+      base::BindOnce(&RequestSenderTest::RequestSenderComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(2, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(2, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetHitCountForURL(GURL(kUrl1)))
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetHitCountForURL(GURL(kUrl2)))
+      << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_STREQ("test", post_interceptor_->GetRequestBody(0).c_str());
+  EXPECT_STREQ("test", post_interceptor_->GetRequestBody(1).c_str());
+  EXPECT_EQ(403, error_);
+}
+
+// Tests that the request fails when no urls are provided.
+TEST_F(RequestSenderTest, RequestSendFailedNoUrls) {
+  std::vector<GURL> urls;
+  request_sender_ = std::make_unique<RequestSender>(config_);
+  request_sender_->Send(
+      urls, {}, "test", false,
+      base::BindOnce(&RequestSenderTest::RequestSenderComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(-10002, error_);
+}
+
+// Tests that a CUP request fails if the response is not signed.
+TEST_F(RequestSenderTest, RequestSendCupError) {
+  EXPECT_TRUE(
+      post_interceptor_->ExpectRequest(std::make_unique<PartialMatch>("test"),
+                                       test_file("updatecheck_reply_1.json")));
+
+  const std::vector<GURL> urls = {GURL(kUrl1)};
+  request_sender_ = std::make_unique<RequestSender>(config_);
+  request_sender_->Send(
+      urls, {}, "test", true,
+      base::BindOnce(&RequestSenderTest::RequestSenderComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_STREQ("test", post_interceptor_->GetRequestBody(0).c_str());
+  EXPECT_EQ(-10000, error_);
+  EXPECT_TRUE(response_.empty());
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/task.h b/src/components/update_client/task.h
new file mode 100644
index 0000000..e9b480e
--- /dev/null
+++ b/src/components/update_client/task.h
@@ -0,0 +1,43 @@
+// Copyright 2015 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 COMPONENTS_UPDATE_CLIENT_TASK_H_
+#define COMPONENTS_UPDATE_CLIENT_TASK_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace update_client {
+
+// Defines an abstraction for a unit of work done by the update client.
+// Each invocation of the update client API results in a task being created and
+// run. In most cases, a task corresponds to a set of CRXs, which are updated
+// together.
+class Task : public base::RefCounted<Task> {
+ public:
+  Task() = default;
+  virtual void Run() = 0;
+
+  // Does a best effort attempt to make a task release its resources and stop
+  // soon. It is possible that a running task may complete even if this
+  // method is called.
+  virtual void Cancel() = 0;
+
+  // Returns the ids corresponding to the CRXs associated with this update task.
+  virtual std::vector<std::string> GetIds() const = 0;
+
+ protected:
+  friend class base::RefCounted<Task>;
+  virtual ~Task() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Task);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_TASK_H_
diff --git a/src/components/update_client/task_send_uninstall_ping.cc b/src/components/update_client/task_send_uninstall_ping.cc
new file mode 100644
index 0000000..018bb1d
--- /dev/null
+++ b/src/components/update_client/task_send_uninstall_ping.cc
@@ -0,0 +1,64 @@
+// 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 "components/update_client/task_send_uninstall_ping.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/version.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_engine.h"
+
+namespace update_client {
+
+TaskSendUninstallPing::TaskSendUninstallPing(
+    scoped_refptr<UpdateEngine> update_engine,
+    const std::string& id,
+    const base::Version& version,
+    int reason,
+    Callback callback)
+    : update_engine_(update_engine),
+      id_(id),
+      version_(version),
+      reason_(reason),
+      callback_(std::move(callback)) {}
+
+TaskSendUninstallPing::~TaskSendUninstallPing() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void TaskSendUninstallPing::Run() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (id_.empty()) {
+    TaskComplete(Error::INVALID_ARGUMENT);
+    return;
+  }
+
+  update_engine_->SendUninstallPing(
+      id_, version_, reason_,
+      base::BindOnce(&TaskSendUninstallPing::TaskComplete, this));
+}
+
+void TaskSendUninstallPing::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  TaskComplete(Error::UPDATE_CANCELED);
+}
+
+std::vector<std::string> TaskSendUninstallPing::GetIds() const {
+  return std::vector<std::string>{id_};
+}
+
+void TaskSendUninstallPing::TaskComplete(Error error) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(callback_), scoped_refptr<Task>(this), error));
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/task_send_uninstall_ping.h b/src/components/update_client/task_send_uninstall_ping.h
new file mode 100644
index 0000000..9367b3d
--- /dev/null
+++ b/src/components/update_client/task_send_uninstall_ping.h
@@ -0,0 +1,68 @@
+// 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 COMPONENTS_UPDATE_CLIENT_TASK_SEND_UNINSTALL_PING_H_
+#define COMPONENTS_UPDATE_CLIENT_TASK_SEND_UNINSTALL_PING_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "components/update_client/task.h"
+#include "components/update_client/update_client.h"
+
+namespace base {
+class Version;
+}
+
+namespace update_client {
+
+class UpdateEngine;
+enum class Error;
+
+// Defines a specialized task for sending the uninstall ping.
+class TaskSendUninstallPing : public Task {
+ public:
+  using Callback =
+      base::OnceCallback<void(scoped_refptr<Task> task, Error error)>;
+
+  // |update_engine| is injected here to handle the task.
+  // |id| represents the CRX to send the ping for.
+  // |callback| is called to return the execution flow back to creator of
+  //    this task when the task is done.
+  TaskSendUninstallPing(scoped_refptr<UpdateEngine> update_engine,
+                        const std::string& id,
+                        const base::Version& version,
+                        int reason,
+                        Callback callback);
+
+  void Run() override;
+
+  void Cancel() override;
+
+  std::vector<std::string> GetIds() const override;
+
+ private:
+  ~TaskSendUninstallPing() override;
+
+  // Called when the task has completed either because the task has run or
+  // it has been canceled.
+  void TaskComplete(Error error);
+
+  base::ThreadChecker thread_checker_;
+  scoped_refptr<UpdateEngine> update_engine_;
+  const std::string id_;
+  const base::Version version_;
+  int reason_;
+  Callback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(TaskSendUninstallPing);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_TASK_SEND_UNINSTALL_PING_H_
diff --git a/src/components/update_client/task_traits.h b/src/components/update_client/task_traits.h
new file mode 100644
index 0000000..45f4cc9
--- /dev/null
+++ b/src/components/update_client/task_traits.h
@@ -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.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_TASK_TRAITS_H_
+#define COMPONENTS_UPDATE_CLIENT_TASK_TRAITS_H_
+
+#include "base/task/task_traits.h"
+
+namespace update_client {
+
+const base::TaskTraits kTaskTraits = {
+    base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+    base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
+
+const base::TaskTraits kTaskTraitsBackgroundDownloader = {
+    base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+    base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
+
+// This task joins a process, hence .WithBaseSyncPrimitives().
+const base::TaskTraits kTaskTraitsRunCommand = {
+    base::MayBlock(), base::WithBaseSyncPrimitives(),
+    base::TaskPriority::BEST_EFFORT,
+    base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_TASK_TRAITS_H_
diff --git a/src/components/update_client/task_update.cc b/src/components/update_client/task_update.cc
new file mode 100644
index 0000000..ab445b5
--- /dev/null
+++ b/src/components/update_client/task_update.cc
@@ -0,0 +1,61 @@
+// Copyright 2015 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 "components/update_client/task_update.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_engine.h"
+
+namespace update_client {
+
+TaskUpdate::TaskUpdate(scoped_refptr<UpdateEngine> update_engine,
+                       bool is_foreground,
+                       const std::vector<std::string>& ids,
+                       UpdateClient::CrxDataCallback crx_data_callback,
+                       Callback callback)
+    : update_engine_(update_engine),
+      is_foreground_(is_foreground),
+      ids_(ids),
+      crx_data_callback_(std::move(crx_data_callback)),
+      callback_(std::move(callback)) {}
+
+TaskUpdate::~TaskUpdate() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void TaskUpdate::Run() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (ids_.empty()) {
+    TaskComplete(Error::INVALID_ARGUMENT);
+    return;
+  }
+
+  update_engine_->Update(is_foreground_, ids_, std::move(crx_data_callback_),
+                         base::BindOnce(&TaskUpdate::TaskComplete, this));
+}
+
+void TaskUpdate::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  TaskComplete(Error::UPDATE_CANCELED);
+}
+
+std::vector<std::string> TaskUpdate::GetIds() const {
+  return ids_;
+}
+
+void TaskUpdate::TaskComplete(Error error) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback_),
+                                scoped_refptr<TaskUpdate>(this), error));
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/task_update.h b/src/components/update_client/task_update.h
new file mode 100644
index 0000000..4df796b
--- /dev/null
+++ b/src/components/update_client/task_update.h
@@ -0,0 +1,66 @@
+// Copyright 2015 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 COMPONENTS_UPDATE_CLIENT_TASK_UPDATE_H_
+#define COMPONENTS_UPDATE_CLIENT_TASK_UPDATE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "components/update_client/task.h"
+#include "components/update_client/update_client.h"
+
+namespace update_client {
+
+class UpdateEngine;
+enum class Error;
+
+// Defines a specialized task for updating a group of CRXs.
+class TaskUpdate : public Task {
+ public:
+  using Callback =
+      base::OnceCallback<void(scoped_refptr<Task> task, Error error)>;
+
+  // |update_engine| is injected here to handle the task.
+  // |is_foreground| is true when the update task is initiated by the user.
+  // |ids| represents the CRXs to be updated by this task.
+  // |crx_data_callback| is called to get update data for the these CRXs.
+  // |callback| is called to return the execution flow back to creator of
+  //    this task when the task is done.
+  TaskUpdate(scoped_refptr<UpdateEngine> update_engine,
+             bool is_foreground,
+             const std::vector<std::string>& ids,
+             UpdateClient::CrxDataCallback crx_data_callback,
+             Callback callback);
+
+  void Run() override;
+
+  void Cancel() override;
+
+  std::vector<std::string> GetIds() const override;
+
+ private:
+  ~TaskUpdate() override;
+
+  // Called when the task has completed either because the task has run or
+  // it has been canceled.
+  void TaskComplete(Error error);
+
+  base::ThreadChecker thread_checker_;
+  scoped_refptr<UpdateEngine> update_engine_;
+  const bool is_foreground_;
+  const std::vector<std::string> ids_;
+  UpdateClient::CrxDataCallback crx_data_callback_;
+  Callback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(TaskUpdate);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_TASK_UPDATE_H_
diff --git a/src/components/update_client/test_configurator.cc b/src/components/update_client/test_configurator.cc
new file mode 100644
index 0000000..515be05
--- /dev/null
+++ b/src/components/update_client/test_configurator.cc
@@ -0,0 +1,216 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/test_configurator.h"
+
+#include <utility>
+
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/version.h"
+#include "components/prefs/pref_service.h"
+#include "components/services/patch/in_process_file_patcher.h"
+#include "components/services/unzip/in_process_unzipper.h"
+#include "components/update_client/activity_data_service.h"
+#include "components/update_client/net/network_chromium.h"
+#include "components/update_client/patch/patch_impl.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/protocol_handler.h"
+#include "components/update_client/unzip/unzip_impl.h"
+#include "components/update_client/unzipper.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+namespace {
+
+std::vector<GURL> MakeDefaultUrls() {
+  std::vector<GURL> urls;
+  urls.push_back(GURL(POST_INTERCEPT_SCHEME
+                      "://" POST_INTERCEPT_HOSTNAME POST_INTERCEPT_PATH));
+  return urls;
+}
+
+}  // namespace
+
+TestConfigurator::TestConfigurator()
+    : brand_("TEST"),
+      initial_time_(0),
+      ondemand_time_(0),
+      enabled_cup_signing_(false),
+      enabled_component_updates_(true),
+      unzip_factory_(base::MakeRefCounted<update_client::UnzipChromiumFactory>(
+          base::BindRepeating(&unzip::LaunchInProcessUnzipper))),
+      patch_factory_(base::MakeRefCounted<update_client::PatchChromiumFactory>(
+          base::BindRepeating(&patch::LaunchInProcessFilePatcher))),
+      test_shared_loader_factory_(
+          base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+              &test_url_loader_factory_)),
+      network_fetcher_factory_(
+          base::MakeRefCounted<NetworkFetcherChromiumFactory>(
+              test_shared_loader_factory_)) {}
+
+TestConfigurator::~TestConfigurator() {}
+
+int TestConfigurator::InitialDelay() const {
+  return initial_time_;
+}
+
+int TestConfigurator::NextCheckDelay() const {
+  return 1;
+}
+
+int TestConfigurator::OnDemandDelay() const {
+  return ondemand_time_;
+}
+
+int TestConfigurator::UpdateDelay() const {
+  return 1;
+}
+
+std::vector<GURL> TestConfigurator::UpdateUrl() const {
+  if (!update_check_url_.is_empty())
+    return std::vector<GURL>(1, update_check_url_);
+
+  return MakeDefaultUrls();
+}
+
+std::vector<GURL> TestConfigurator::PingUrl() const {
+  if (!ping_url_.is_empty())
+    return std::vector<GURL>(1, ping_url_);
+
+  return UpdateUrl();
+}
+
+std::string TestConfigurator::GetProdId() const {
+  return "fake_prodid";
+}
+
+base::Version TestConfigurator::GetBrowserVersion() const {
+  // Needs to be larger than the required version in tested component manifests.
+  return base::Version("30.0");
+}
+
+std::string TestConfigurator::GetChannel() const {
+  return "fake_channel_string";
+}
+
+std::string TestConfigurator::GetBrand() const {
+  return brand_;
+}
+
+std::string TestConfigurator::GetLang() const {
+  return "fake_lang";
+}
+
+std::string TestConfigurator::GetOSLongName() const {
+  return "Fake Operating System";
+}
+
+base::flat_map<std::string, std::string> TestConfigurator::ExtraRequestParams()
+    const {
+  return {{"extra", "foo"}};
+}
+
+std::string TestConfigurator::GetDownloadPreference() const {
+  return download_preference_;
+}
+
+scoped_refptr<NetworkFetcherFactory>
+TestConfigurator::GetNetworkFetcherFactory() {
+  return network_fetcher_factory_;
+}
+
+scoped_refptr<UnzipperFactory> TestConfigurator::GetUnzipperFactory() {
+  return unzip_factory_;
+}
+
+scoped_refptr<PatcherFactory> TestConfigurator::GetPatcherFactory() {
+  return patch_factory_;
+}
+
+bool TestConfigurator::EnabledDeltas() const {
+  return true;
+}
+
+bool TestConfigurator::EnabledComponentUpdates() const {
+  return enabled_component_updates_;
+}
+
+bool TestConfigurator::EnabledBackgroundDownloader() const {
+  return false;
+}
+
+bool TestConfigurator::EnabledCupSigning() const {
+  return enabled_cup_signing_;
+}
+
+void TestConfigurator::SetBrand(const std::string& brand) {
+  brand_ = brand;
+}
+
+void TestConfigurator::SetOnDemandTime(int seconds) {
+  ondemand_time_ = seconds;
+}
+
+void TestConfigurator::SetInitialDelay(int seconds) {
+  initial_time_ = seconds;
+}
+
+void TestConfigurator::SetEnabledCupSigning(bool enabled_cup_signing) {
+  enabled_cup_signing_ = enabled_cup_signing;
+}
+
+void TestConfigurator::SetEnabledComponentUpdates(
+    bool enabled_component_updates) {
+  enabled_component_updates_ = enabled_component_updates;
+}
+
+void TestConfigurator::SetDownloadPreference(
+    const std::string& download_preference) {
+  download_preference_ = download_preference;
+}
+
+void TestConfigurator::SetUpdateCheckUrl(const GURL& url) {
+  update_check_url_ = url;
+}
+
+void TestConfigurator::SetPingUrl(const GURL& url) {
+  ping_url_ = url;
+}
+
+void TestConfigurator::SetAppGuid(const std::string& app_guid) {
+  app_guid_ = app_guid;
+}
+
+PrefService* TestConfigurator::GetPrefService() const {
+  return nullptr;
+}
+
+ActivityDataService* TestConfigurator::GetActivityDataService() const {
+  return nullptr;
+}
+
+bool TestConfigurator::IsPerUserInstall() const {
+  return true;
+}
+
+std::vector<uint8_t> TestConfigurator::GetRunActionKeyHash() const {
+  return std::vector<uint8_t>(std::begin(gjpm_hash), std::end(gjpm_hash));
+}
+
+std::string TestConfigurator::GetAppGuid() const {
+  return app_guid_;
+}
+
+std::unique_ptr<ProtocolHandlerFactory>
+TestConfigurator::GetProtocolHandlerFactory() const {
+  return std::make_unique<ProtocolHandlerFactoryJSON>();
+}
+
+RecoveryCRXElevator TestConfigurator::GetRecoveryCRXElevator() const {
+  return {};
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/test_configurator.h b/src/components/update_client/test_configurator.h
new file mode 100644
index 0000000..6935347
--- /dev/null
+++ b/src/components/update_client/test_configurator.h
@@ -0,0 +1,146 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_TEST_CONFIGURATOR_H_
+#define COMPONENTS_UPDATE_CLIENT_TEST_CONFIGURATOR_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/configurator.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "url/gurl.h"
+
+class PrefService;
+
+namespace network {
+class SharedURLLoaderFactory;
+}  // namespace network
+
+namespace update_client {
+
+class ActivityDataService;
+class NetworkFetcherFactory;
+class PatchChromiumFactory;
+class ProtocolHandlerFactory;
+class UnzipChromiumFactory;
+
+#define POST_INTERCEPT_SCHEME "https"
+#define POST_INTERCEPT_HOSTNAME "localhost2"
+#define POST_INTERCEPT_PATH "/update2"
+
+// component 1 has extension id "jebgalgnebhfojomionfpkfelancnnkf", and
+// the RSA public key the following hash:
+const uint8_t jebg_hash[] = {0x94, 0x16, 0x0b, 0x6d, 0x41, 0x75, 0xe9, 0xec,
+                             0x8e, 0xd5, 0xfa, 0x54, 0xb0, 0xd2, 0xdd, 0xa5,
+                             0x6e, 0x05, 0x6b, 0xe8, 0x73, 0x47, 0xf6, 0xc4,
+                             0x11, 0x9f, 0xbc, 0xb3, 0x09, 0xb3, 0x5b, 0x40};
+// component 1 public key (base64 encoded):
+const char jebg_public_key[] =
+    "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC68bW8i/RzSaeXOcNLuBw0SP9+1bdo5ysLqH"
+    "qfLqZs6XyJWEyL0U6f1axPR6LwViku21kgdc6PI524eb8Cr+a/iXGgZ8SdvZTcfQ/g/ukwlblF"
+    "mtqYfDoVpz03U8rDQ9b6DxeJBF4r48TNlFORggrAiNR26qbf1i178Au12AzWtwIDAQAB";
+// component 2 has extension id "abagagagagagagagagagagagagagagag", and
+// the RSA public key the following hash:
+const uint8_t abag_hash[] = {0x01, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+                             0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+                             0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+                             0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x01};
+// component 3 has extension id "ihfokbkgjpifnbbojhneepfflplebdkc", and
+// the RSA public key the following hash:
+const uint8_t ihfo_hash[] = {0x87, 0x5e, 0xa1, 0xa6, 0x9f, 0x85, 0xd1, 0x1e,
+                             0x97, 0xd4, 0x4f, 0x55, 0xbf, 0xb4, 0x13, 0xa2,
+                             0xe7, 0xc5, 0xc8, 0xf5, 0x60, 0x19, 0x78, 0x1b,
+                             0x6d, 0xe9, 0x4c, 0xeb, 0x96, 0x05, 0x42, 0x17};
+
+// runaction_test_win.crx and its payload id: gjpmebpgbhcamgdgjcmnjfhggjpgcimm
+const uint8_t gjpm_hash[] = {0x69, 0xfc, 0x41, 0xf6, 0x17, 0x20, 0xc6, 0x36,
+                             0x92, 0xcd, 0x95, 0x76, 0x69, 0xf6, 0x28, 0xcc,
+                             0xbe, 0x98, 0x4b, 0x93, 0x17, 0xd6, 0x9c, 0xb3,
+                             0x64, 0x0c, 0x0d, 0x25, 0x61, 0xc5, 0x80, 0x1d};
+
+class TestConfigurator : public Configurator {
+ public:
+  TestConfigurator();
+
+  // Overrrides for Configurator.
+  int InitialDelay() const override;
+  int NextCheckDelay() const override;
+  int OnDemandDelay() const override;
+  int UpdateDelay() const override;
+  std::vector<GURL> UpdateUrl() const override;
+  std::vector<GURL> PingUrl() const override;
+  std::string GetProdId() const override;
+  base::Version GetBrowserVersion() const override;
+  std::string GetChannel() const override;
+  std::string GetBrand() const override;
+  std::string GetLang() const override;
+  std::string GetOSLongName() const override;
+  base::flat_map<std::string, std::string> ExtraRequestParams() const override;
+  std::string GetDownloadPreference() const override;
+  scoped_refptr<NetworkFetcherFactory> GetNetworkFetcherFactory() override;
+  scoped_refptr<UnzipperFactory> GetUnzipperFactory() override;
+  scoped_refptr<PatcherFactory> GetPatcherFactory() override;
+  bool EnabledDeltas() const override;
+  bool EnabledComponentUpdates() const override;
+  bool EnabledBackgroundDownloader() const override;
+  bool EnabledCupSigning() const override;
+  PrefService* GetPrefService() const override;
+  ActivityDataService* GetActivityDataService() const override;
+  bool IsPerUserInstall() const override;
+  std::vector<uint8_t> GetRunActionKeyHash() const override;
+  std::string GetAppGuid() const override;
+  std::unique_ptr<ProtocolHandlerFactory> GetProtocolHandlerFactory()
+      const override;
+  RecoveryCRXElevator GetRecoveryCRXElevator() const override;
+
+  void SetBrand(const std::string& brand);
+  void SetOnDemandTime(int seconds);
+  void SetInitialDelay(int seconds);
+  void SetDownloadPreference(const std::string& download_preference);
+  void SetEnabledCupSigning(bool use_cup_signing);
+  void SetEnabledComponentUpdates(bool enabled_component_updates);
+  void SetUpdateCheckUrl(const GURL& url);
+  void SetPingUrl(const GURL& url);
+  void SetAppGuid(const std::string& app_guid);
+  network::TestURLLoaderFactory* test_url_loader_factory() {
+    return &test_url_loader_factory_;
+  }
+
+ private:
+  friend class base::RefCountedThreadSafe<TestConfigurator>;
+  ~TestConfigurator() override;
+
+  class TestPatchService;
+
+  std::string brand_;
+  int initial_time_;
+  int ondemand_time_;
+  std::string download_preference_;
+  bool enabled_cup_signing_;
+  bool enabled_component_updates_;
+  GURL update_check_url_;
+  GURL ping_url_;
+  std::string app_guid_;
+
+  scoped_refptr<update_client::UnzipChromiumFactory> unzip_factory_;
+  scoped_refptr<update_client::PatchChromiumFactory> patch_factory_;
+
+  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
+  network::TestURLLoaderFactory test_url_loader_factory_;
+  scoped_refptr<NetworkFetcherFactory> network_fetcher_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestConfigurator);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_TEST_CONFIGURATOR_H_
diff --git a/src/components/update_client/test_installer.cc b/src/components/update_client/test_installer.cc
new file mode 100644
index 0000000..f4957be
--- /dev/null
+++ b/src/components/update_client/test_installer.cc
@@ -0,0 +1,108 @@
+// Copyright 2013 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 "components/update_client/test_installer.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/values.h"
+
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+TestInstaller::TestInstaller() : error_(0), install_count_(0) {}
+
+TestInstaller::~TestInstaller() {
+  // The unpack path is deleted unconditionally by the component state code,
+  // which is driving this installer. Therefore, the unpack path must not
+  // exist when this object is destroyed.
+  if (!unpack_path_.empty())
+    EXPECT_FALSE(base::DirectoryExists(unpack_path_));
+}
+
+void TestInstaller::OnUpdateError(int error) {
+  error_ = error;
+}
+
+void TestInstaller::Install(const base::FilePath& unpack_path,
+                            const std::string& /*public_key*/,
+                            Callback callback) {
+  ++install_count_;
+  unpack_path_ = unpack_path;
+
+  InstallComplete(std::move(callback), Result(InstallError::NONE));
+}
+
+void TestInstaller::InstallComplete(Callback callback,
+                                    const Result& result) const {
+  base::PostTaskWithTraits(FROM_HERE, {base::MayBlock()},
+                           base::BindOnce(std::move(callback), result));
+}
+
+bool TestInstaller::GetInstalledFile(const std::string& file,
+                                     base::FilePath* installed_file) {
+  return false;
+}
+
+bool TestInstaller::Uninstall() {
+  return false;
+}
+
+ReadOnlyTestInstaller::ReadOnlyTestInstaller(const base::FilePath& install_dir)
+    : install_directory_(install_dir) {}
+
+ReadOnlyTestInstaller::~ReadOnlyTestInstaller() {}
+
+bool ReadOnlyTestInstaller::GetInstalledFile(const std::string& file,
+                                             base::FilePath* installed_file) {
+  *installed_file = install_directory_.AppendASCII(file);
+  return true;
+}
+
+VersionedTestInstaller::VersionedTestInstaller() {
+  base::CreateNewTempDirectory(FILE_PATH_LITERAL("TEST_"), &install_directory_);
+}
+
+VersionedTestInstaller::~VersionedTestInstaller() {
+  base::DeleteFile(install_directory_, true);
+}
+
+void VersionedTestInstaller::Install(const base::FilePath& unpack_path,
+                                     const std::string& public_key,
+                                     Callback callback) {
+  const auto manifest = update_client::ReadManifest(unpack_path);
+  std::string version_string;
+  manifest->GetStringASCII("version", &version_string);
+  const base::Version version(version_string);
+
+  const base::FilePath path =
+      install_directory_.AppendASCII(version.GetString());
+  base::CreateDirectory(path.DirName());
+  if (!base::Move(unpack_path, path)) {
+    InstallComplete(std::move(callback), Result(InstallError::GENERIC_ERROR));
+    return;
+  }
+  current_version_ = version;
+  ++install_count_;
+
+  InstallComplete(std::move(callback), Result(InstallError::NONE));
+}
+
+bool VersionedTestInstaller::GetInstalledFile(const std::string& file,
+                                              base::FilePath* installed_file) {
+  const base::FilePath path =
+      install_directory_.AppendASCII(current_version_.GetString());
+  *installed_file = path.Append(base::FilePath::FromUTF8Unsafe(file));
+  return true;
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/test_installer.h b/src/components/update_client/test_installer.h
new file mode 100644
index 0000000..a12baf9
--- /dev/null
+++ b/src/components/update_client/test_installer.h
@@ -0,0 +1,89 @@
+// Copyright 2013 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 COMPONENTS_UPDATE_CLIENT_TEST_INSTALLER_H_
+#define COMPONENTS_UPDATE_CLIENT_TEST_INSTALLER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "components/update_client/update_client.h"
+
+namespace update_client {
+
+// TODO(sorin): consider reducing the number of the installer mocks.
+// A TestInstaller is an installer that does nothing for installation except
+// increment a counter.
+class TestInstaller : public CrxInstaller {
+ public:
+  TestInstaller();
+
+  void OnUpdateError(int error) override;
+
+  void Install(const base::FilePath& unpack_path,
+               const std::string& public_key,
+               Callback callback) override;
+
+  bool GetInstalledFile(const std::string& file,
+                        base::FilePath* installed_file) override;
+
+  bool Uninstall() override;
+
+  int error() const { return error_; }
+
+  int install_count() const { return install_count_; }
+
+ protected:
+  ~TestInstaller() override;
+
+  void InstallComplete(Callback callback, const Result& result) const;
+
+  int error_;
+  int install_count_;
+
+ private:
+  // Contains the |unpack_path| argument of the Install call.
+  base::FilePath unpack_path_;
+};
+
+// A ReadOnlyTestInstaller is an installer that knows about files in an existing
+// directory. It will not write to the directory.
+class ReadOnlyTestInstaller : public TestInstaller {
+ public:
+  explicit ReadOnlyTestInstaller(const base::FilePath& installed_path);
+
+  bool GetInstalledFile(const std::string& file,
+                        base::FilePath* installed_file) override;
+
+ private:
+  ~ReadOnlyTestInstaller() override;
+
+  base::FilePath install_directory_;
+};
+
+// A VersionedTestInstaller is an installer that installs files into versioned
+// directories (e.g. somedir/25.23.89.141/<files>).
+class VersionedTestInstaller : public TestInstaller {
+ public:
+  VersionedTestInstaller();
+
+  void Install(const base::FilePath& unpack_path,
+               const std::string& public_key,
+               Callback callback) override;
+
+  bool GetInstalledFile(const std::string& file,
+                        base::FilePath* installed_file) override;
+
+ private:
+  ~VersionedTestInstaller() override;
+
+  base::FilePath install_directory_;
+  base::Version current_version_;
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_TEST_INSTALLER_H_
diff --git a/src/components/update_client/unzip/unzip_impl.cc b/src/components/update_client/unzip/unzip_impl.cc
new file mode 100644
index 0000000..1fa9058
--- /dev/null
+++ b/src/components/update_client/unzip/unzip_impl.cc
@@ -0,0 +1,39 @@
+// Copyright 2019 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 "components/update_client/unzip/unzip_impl.h"
+
+#include "components/services/unzip/public/cpp/unzip.h"
+
+namespace update_client {
+
+namespace {
+
+class UnzipperImpl : public Unzipper {
+ public:
+  explicit UnzipperImpl(UnzipChromiumFactory::Callback callback)
+      : callback_(std::move(callback)) {}
+
+  void Unzip(const base::FilePath& zip_file,
+             const base::FilePath& destination,
+             UnzipCompleteCallback callback) override {
+    unzip::Unzip(callback_.Run(), zip_file, destination, std::move(callback));
+  }
+
+ private:
+  const UnzipChromiumFactory::Callback callback_;
+};
+
+}  // namespace
+
+UnzipChromiumFactory::UnzipChromiumFactory(Callback callback)
+    : callback_(std::move(callback)) {}
+
+std::unique_ptr<Unzipper> UnzipChromiumFactory::Create() const {
+  return std::make_unique<UnzipperImpl>(callback_);
+}
+
+UnzipChromiumFactory::~UnzipChromiumFactory() = default;
+
+}  // namespace update_client
diff --git a/src/components/update_client/unzip/unzip_impl.h b/src/components/update_client/unzip/unzip_impl.h
new file mode 100644
index 0000000..bbf9156
--- /dev/null
+++ b/src/components/update_client/unzip/unzip_impl.h
@@ -0,0 +1,38 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_UNZIP_UNZIP_IMPL_H_
+#define COMPONENTS_UPDATE_CLIENT_UNZIP_UNZIP_IMPL_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/services/unzip/public/mojom/unzipper.mojom.h"
+#include "components/update_client/unzipper.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+
+namespace update_client {
+
+class UnzipChromiumFactory : public UnzipperFactory {
+ public:
+  using Callback =
+      base::RepeatingCallback<mojo::PendingRemote<unzip::mojom::Unzipper>()>;
+  explicit UnzipChromiumFactory(Callback callback);
+
+  std::unique_ptr<Unzipper> Create() const override;
+
+ protected:
+  ~UnzipChromiumFactory() override;
+
+ private:
+  const Callback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(UnzipChromiumFactory);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_UNZIP_UNZIP_IMPL_H_
diff --git a/src/components/update_client/unzipper.h b/src/components/update_client/unzipper.h
new file mode 100644
index 0000000..6aed58c
--- /dev/null
+++ b/src/components/update_client/unzipper.h
@@ -0,0 +1,50 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_UNZIPPER_H_
+#define COMPONENTS_UPDATE_CLIENT_UNZIPPER_H_
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace base {
+class FilePath;
+}  // namespace base
+
+namespace update_client {
+
+class Unzipper {
+ public:
+  using UnzipCompleteCallback = base::OnceCallback<void(bool success)>;
+
+  virtual ~Unzipper() = default;
+
+  virtual void Unzip(const base::FilePath& zip_file,
+                     const base::FilePath& destination,
+                     UnzipCompleteCallback callback) = 0;
+
+ protected:
+  Unzipper() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Unzipper);
+};
+
+class UnzipperFactory : public base::RefCountedThreadSafe<UnzipperFactory> {
+ public:
+  virtual std::unique_ptr<Unzipper> Create() const = 0;
+
+ protected:
+  friend class base::RefCountedThreadSafe<UnzipperFactory>;
+  UnzipperFactory() = default;
+  virtual ~UnzipperFactory() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UnzipperFactory);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_UNZIPPER_H_
diff --git a/src/components/update_client/update_checker.cc b/src/components/update_client/update_checker.cc
new file mode 100644
index 0000000..da15867
--- /dev/null
+++ b/src/components/update_client/update_checker.cc
@@ -0,0 +1,340 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/update_checker.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/protocol_definition.h"
+#include "components/update_client/protocol_handler.h"
+#include "components/update_client/protocol_serializer.h"
+#include "components/update_client/request_sender.h"
+#include "components/update_client/task_traits.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/updater_state.h"
+#include "components/update_client/utils.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+namespace {
+
+#if defined(COBALT_BUILD_TYPE_DEBUG) || defined(COBALT_BUILD_TYPE_DEVEL)
+const std::string kDefaultUpdaterChannel = "dev";
+#elif defined(COBALT_BUILD_TYPE_QA)
+const std::string kDefaultUpdaterChannel = "qa";
+#elif defined(COBALT_BUILD_TYPE_GOLD)
+const std::string kDefaultUpdaterChannel = "prod";
+#endif
+
+// Returns a sanitized version of the brand or an empty string otherwise.
+std::string SanitizeBrand(const std::string& brand) {
+  return IsValidBrand(brand) ? brand : std::string("");
+}
+
+// Returns true if at least one item requires network encryption.
+bool IsEncryptionRequired(const IdToComponentPtrMap& components) {
+  for (const auto& item : components) {
+    const auto& component = item.second;
+    if (component->crx_component() &&
+        component->crx_component()->requires_network_encryption)
+      return true;
+  }
+  return false;
+}
+
+// Filters invalid attributes from |installer_attributes|.
+using InstallerAttributesFlatMap = base::flat_map<std::string, std::string>;
+InstallerAttributesFlatMap SanitizeInstallerAttributes(
+    const InstallerAttributes& installer_attributes) {
+  InstallerAttributesFlatMap sanitized_attrs;
+  for (const auto& attr : installer_attributes) {
+    if (IsValidInstallerAttribute(attr))
+      sanitized_attrs.insert(attr);
+  }
+  return sanitized_attrs;
+}
+
+class UpdateCheckerImpl : public UpdateChecker {
+ public:
+  UpdateCheckerImpl(scoped_refptr<Configurator> config,
+                    PersistedData* metadata);
+  ~UpdateCheckerImpl() override;
+
+  // Overrides for UpdateChecker.
+  void CheckForUpdates(
+      const std::string& session_id,
+      const std::vector<std::string>& ids_checked,
+      const IdToComponentPtrMap& components,
+      const base::flat_map<std::string, std::string>& additional_attributes,
+      bool enabled_component_updates,
+      UpdateCheckCallback update_check_callback) override;
+
+#if defined(OS_STARBOARD)
+  PersistedData* GetPersistedData() override { return metadata_; }
+#endif
+
+ private:
+  void ReadUpdaterStateAttributes();
+  void CheckForUpdatesHelper(
+      const std::string& session_id,
+      const IdToComponentPtrMap& components,
+      const base::flat_map<std::string, std::string>& additional_attributes,
+      bool enabled_component_updates);
+  void OnRequestSenderComplete(int error,
+                               const std::string& response,
+                               int retry_after_sec);
+  void UpdateCheckSucceeded(const ProtocolParser::Results& results,
+                            int retry_after_sec);
+  void UpdateCheckFailed(ErrorCategory error_category,
+                         int error,
+                         int retry_after_sec);
+
+  base::ThreadChecker thread_checker_;
+
+  const scoped_refptr<Configurator> config_;
+  PersistedData* metadata_ = nullptr;
+  std::vector<std::string> ids_checked_;
+  UpdateCheckCallback update_check_callback_;
+  std::unique_ptr<UpdaterState::Attributes> updater_state_attributes_;
+  std::unique_ptr<RequestSender> request_sender_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateCheckerImpl);
+};
+
+UpdateCheckerImpl::UpdateCheckerImpl(scoped_refptr<Configurator> config,
+                                     PersistedData* metadata)
+    : config_(config), metadata_(metadata) {}
+
+UpdateCheckerImpl::~UpdateCheckerImpl() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void UpdateCheckerImpl::CheckForUpdates(
+    const std::string& session_id,
+    const std::vector<std::string>& ids_checked,
+    const IdToComponentPtrMap& components,
+    const base::flat_map<std::string, std::string>& additional_attributes,
+    bool enabled_component_updates,
+    UpdateCheckCallback update_check_callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  ids_checked_ = ids_checked;
+  update_check_callback_ = std::move(update_check_callback);
+
+  base::PostTaskWithTraitsAndReply(
+      FROM_HERE, kTaskTraits,
+      base::BindOnce(&UpdateCheckerImpl::ReadUpdaterStateAttributes,
+                     base::Unretained(this)),
+      base::BindOnce(&UpdateCheckerImpl::CheckForUpdatesHelper,
+                     base::Unretained(this), session_id, std::cref(components),
+                     additional_attributes, enabled_component_updates));
+}
+
+// This function runs on the blocking pool task runner.
+void UpdateCheckerImpl::ReadUpdaterStateAttributes() {
+#if defined(OS_WIN)
+  // On Windows, the Chrome and the updater install modes are matched by design.
+  updater_state_attributes_ =
+      UpdaterState::GetState(!config_->IsPerUserInstall());
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+  // MacOS ignores this value in the current implementation but this may change.
+  updater_state_attributes_ = UpdaterState::GetState(false);
+#else
+// Other platforms don't have updaters.
+#endif  // OS_WIN
+}
+
+void UpdateCheckerImpl::CheckForUpdatesHelper(
+    const std::string& session_id,
+    const IdToComponentPtrMap& components,
+    const base::flat_map<std::string, std::string>& additional_attributes,
+    bool enabled_component_updates) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto urls(config_->UpdateUrl());
+  if (IsEncryptionRequired(components))
+    RemoveUnsecureUrls(&urls);
+
+  // Components in this update check are either all foreground, or all
+  // background since this member is inherited from the component's update
+  // context. Pick the state of the first component to use in the update check.
+  DCHECK(!components.empty());
+  const bool is_foreground = components.at(ids_checked_[0])->is_foreground();
+  DCHECK(
+      std::all_of(components.cbegin(), components.cend(),
+                  [is_foreground](IdToComponentPtrMap::const_reference& elem) {
+                    return is_foreground == elem.second->is_foreground();
+                  }));
+
+  std::vector<protocol_request::App> apps;
+  for (const auto& app_id : ids_checked_) {
+    DCHECK_EQ(1u, components.count(app_id));
+    const auto& component = components.at(app_id);
+    DCHECK_EQ(component->id(), app_id);
+    const auto& crx_component = component->crx_component();
+    DCHECK(crx_component);
+
+    std::string install_source;
+    if (!crx_component->install_source.empty())
+      install_source = crx_component->install_source;
+    else if (component->is_foreground())
+      install_source = "ondemand";
+
+    const bool is_update_disabled =
+        crx_component->supports_group_policy_enable_component_updates &&
+        !enabled_component_updates;
+
+    base::Version version = crx_component->version;
+#if defined(OS_STARBOARD)
+    std::string unpacked_version =
+        GetPersistedData()->GetLastUnpackedVersion(app_id);
+    // If the version of the last unpacked update package is higher than the
+    // version of the running binary, use the former to indicate the current
+    // update version in the update check request.
+    if (!unpacked_version.empty() &&
+        base::Version(unpacked_version).CompareTo(version) > 0) {
+      version = base::Version(unpacked_version);
+    }
+#endif
+    apps.push_back(MakeProtocolApp(
+        app_id, version, SanitizeBrand(config_->GetBrand()), install_source,
+        crx_component->install_location, crx_component->fingerprint,
+        SanitizeInstallerAttributes(crx_component->installer_attributes),
+        metadata_->GetCohort(app_id), metadata_->GetCohortName(app_id),
+        metadata_->GetCohortHint(app_id), crx_component->disabled_reasons,
+        MakeProtocolUpdateCheck(is_update_disabled),
+        MakeProtocolPing(app_id, metadata_)));
+  }
+  std::string updater_channel = config_->GetChannel();
+#if defined(OS_STARBOARD)
+  // If the updater channel is not set, read from pref store instead.
+  if (updater_channel.empty()) {
+    // All apps of the update use the same channel.
+    updater_channel = GetPersistedData()->GetUpdaterChannel(ids_checked_[0]);
+    if (updater_channel.empty()) {
+      updater_channel = kDefaultUpdaterChannel;
+    }
+    // Set the updater channel from the persistent store or to default channel,
+    // if it's not set already.
+    config_->SetChannel(updater_channel);
+  } else {
+    // Update the record of updater channel in pref store.
+    GetPersistedData()->SetUpdaterChannel(ids_checked_[0], updater_channel);
+  }
+#endif
+
+  const auto request = MakeProtocolRequest(
+      session_id, config_->GetProdId(),
+      config_->GetBrowserVersion().GetString(), config_->GetLang(),
+      updater_channel, config_->GetOSLongName(),
+      config_->GetDownloadPreference(), additional_attributes,
+      updater_state_attributes_.get(), std::move(apps));
+
+  request_sender_ = std::make_unique<RequestSender>(config_);
+  request_sender_->Send(
+      urls,
+      BuildUpdateCheckExtraRequestHeaders(config_->GetProdId(),
+                                          config_->GetBrowserVersion(),
+                                          ids_checked_, is_foreground),
+      config_->GetProtocolHandlerFactory()->CreateSerializer()->Serialize(
+          request),
+      config_->EnabledCupSigning(),
+      base::BindOnce(&UpdateCheckerImpl::OnRequestSenderComplete,
+                     base::Unretained(this)));
+}
+
+void UpdateCheckerImpl::OnRequestSenderComplete(int error,
+                                                const std::string& response,
+                                                int retry_after_sec) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (error) {
+    VLOG(1) << "RequestSender failed " << error;
+    UpdateCheckFailed(ErrorCategory::kUpdateCheck, error, retry_after_sec);
+    return;
+  }
+
+  auto parser = config_->GetProtocolHandlerFactory()->CreateParser();
+  if (!parser->Parse(response)) {
+    VLOG(1) << "Parse failed " << parser->errors();
+    UpdateCheckFailed(ErrorCategory::kUpdateCheck,
+                      static_cast<int>(ProtocolError::PARSE_FAILED),
+                      retry_after_sec);
+    return;
+  }
+
+  DCHECK_EQ(0, error);
+  UpdateCheckSucceeded(parser->results(), retry_after_sec);
+}
+
+void UpdateCheckerImpl::UpdateCheckSucceeded(
+    const ProtocolParser::Results& results,
+    int retry_after_sec) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  const int daynum = results.daystart_elapsed_days;
+  if (daynum != ProtocolParser::kNoDaystart) {
+    metadata_->SetDateLastActive(ids_checked_, daynum);
+    metadata_->SetDateLastRollCall(ids_checked_, daynum);
+  }
+  for (const auto& result : results.list) {
+    auto entry = result.cohort_attrs.find(ProtocolParser::Result::kCohort);
+    if (entry != result.cohort_attrs.end())
+      metadata_->SetCohort(result.extension_id, entry->second);
+    entry = result.cohort_attrs.find(ProtocolParser::Result::kCohortName);
+    if (entry != result.cohort_attrs.end())
+      metadata_->SetCohortName(result.extension_id, entry->second);
+    entry = result.cohort_attrs.find(ProtocolParser::Result::kCohortHint);
+    if (entry != result.cohort_attrs.end())
+      metadata_->SetCohortHint(result.extension_id, entry->second);
+  }
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(update_check_callback_),
+                     base::make_optional<ProtocolParser::Results>(results),
+                     ErrorCategory::kNone, 0, retry_after_sec));
+}
+
+void UpdateCheckerImpl::UpdateCheckFailed(ErrorCategory error_category,
+                                          int error,
+                                          int retry_after_sec) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_NE(0, error);
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(update_check_callback_), base::nullopt,
+                     error_category, error, retry_after_sec));
+}
+
+}  // namespace
+
+std::unique_ptr<UpdateChecker> UpdateChecker::Create(
+    scoped_refptr<Configurator> config,
+    PersistedData* persistent) {
+  return std::make_unique<UpdateCheckerImpl>(config, persistent);
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/update_checker.h b/src/components/update_client/update_checker.h
new file mode 100644
index 0000000..30e4fba
--- /dev/null
+++ b/src/components/update_client/update_checker.h
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_UPDATE_CHECKER_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_CHECKER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "components/update_client/component.h"
+#include "components/update_client/protocol_parser.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+class Configurator;
+class PersistedData;
+
+class UpdateChecker {
+ public:
+  using UpdateCheckCallback = base::OnceCallback<void(
+      const base::Optional<ProtocolParser::Results>& results,
+      ErrorCategory error_category,
+      int error,
+      int retry_after_sec)>;
+
+  using Factory =
+      std::unique_ptr<UpdateChecker> (*)(scoped_refptr<Configurator> config,
+                                         PersistedData* persistent);
+
+  virtual ~UpdateChecker() = default;
+
+  // Initiates an update check for the components specified by their ids.
+  // |additional_attributes| provides a way to customize the <request> element.
+  // |is_foreground| controls the value of "X-Goog-Update-Interactivity"
+  // header which is sent with the update check.
+  // On completion, the state of |components| is mutated as required by the
+  // server response received.
+  virtual void CheckForUpdates(
+      const std::string& session_id,
+      const std::vector<std::string>& ids_to_check,
+      const IdToComponentPtrMap& components,
+      const base::flat_map<std::string, std::string>& additional_attributes,
+      bool enabled_component_updates,
+      UpdateCheckCallback update_check_callback) = 0;
+
+  static std::unique_ptr<UpdateChecker> Create(
+      scoped_refptr<Configurator> config,
+      PersistedData* persistent);
+
+#if defined(OS_STARBOARD)
+  virtual PersistedData* GetPersistedData() = 0;
+#endif
+
+ protected:
+  UpdateChecker() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UpdateChecker);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_UPDATE_CHECKER_H_
diff --git a/src/components/update_client/update_checker_unittest.cc b/src/components/update_client/update_checker_unittest.cc
new file mode 100644
index 0000000..1554743
--- /dev/null
+++ b/src/components/update_client/update_checker_unittest.cc
@@ -0,0 +1,1218 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/update_checker.h"
+
+#include <map>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/post_task.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/version.h"
+#include "build/build_config.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/update_client/activity_data_service.h"
+#include "components/update_client/component.h"
+#include "components/update_client/net/url_loader_post_interceptor.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/test_configurator.h"
+#include "components/update_client/update_engine.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using std::string;
+
+namespace update_client {
+
+namespace {
+
+base::FilePath test_file(const char* file) {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  return path.AppendASCII("components")
+      .AppendASCII("test")
+      .AppendASCII("data")
+      .AppendASCII("update_client")
+      .AppendASCII(file);
+}
+
+const char kUpdateItemId[] = "jebgalgnebhfojomionfpkfelancnnkf";
+
+class ActivityDataServiceTest final : public ActivityDataService {
+ public:
+  bool GetActiveBit(const std::string& id) const override;
+  void ClearActiveBit(const std::string& id) override;
+  int GetDaysSinceLastActive(const std::string& id) const override;
+  int GetDaysSinceLastRollCall(const std::string& id) const override;
+
+  void SetActiveBit(const std::string& id, bool value);
+  void SetDaysSinceLastActive(const std::string& id, int daynum);
+  void SetDaysSinceLastRollCall(const std::string& id, int daynum);
+
+ private:
+  std::map<std::string, bool> actives_;
+  std::map<std::string, int> days_since_last_actives_;
+  std::map<std::string, int> days_since_last_rollcalls_;
+};
+
+bool ActivityDataServiceTest::GetActiveBit(const std::string& id) const {
+  const auto& it = actives_.find(id);
+  return it != actives_.end() ? it->second : false;
+}
+
+void ActivityDataServiceTest::ClearActiveBit(const std::string& id) {
+  SetActiveBit(id, false);
+}
+
+int ActivityDataServiceTest::GetDaysSinceLastActive(
+    const std::string& id) const {
+  const auto& it = days_since_last_actives_.find(id);
+  return it != days_since_last_actives_.end() ? it->second : -2;
+}
+
+int ActivityDataServiceTest::GetDaysSinceLastRollCall(
+    const std::string& id) const {
+  const auto& it = days_since_last_rollcalls_.find(id);
+  return it != days_since_last_rollcalls_.end() ? it->second : -2;
+}
+
+void ActivityDataServiceTest::SetActiveBit(const std::string& id, bool value) {
+  actives_[id] = value;
+}
+
+void ActivityDataServiceTest::SetDaysSinceLastActive(const std::string& id,
+                                                     int daynum) {
+  days_since_last_actives_[id] = daynum;
+}
+
+void ActivityDataServiceTest::SetDaysSinceLastRollCall(const std::string& id,
+                                                       int daynum) {
+  days_since_last_rollcalls_[id] = daynum;
+}
+
+}  // namespace
+
+class UpdateCheckerTest : public testing::TestWithParam<bool> {
+ public:
+  UpdateCheckerTest();
+  ~UpdateCheckerTest() override;
+
+  // Overrides from testing::Test.
+  void SetUp() override;
+  void TearDown() override;
+
+  void UpdateCheckComplete(
+      const base::Optional<ProtocolParser::Results>& results,
+      ErrorCategory error_category,
+      int error,
+      int retry_after_sec);
+
+ protected:
+  void Quit();
+  void RunThreads();
+
+  std::unique_ptr<Component> MakeComponent() const;
+
+  scoped_refptr<TestConfigurator> config_;
+  std::unique_ptr<ActivityDataServiceTest> activity_data_service_;
+  std::unique_ptr<TestingPrefServiceSimple> pref_;
+  std::unique_ptr<PersistedData> metadata_;
+
+  std::unique_ptr<UpdateChecker> update_checker_;
+
+  std::unique_ptr<URLLoaderPostInterceptor> post_interceptor_;
+
+  base::Optional<ProtocolParser::Results> results_;
+  ErrorCategory error_category_ = ErrorCategory::kNone;
+  int error_ = 0;
+  int retry_after_sec_ = 0;
+
+  scoped_refptr<UpdateContext> update_context_;
+
+  bool is_foreground_ = false;
+
+ private:
+  scoped_refptr<UpdateContext> MakeMockUpdateContext() const;
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  base::OnceClosure quit_closure_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateCheckerTest);
+};
+
+// This test is parameterized for |is_foreground|.
+INSTANTIATE_TEST_SUITE_P(Parameterized, UpdateCheckerTest, testing::Bool());
+
+UpdateCheckerTest::UpdateCheckerTest()
+    : scoped_task_environment_(
+          base::test::ScopedTaskEnvironment::MainThreadType::IO) {}
+
+UpdateCheckerTest::~UpdateCheckerTest() {}
+
+void UpdateCheckerTest::SetUp() {
+  is_foreground_ = GetParam();
+
+  config_ = base::MakeRefCounted<TestConfigurator>();
+
+  pref_ = std::make_unique<TestingPrefServiceSimple>();
+  activity_data_service_ = std::make_unique<ActivityDataServiceTest>();
+  PersistedData::RegisterPrefs(pref_->registry());
+  metadata_ = std::make_unique<PersistedData>(pref_.get(),
+                                              activity_data_service_.get());
+
+  post_interceptor_ = std::make_unique<URLLoaderPostInterceptor>(
+      config_->test_url_loader_factory());
+  EXPECT_TRUE(post_interceptor_);
+
+  update_checker_ = nullptr;
+
+  error_ = 0;
+  retry_after_sec_ = 0;
+  update_context_ = MakeMockUpdateContext();
+  update_context_->is_foreground = is_foreground_;
+}
+
+void UpdateCheckerTest::TearDown() {
+  update_checker_ = nullptr;
+
+  post_interceptor_.reset();
+
+  config_ = nullptr;
+
+  // The PostInterceptor requires the message loop to run to destruct correctly.
+  // TODO(sorin): This is fragile and should be fixed.
+  scoped_task_environment_.RunUntilIdle();
+}
+
+void UpdateCheckerTest::RunThreads() {
+  base::RunLoop runloop;
+  quit_closure_ = runloop.QuitClosure();
+  runloop.Run();
+}
+
+void UpdateCheckerTest::Quit() {
+  if (!quit_closure_.is_null())
+    std::move(quit_closure_).Run();
+}
+
+void UpdateCheckerTest::UpdateCheckComplete(
+    const base::Optional<ProtocolParser::Results>& results,
+    ErrorCategory error_category,
+    int error,
+    int retry_after_sec) {
+  results_ = results;
+  error_category_ = error_category;
+  error_ = error;
+  retry_after_sec_ = retry_after_sec;
+  Quit();
+}
+
+scoped_refptr<UpdateContext> UpdateCheckerTest::MakeMockUpdateContext() const {
+  return base::MakeRefCounted<UpdateContext>(
+      config_, false, std::vector<std::string>(),
+      UpdateClient::CrxDataCallback(), UpdateEngine::NotifyObserversCallback(),
+      UpdateEngine::Callback(), nullptr);
+}
+
+std::unique_ptr<Component> UpdateCheckerTest::MakeComponent() const {
+  CrxComponent crx_component;
+  crx_component.name = "test_jebg";
+  crx_component.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+  crx_component.installer = nullptr;
+  crx_component.version = base::Version("0.9");
+  crx_component.fingerprint = "fp1";
+
+  auto component = std::make_unique<Component>(*update_context_, kUpdateItemId);
+  component->state_ = std::make_unique<Component::StateNew>(component.get());
+  component->crx_component_ = crx_component;
+
+  return component;
+}
+
+// This test is parameterized for |is_foreground|.
+TEST_P(UpdateCheckerTest, UpdateCheckSuccess) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"),
+      test_file("updatecheck_reply_1.json")));
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  auto& component = components[kUpdateItemId];
+  component->crx_component_->installer_attributes["ap"] = "some_ap";
+
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components,
+      {{"extra", "params"}, {"testrequest", "1"}}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  ASSERT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  // Sanity check the request.
+  const auto root =
+      base::JSONReader::Read(post_interceptor_->GetRequestBody(0));
+  ASSERT_TRUE(root);
+  const auto* request = root->FindKey("request");
+  ASSERT_TRUE(request);
+  EXPECT_TRUE(request->FindKey("@os"));
+  EXPECT_EQ("fake_prodid", request->FindKey("@updater")->GetString());
+  EXPECT_EQ("crx2,crx3", request->FindKey("acceptformat")->GetString());
+  EXPECT_TRUE(request->FindKey("arch"));
+  EXPECT_EQ("cr", request->FindKey("dedup")->GetString());
+  EXPECT_EQ("params", request->FindKey("extra")->GetString());
+  EXPECT_LT(0, request->FindPath({"hw", "physmemory"})->GetInt());
+  EXPECT_EQ("fake_lang", request->FindKey("lang")->GetString());
+  EXPECT_TRUE(request->FindKey("nacl_arch"));
+  EXPECT_EQ("fake_channel_string",
+            request->FindKey("prodchannel")->GetString());
+  EXPECT_EQ("30.0", request->FindKey("prodversion")->GetString());
+  EXPECT_EQ("3.1", request->FindKey("protocol")->GetString());
+  EXPECT_TRUE(request->FindKey("requestid"));
+  EXPECT_TRUE(request->FindKey("sessionid"));
+  EXPECT_EQ("1", request->FindKey("testrequest")->GetString());
+  EXPECT_EQ("fake_channel_string",
+            request->FindKey("updaterchannel")->GetString());
+  EXPECT_EQ("30.0", request->FindKey("updaterversion")->GetString());
+
+  // No "dlpref" is sent by default.
+  EXPECT_FALSE(request->FindKey("dlpref"));
+
+  EXPECT_TRUE(request->FindPath({"os", "arch"})->is_string());
+  EXPECT_EQ("Fake Operating System",
+            request->FindPath({"os", "platform"})->GetString());
+  EXPECT_TRUE(request->FindPath({"os", "version"})->is_string());
+
+  const auto& app = request->FindKey("app")->GetList()[0];
+  EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+  EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+  EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+  if (is_foreground_)
+    EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+  EXPECT_EQ("some_ap", app.FindKey("ap")->GetString());
+  EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+  EXPECT_TRUE(app.FindKey("updatecheck"));
+  EXPECT_TRUE(app.FindKey("ping"));
+  EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+  EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+                       ->GetList()[0]
+                       .FindKey("fp")
+                       ->GetString());
+
+#if defined(OS_WIN)
+  EXPECT_TRUE(request->FindKey("domainjoined"));
+#if defined(GOOGLE_CHROME_BUILD)
+  const auto* updater = request->FindKey("updater");
+  EXPECT_TRUE(updater);
+  EXPECT_EQ("Omaha", updater->FindKey("name")->GetString());
+  EXPECT_TRUE(updater->FindKey("autoupdatecheckenabled")->is_bool());
+  EXPECT_TRUE(updater->FindKey("ismachine")->is_bool());
+  EXPECT_TRUE(updater->FindKey("updatepolicy")->is_int());
+#endif  // GOOGLE_CHROME_BUILD
+#endif  // OS_WIN
+
+  // Sanity check the arguments of the callback after parsing.
+  EXPECT_EQ(ErrorCategory::kNone, error_category_);
+  EXPECT_EQ(0, error_);
+  EXPECT_TRUE(results_);
+  EXPECT_EQ(1u, results_->list.size());
+  const auto& result = results_->list.front();
+  EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", result.extension_id.c_str());
+  EXPECT_EQ("1.0", result.manifest.version);
+  EXPECT_EQ("11.0.1.0", result.manifest.browser_min_version);
+  EXPECT_EQ(1u, result.manifest.packages.size());
+  EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf.crx",
+               result.manifest.packages.front().name.c_str());
+  EXPECT_EQ(1u, result.crx_urls.size());
+  EXPECT_EQ(GURL("http://localhost/download/"), result.crx_urls.front());
+  EXPECT_STREQ("this", result.action_run.c_str());
+
+  // Check the DDOS protection header values.
+  const auto extra_request_headers =
+      std::get<1>(post_interceptor_->GetRequests()[0]);
+  EXPECT_TRUE(extra_request_headers.HasHeader("X-Goog-Update-Interactivity"));
+  std::string header;
+  extra_request_headers.GetHeader("X-Goog-Update-Interactivity", &header);
+  EXPECT_STREQ(is_foreground_ ? "fg" : "bg", header.c_str());
+  extra_request_headers.GetHeader("X-Goog-Update-Updater", &header);
+  EXPECT_STREQ("fake_prodid-30.0", header.c_str());
+  extra_request_headers.GetHeader("X-Goog-Update-AppId", &header);
+  EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", header.c_str());
+}
+
+// Tests that an invalid "ap" is not serialized.
+TEST_P(UpdateCheckerTest, UpdateCheckInvalidAp) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"),
+      test_file("updatecheck_reply_1.json")));
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  // Make "ap" too long.
+  auto& component = components[kUpdateItemId];
+  component->crx_component_->installer_attributes["ap"] = std::string(257, 'a');
+
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components, {}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+
+  RunThreads();
+
+  const auto request = post_interceptor_->GetRequestBody(0);
+  const auto root = base::JSONReader::Read(request);
+  ASSERT_TRUE(root);
+  const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+  EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+  EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+  EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+  if (is_foreground_)
+    EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+  EXPECT_FALSE(app.FindKey("ap"));
+  EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+  EXPECT_TRUE(app.FindKey("updatecheck"));
+  EXPECT_TRUE(app.FindKey("ping"));
+  EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+  EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+                       ->GetList()[0]
+                       .FindKey("fp")
+                       ->GetString());
+}
+
+TEST_P(UpdateCheckerTest, UpdateCheckSuccessNoBrand) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"),
+      test_file("updatecheck_reply_1.json")));
+
+  config_->SetBrand("TOOLONG");  // Sets an invalid brand code.
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components, {}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+
+  RunThreads();
+
+  const auto request = post_interceptor_->GetRequestBody(0);
+
+  const auto root = base::JSONReader::Read(request);
+  ASSERT_TRUE(root);
+  const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+  EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+  EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+  EXPECT_FALSE(app.FindKey("brand"));
+  if (is_foreground_)
+    EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+  EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+  EXPECT_TRUE(app.FindKey("updatecheck"));
+  EXPECT_TRUE(app.FindKey("ping"));
+  EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+  EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+                       ->GetList()[0]
+                       .FindKey("fp")
+                       ->GetString());
+}
+
+// Simulates a 403 server response error.
+TEST_P(UpdateCheckerTest, UpdateCheckError) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"), net::HTTP_FORBIDDEN));
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components, {}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  EXPECT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_EQ(ErrorCategory::kUpdateCheck, error_category_);
+  EXPECT_EQ(403, error_);
+  EXPECT_FALSE(results_);
+}
+
+TEST_P(UpdateCheckerTest, UpdateCheckDownloadPreference) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"),
+      test_file("updatecheck_reply_1.json")));
+
+  config_->SetDownloadPreference(string("cacheable"));
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components,
+      {{"extra", "params"}}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  // The request must contain dlpref="cacheable".
+  const auto request = post_interceptor_->GetRequestBody(0);
+  const auto root = base::JSONReader().Read(request);
+  ASSERT_TRUE(root);
+  EXPECT_EQ("cacheable",
+            root->FindKey("request")->FindKey("dlpref")->GetString());
+}
+
+// This test is checking that an update check signed with CUP fails, since there
+// is currently no entity that can respond with a valid signed response.
+// A proper CUP test requires network mocks, which are not available now.
+TEST_P(UpdateCheckerTest, UpdateCheckCupError) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"),
+      test_file("updatecheck_reply_1.json")));
+
+  config_->SetEnabledCupSigning(true);
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components, {}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+
+  RunThreads();
+
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  ASSERT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  // Sanity check the request.
+  const auto& request = post_interceptor_->GetRequestBody(0);
+  const auto root = base::JSONReader::Read(request);
+  ASSERT_TRUE(root);
+  const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+  EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+  EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+  EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+  if (is_foreground_)
+    EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+  EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+  EXPECT_TRUE(app.FindKey("updatecheck"));
+  EXPECT_TRUE(app.FindKey("ping"));
+  EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+  EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+                       ->GetList()[0]
+                       .FindKey("fp")
+                       ->GetString());
+
+  // Expect an error since the response is not trusted.
+  EXPECT_EQ(ErrorCategory::kUpdateCheck, error_category_);
+  EXPECT_EQ(-10000, error_);
+  EXPECT_FALSE(results_);
+}
+
+// Tests that the UpdateCheckers will not make an update check for a
+// component that requires encryption when the update check URL is unsecure.
+TEST_P(UpdateCheckerTest, UpdateCheckRequiresEncryptionError) {
+  config_->SetUpdateCheckUrl(GURL("http:\\foo\bar"));
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  auto& component = components[kUpdateItemId];
+  component->crx_component_->requires_network_encryption = true;
+
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components, {}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(ErrorCategory::kUpdateCheck, error_category_);
+  EXPECT_EQ(-10002, error_);
+  EXPECT_FALSE(component->next_version_.IsValid());
+}
+
+// Tests that the PersistedData will get correctly update and reserialize
+// the elapsed_days value.
+TEST_P(UpdateCheckerTest, UpdateCheckLastRollCall) {
+  const char* filename = "updatecheck_reply_4.json";
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  // Do two update-checks.
+  activity_data_service_->SetDaysSinceLastRollCall(kUpdateItemId, 5);
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components,
+      {{"extra", "params"}}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components,
+      {{"extra", "params"}}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(2, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  ASSERT_EQ(2, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  const auto root1 =
+      base::JSONReader::Read(post_interceptor_->GetRequestBody(0));
+  ASSERT_TRUE(root1);
+  const auto& app1 = root1->FindKey("request")->FindKey("app")->GetList()[0];
+  EXPECT_EQ(5, app1.FindPath({"ping", "r"})->GetInt());
+  const auto root2 =
+      base::JSONReader::Read(post_interceptor_->GetRequestBody(1));
+  ASSERT_TRUE(root2);
+  const auto& app2 = root2->FindKey("request")->FindKey("app")->GetList()[0];
+  EXPECT_EQ(3383, app2.FindPath({"ping", "rd"})->GetInt());
+  EXPECT_TRUE(app2.FindPath({"ping", "ping_freshness"})->is_string());
+}
+
+TEST_P(UpdateCheckerTest, UpdateCheckLastActive) {
+  const char* filename = "updatecheck_reply_4.json";
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  activity_data_service_->SetActiveBit(kUpdateItemId, true);
+  activity_data_service_->SetDaysSinceLastActive(kUpdateItemId, 10);
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components,
+      {{"extra", "params"}}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  // The active bit should be reset.
+  EXPECT_FALSE(metadata_->GetActiveBit(kUpdateItemId));
+
+  activity_data_service_->SetActiveBit(kUpdateItemId, true);
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components,
+      {{"extra", "params"}}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  // The active bit should be reset.
+  EXPECT_FALSE(metadata_->GetActiveBit(kUpdateItemId));
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components,
+      {{"extra", "params"}}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_FALSE(metadata_->GetActiveBit(kUpdateItemId));
+
+  EXPECT_EQ(3, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  ASSERT_EQ(3, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  {
+    const auto root =
+        base::JSONReader::Read(post_interceptor_->GetRequestBody(0));
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(10, app.FindPath({"ping", "a"})->GetInt());
+    EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+  }
+  {
+    const auto root =
+        base::JSONReader::Read(post_interceptor_->GetRequestBody(1));
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(3383, app.FindPath({"ping", "ad"})->GetInt());
+    EXPECT_EQ(3383, app.FindPath({"ping", "rd"})->GetInt());
+    EXPECT_TRUE(app.FindPath({"ping", "ping_freshness"})->is_string());
+  }
+  {
+    const auto root =
+        base::JSONReader::Read(post_interceptor_->GetRequestBody(2));
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(3383, app.FindPath({"ping", "rd"})->GetInt());
+    EXPECT_TRUE(app.FindPath({"ping", "ping_freshness"})->is_string());
+  }
+}
+
+TEST_P(UpdateCheckerTest, UpdateCheckInstallSource) {
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  auto& component = components[kUpdateItemId];
+  auto crx_component = component->crx_component();
+
+  if (is_foreground_) {
+    {
+      auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+          config_->test_url_loader_factory());
+      EXPECT_TRUE(post_interceptor->ExpectRequest(
+          std::make_unique<PartialMatch>("updatecheck"),
+          test_file("updatecheck_reply_1.json")));
+      update_checker_->CheckForUpdates(
+          update_context_->session_id, {kUpdateItemId}, components, {}, false,
+          base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                         base::Unretained(this)));
+      RunThreads();
+      const auto& request = post_interceptor->GetRequestBody(0);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+      EXPECT_FALSE(app.FindKey("installedby"));
+    }
+    {
+      auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+          config_->test_url_loader_factory());
+      EXPECT_TRUE(post_interceptor->ExpectRequest(
+          std::make_unique<PartialMatch>("updatecheck"),
+          test_file("updatecheck_reply_1.json")));
+      crx_component->install_source = "sideload";
+      crx_component->install_location = "policy";
+      component->set_crx_component(*crx_component);
+      update_checker_->CheckForUpdates(
+          update_context_->session_id, {kUpdateItemId}, components, {}, false,
+          base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                         base::Unretained(this)));
+      RunThreads();
+      const auto& request = post_interceptor->GetRequestBody(0);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ("sideload", app.FindKey("installsource")->GetString());
+      EXPECT_EQ("policy", app.FindKey("installedby")->GetString());
+    }
+    return;
+  }
+
+  DCHECK(!is_foreground_);
+  {
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_FALSE(app.FindKey("installsource"));
+  }
+  {
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    crx_component->install_source = "webstore";
+    crx_component->install_location = "external";
+    component->set_crx_component(*crx_component);
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ("webstore", app.FindKey("installsource")->GetString());
+    EXPECT_EQ("external", app.FindKey("installedby")->GetString());
+  }
+}
+
+TEST_P(UpdateCheckerTest, ComponentDisabled) {
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  auto& component = components[kUpdateItemId];
+  auto crx_component = component->crx_component();
+
+  {
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+    EXPECT_FALSE(app.FindKey("disabled"));
+  }
+
+  {
+    crx_component->disabled_reasons = {};
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+    EXPECT_FALSE(app.FindKey("disabled"));
+  }
+
+  {
+    crx_component->disabled_reasons = {0};
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+    const auto& disabled = app.FindKey("disabled")->GetList();
+    EXPECT_EQ(1u, disabled.size());
+    EXPECT_EQ(0, disabled[0].FindKey("reason")->GetInt());
+  }
+  {
+    crx_component->disabled_reasons = {1};
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+    const auto& disabled = app.FindKey("disabled")->GetList();
+    EXPECT_EQ(1u, disabled.size());
+    EXPECT_EQ(1, disabled[0].FindKey("reason")->GetInt());
+  }
+
+  {
+    crx_component->disabled_reasons = {4, 8, 16};
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+    const auto& disabled = app.FindKey("disabled")->GetList();
+    EXPECT_EQ(3u, disabled.size());
+    EXPECT_EQ(4, disabled[0].FindKey("reason")->GetInt());
+    EXPECT_EQ(8, disabled[1].FindKey("reason")->GetInt());
+    EXPECT_EQ(16, disabled[2].FindKey("reason")->GetInt());
+  }
+
+  {
+    crx_component->disabled_reasons = {0, 4, 8, 16};
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+    const auto& disabled = app.FindKey("disabled")->GetList();
+    EXPECT_EQ(4u, disabled.size());
+    EXPECT_EQ(0, disabled[0].FindKey("reason")->GetInt());
+    EXPECT_EQ(4, disabled[1].FindKey("reason")->GetInt());
+    EXPECT_EQ(8, disabled[2].FindKey("reason")->GetInt());
+    EXPECT_EQ(16, disabled[3].FindKey("reason")->GetInt());
+  }
+}
+
+TEST_P(UpdateCheckerTest, UpdateCheckUpdateDisabled) {
+  config_->SetBrand("");
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  auto& component = components[kUpdateItemId];
+  auto crx_component = component->crx_component();
+
+  // Ignore this test parameter to keep the test simple.
+  update_context_->is_foreground = false;
+  {
+    // Tests the scenario where:
+    //  * the component does not support group policies.
+    //  * the component updates are disabled.
+    // Expects the group policy to be ignored and the update check to not
+    // include the "updatedisabled" attribute.
+    EXPECT_FALSE(crx_component->supports_group_policy_enable_component_updates);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+    EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+  }
+  {
+    // Tests the scenario where:
+    //  * the component supports group policies.
+    //  * the component updates are disabled.
+    // Expects the update check to include the "updatedisabled" attribute.
+    crx_component->supports_group_policy_enable_component_updates = true;
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+    EXPECT_TRUE(app.FindPath({"updatecheck", "updatedisabled"})->GetBool());
+  }
+  {
+    // Tests the scenario where:
+    //  * the component does not support group policies.
+    //  * the component updates are enabled.
+    // Expects the update check to not include the "updatedisabled" attribute.
+    crx_component->supports_group_policy_enable_component_updates = false;
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, true,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+    EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+  }
+  {
+    // Tests the scenario where:
+    //  * the component supports group policies.
+    //  * the component updates are enabled.
+    // Expects the update check to not include the "updatedisabled" attribute.
+    crx_component->supports_group_policy_enable_component_updates = true;
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file("updatecheck_reply_1.json")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, true,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+    EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+  }
+}
+
+TEST_P(UpdateCheckerTest, NoUpdateActionRun) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"),
+      test_file("updatecheck_reply_noupdate.json")));
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components, {}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  ASSERT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  // Sanity check the arguments of the callback after parsing.
+  EXPECT_EQ(ErrorCategory::kNone, error_category_);
+  EXPECT_EQ(0, error_);
+  EXPECT_TRUE(results_);
+  EXPECT_EQ(1u, results_->list.size());
+  const auto& result = results_->list.front();
+  EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", result.extension_id.c_str());
+  EXPECT_STREQ("noupdate", result.status.c_str());
+  EXPECT_STREQ("this", result.action_run.c_str());
+}
+
+TEST_P(UpdateCheckerTest, UpdatePauseResume) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"),
+      test_file("updatecheck_reply_noupdate.json")));
+  post_interceptor_->url_job_request_ready_callback(base::BindOnce(
+      [](URLLoaderPostInterceptor* post_interceptor) {
+        post_interceptor->Resume();
+      },
+      post_interceptor_.get()));
+  post_interceptor_->Pause();
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  // Ignore this test parameter to keep the test simple.
+  update_context_->is_foreground = false;
+
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components, {}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  const auto& request = post_interceptor_->GetRequestBody(0);
+  const auto root = base::JSONReader::Read(request);
+  ASSERT_TRUE(root);
+  const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+  EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+  EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+  EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+  EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+  EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+  EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+  EXPECT_EQ("fp1", app.FindKey("packages")
+                       ->FindKey("package")
+                       ->GetList()[0]
+                       .FindKey("fp")
+                       ->GetString());
+}
+
+// Tests that an update checker object and its underlying SimpleURLLoader can
+// be safely destroyed while it is paused.
+TEST_P(UpdateCheckerTest, UpdateResetUpdateChecker) {
+  base::RunLoop runloop;
+  auto quit_closure = runloop.QuitClosure();
+
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"),
+      test_file("updatecheck_reply_1.json")));
+  post_interceptor_->url_job_request_ready_callback(base::BindOnce(
+      [](base::OnceClosure quit_closure) { std::move(quit_closure).Run(); },
+      std::move(quit_closure)));
+  post_interceptor_->Pause();
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components, {}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  runloop.Run();
+}
+
+// The update response contains a protocol version which does not match the
+// expected protocol version.
+TEST_P(UpdateCheckerTest, ParseErrorProtocolVersionMismatch) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"),
+      test_file("updatecheck_reply_parse_error.json")));
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components, {}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  ASSERT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_EQ(ErrorCategory::kUpdateCheck, error_category_);
+  EXPECT_EQ(-10003, error_);
+  EXPECT_FALSE(results_);
+}
+
+// The update response contains a status |error-unknownApplication| for the
+// app. The response is succesfully parsed and a result is extracted to
+// indicate this status.
+TEST_P(UpdateCheckerTest, ParseErrorAppStatusErrorUnknownApplication) {
+  EXPECT_TRUE(post_interceptor_->ExpectRequest(
+      std::make_unique<PartialMatch>("updatecheck"),
+      test_file("updatecheck_reply_unknownapp.json")));
+
+  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+  IdToComponentPtrMap components;
+  components[kUpdateItemId] = MakeComponent();
+
+  update_checker_->CheckForUpdates(
+      update_context_->session_id, {kUpdateItemId}, components, {}, true,
+      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                     base::Unretained(this)));
+  RunThreads();
+
+  EXPECT_EQ(1, post_interceptor_->GetHitCount())
+      << post_interceptor_->GetRequestsAsString();
+  ASSERT_EQ(1, post_interceptor_->GetCount())
+      << post_interceptor_->GetRequestsAsString();
+
+  EXPECT_EQ(ErrorCategory::kNone, error_category_);
+  EXPECT_EQ(0, error_);
+  EXPECT_TRUE(results_);
+  EXPECT_EQ(1u, results_->list.size());
+  const auto& result = results_->list.front();
+  EXPECT_STREQ("error-unknownApplication", result.status.c_str());
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/update_client.cc b/src/components/update_client/update_client.cc
new file mode 100644
index 0000000..369c6d3
--- /dev/null
+++ b/src/components/update_client/update_client.cc
@@ -0,0 +1,254 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/update_client.h"
+
+#include <algorithm>
+#include <queue>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/stl_util.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/crx_update_item.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/ping_manager.h"
+#include "components/update_client/protocol_parser.h"
+#include "components/update_client/task_send_uninstall_ping.h"
+#include "components/update_client/task_update.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/update_client_internal.h"
+#include "components/update_client/update_engine.h"
+#include "components/update_client/utils.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+CrxUpdateItem::CrxUpdateItem() : state(ComponentState::kNew) {}
+CrxUpdateItem::~CrxUpdateItem() = default;
+CrxUpdateItem::CrxUpdateItem(const CrxUpdateItem& other) = default;
+
+CrxComponent::CrxComponent()
+    : allows_background_download(true),
+      requires_network_encryption(true),
+      crx_format_requirement(
+          crx_file::VerifierFormat::CRX3_WITH_PUBLISHER_PROOF),
+      supports_group_policy_enable_component_updates(false) {}
+CrxComponent::CrxComponent(const CrxComponent& other) = default;
+CrxComponent::~CrxComponent() = default;
+
+// It is important that an instance of the UpdateClient binds an unretained
+// pointer to itself. Otherwise, a life time circular dependency between this
+// instance and its inner members prevents the destruction of this instance.
+// Using unretained references is allowed in this case since the life time of
+// the UpdateClient instance exceeds the life time of its inner members,
+// including any thread objects that might execute callbacks bound to it.
+UpdateClientImpl::UpdateClientImpl(
+    scoped_refptr<Configurator> config,
+    scoped_refptr<PingManager> ping_manager,
+    UpdateChecker::Factory update_checker_factory,
+    CrxDownloader::Factory crx_downloader_factory)
+    : is_stopped_(false),
+      config_(config),
+      ping_manager_(ping_manager),
+      update_engine_(base::MakeRefCounted<UpdateEngine>(
+          config,
+          update_checker_factory,
+          crx_downloader_factory,
+          ping_manager_.get(),
+          base::Bind(&UpdateClientImpl::NotifyObservers,
+                     base::Unretained(this)))) {}
+
+UpdateClientImpl::~UpdateClientImpl() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  DCHECK(task_queue_.empty());
+  DCHECK(tasks_.empty());
+
+  config_ = nullptr;
+}
+
+void UpdateClientImpl::Install(const std::string& id,
+                               CrxDataCallback crx_data_callback,
+                               Callback callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (IsUpdating(id)) {
+    std::move(callback).Run(Error::UPDATE_IN_PROGRESS);
+    return;
+  }
+
+  std::vector<std::string> ids = {id};
+
+  // Install tasks are run concurrently and never queued up. They are always
+  // considered foreground tasks.
+  constexpr bool kIsForeground = true;
+  RunTask(base::MakeRefCounted<TaskUpdate>(
+      update_engine_.get(), kIsForeground, ids, std::move(crx_data_callback),
+      base::BindOnce(&UpdateClientImpl::OnTaskComplete, this,
+                     std::move(callback))));
+}
+
+void UpdateClientImpl::Update(const std::vector<std::string>& ids,
+                              CrxDataCallback crx_data_callback,
+                              bool is_foreground,
+                              Callback callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  auto task = base::MakeRefCounted<TaskUpdate>(
+      update_engine_.get(), is_foreground, ids, std::move(crx_data_callback),
+      base::BindOnce(&UpdateClientImpl::OnTaskComplete, this,
+                     std::move(callback)));
+
+  // If no other tasks are running at the moment, run this update task.
+  // Otherwise, queue the task up.
+  if (tasks_.empty()) {
+    RunTask(task);
+  } else {
+    task_queue_.push_back(task);
+  }
+}
+
+void UpdateClientImpl::RunTask(scoped_refptr<Task> task) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&Task::Run, base::Unretained(task.get())));
+  tasks_.insert(task);
+}
+
+void UpdateClientImpl::OnTaskComplete(Callback callback,
+                                      scoped_refptr<Task> task,
+                                      Error error) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(task);
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), error));
+
+  // Remove the task from the set of the running tasks. Only tasks handled by
+  // the update engine can be in this data structure.
+  tasks_.erase(task);
+
+  if (is_stopped_)
+    return;
+
+  // Pick up a task from the queue if the queue has pending tasks and no other
+  // task is running.
+  if (tasks_.empty() && !task_queue_.empty()) {
+    auto task = task_queue_.front();
+    task_queue_.pop_front();
+    RunTask(task);
+  }
+}
+
+void UpdateClientImpl::AddObserver(Observer* observer) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  observer_list_.AddObserver(observer);
+}
+
+void UpdateClientImpl::RemoveObserver(Observer* observer) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  observer_list_.RemoveObserver(observer);
+}
+
+void UpdateClientImpl::NotifyObservers(Observer::Events event,
+                                       const std::string& id) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  for (auto& observer : observer_list_)
+    observer.OnEvent(event, id);
+}
+
+bool UpdateClientImpl::GetCrxUpdateState(const std::string& id,
+                                         CrxUpdateItem* update_item) const {
+  return update_engine_->GetUpdateState(id, update_item);
+}
+
+bool UpdateClientImpl::IsUpdating(const std::string& id) const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  for (const auto task : tasks_) {
+    const auto ids = task->GetIds();
+    if (base::ContainsValue(ids, id)) {
+      return true;
+    }
+  }
+
+  for (const auto task : task_queue_) {
+    const auto ids = task->GetIds();
+    if (base::ContainsValue(ids, id)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void UpdateClientImpl::Stop() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  is_stopped_ = true;
+
+  // In the current implementation it is sufficient to cancel the pending
+  // tasks only. The tasks that are run by the update engine will stop
+  // making progress naturally, as the main task runner stops running task
+  // actions. Upon the browser shutdown, the resources employed by the active
+  // tasks will leak, as the operating system kills the thread associated with
+  // the update engine task runner. Further refactoring may be needed in this
+  // area, to cancel the running tasks by canceling the current action update.
+  // This behavior would be expected, correct, and result in no resource leaks
+  // in all cases, in shutdown or not.
+  //
+  // Cancel the pending tasks. These tasks are safe to cancel and delete since
+  // they have not picked up by the update engine, and not shared with any
+  // task runner yet.
+  while (!task_queue_.empty()) {
+    auto task = task_queue_.front();
+    task_queue_.pop_front();
+    task->Cancel();
+  }
+}
+
+void UpdateClientImpl::SendUninstallPing(const std::string& id,
+                                         const base::Version& version,
+                                         int reason,
+                                         Callback callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  RunTask(base::MakeRefCounted<TaskSendUninstallPing>(
+      update_engine_.get(), id, version, reason,
+      base::BindOnce(&UpdateClientImpl::OnTaskComplete, base::Unretained(this),
+                     std::move(callback))));
+}
+
+scoped_refptr<UpdateClient> UpdateClientFactory(
+    scoped_refptr<Configurator> config) {
+  return base::MakeRefCounted<UpdateClientImpl>(
+      config, base::MakeRefCounted<PingManager>(config), &UpdateChecker::Create,
+      &CrxDownloader::Create);
+}
+
+void RegisterPrefs(PrefRegistrySimple* registry) {
+  PersistedData::RegisterPrefs(registry);
+}
+
+// This function has the exact same implementation as RegisterPrefs. We have
+// this implementation here to make the intention more clear that is local user
+// profile access is needed.
+void RegisterProfilePrefs(PrefRegistrySimple* registry) {
+  PersistedData::RegisterPrefs(registry);
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/update_client.gyp b/src/components/update_client/update_client.gyp
new file mode 100644
index 0000000..45dc09d
--- /dev/null
+++ b/src/components/update_client/update_client.gyp
@@ -0,0 +1,113 @@
+# Copyright 2019 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'targets': [
+    {
+      'target_name': 'update_client',
+      'type': 'static_library',
+      'sources': [
+        'action_runner.cc',
+        'action_runner.h',
+        'activity_data_service.h',
+        'command_line_config_policy.cc',
+        'command_line_config_policy.h',
+        'component.cc',
+        'component.h',
+        'component_patcher.cc',
+        'component_patcher.h',
+        'component_patcher_operation.cc',
+        'component_patcher_operation.h',
+        'component_unpacker.cc',
+        'component_unpacker.h',
+        'configurator.h',
+        'crx_downloader.cc',
+        'crx_downloader.h',
+        'crx_update_item.h',
+        'network.cc',
+        'network.h',
+        'patcher.h',
+        'persisted_data.cc',
+        'persisted_data.h',
+        'ping_manager.cc',
+        'ping_manager.h',
+        'protocol_definition.cc',
+        'protocol_definition.h',
+        'protocol_handler.cc',
+        'protocol_handler.h',
+        'protocol_parser.cc',
+        'protocol_parser.h',
+        'protocol_parser_json.cc',
+        'protocol_parser_json.h',
+        'protocol_serializer.cc',
+        'protocol_serializer.h',
+        'protocol_serializer_json.cc',
+        'protocol_serializer_json.h',
+        'request_sender.cc',
+        'request_sender.h',
+        'task.h',
+        'task_send_uninstall_ping.cc',
+        'task_send_uninstall_ping.h',
+        'task_traits.h',
+        'task_update.cc',
+        'task_update.h',
+        'unzipper.h',
+        'update_checker.cc',
+        'update_checker.h',
+        'update_client.cc',
+        'update_client.h',
+        'update_client_errors.h',
+        'update_client_internal.h',
+        'update_engine.cc',
+        'update_engine.h',
+        'update_query_params.cc',
+        'update_query_params.h',
+        'update_query_params_delegate.cc',
+        'update_query_params_delegate.h',
+        'updater_state.cc',
+        'updater_state.h',
+        'url_fetcher_downloader.cc',
+        'url_fetcher_downloader.h',
+        'utils.cc',
+        'utils.h',
+      ],
+      'dependencies': [
+        '<(DEPTH)/third_party/libxml/libxml.gyp:libxml',
+        '<(DEPTH)/components/crx_file/crx_file.gyp:crx_file',
+        '<(DEPTH)/components/client_update_protocol/client_update_protocol.gyp:client_update_protocol',
+        '<(DEPTH)/components/prefs/prefs.gyp:prefs',
+        '<(DEPTH)/crypto/crypto.gyp:crypto',
+        '<(DEPTH)/url/url.gyp:url',
+      ],
+      'defines': [
+        'LIBXML_READER_ENABLED',
+        'LIBXML_WRITER_ENABLED',
+      ],
+    },
+    {
+      'target_name': 'update_client_test',
+      'type': '<(gtest_target_type)',
+      'sources': [
+        'utils_unittest.cc',
+      ],
+      'dependencies': [
+        ':update_client',
+        '<(DEPTH)/cobalt/base/base.gyp:base',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+      ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
+    },
+  ]
+}
diff --git a/src/components/update_client/update_client.h b/src/components/update_client/update_client.h
new file mode 100644
index 0000000..4b8061c
--- /dev/null
+++ b/src/components/update_client/update_client.h
@@ -0,0 +1,423 @@
+// Copyright 2015 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 COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/version.h"
+#include "components/update_client/update_client_errors.h"
+
+// The UpdateClient class is a facade with a simple interface. The interface
+// exposes a few APIs to install a CRX or update a group of CRXs.
+//
+// The difference between a CRX install and a CRX update is relatively minor.
+// The terminology going forward will use the word "update" to cover both
+// install and update scenarios, except where details regarding the install
+// case are relevant.
+//
+// Handling an update consists of a series of actions such as sending an update
+// check to the server, followed by parsing the server response, identifying
+// the CRXs that require an update, downloading the differential update if
+// it is available, unpacking and patching the differential update, then
+// falling back to trying a similar set of actions using the full update.
+// At the end of this process, completion pings are sent to the server,
+// as needed, for the CRXs which had updates.
+//
+// As a general idea, this code handles the action steps needed to update
+// a group of components serially, one step at a time. However, concurrent
+// execution of calls to UpdateClient::Update is possible, therefore,
+// queuing of updates could happen in some cases. More below.
+//
+// The UpdateClient class features a subject-observer interface to observe
+// the CRX state changes during an update.
+//
+// The threading model for this code assumes that most of the code in the
+// public interface runs on a SingleThreadTaskRunner.
+// This task runner corresponds to the browser UI thread in many cases. There
+// are parts of the installer interface that run on blocking task runners, which
+// are usually threads in a thread pool.
+//
+// Using the UpdateClient is relatively easy. This assumes that the client
+// of this code has already implemented the observer interface as needed, and
+// can provide an installer, as described below.
+//
+//    std::unique_ptr<UpdateClient> update_client(UpdateClientFactory(...));
+//    update_client->AddObserver(&observer);
+//    std::vector<std::string> ids;
+//    ids.push_back(...));
+//    update_client->Update(ids, base::BindOnce(...), base::BindOnce(...));
+//
+// UpdateClient::Update takes two callbacks as parameters. First callback
+// allows the client of this code to provide an instance of CrxComponent
+// data structure that specifies additional parameters of the update.
+// CrxComponent has a CrxInstaller data member, which must be provided by the
+// callers of this class. The second callback indicates that this non-blocking
+// call has completed.
+//
+// There could be several ways of triggering updates for a CRX, user-initiated,
+// or timer-based. Since the execution of updates is concurrent, the parameters
+// for the update must be provided right before the update is handled.
+// Otherwise, the version of the CRX set in the CrxComponent may not be correct.
+//
+// The UpdateClient public interface includes two functions: Install and
+// Update. These functions correspond to installing one CRX immediately as a
+// foreground activity (Install), and updating a group of CRXs silently in the
+// background (Update). This distinction is important. Background updates are
+// queued up and their actions run serially, one at a time, for the purpose of
+// conserving local resources such as CPU, network, and I/O.
+// On the other hand, installs are never queued up but run concurrently, as
+// requested by the user.
+//
+// The update client introduces a runtime constraint regarding interleaving
+// updates and installs. If installs or updates for a given CRX are in progress,
+// then installs for the same CRX will fail with a specific error.
+//
+// Implementation details.
+//
+// The implementation details below are not relevant to callers of this
+// code. However, these design notes are relevant to the owners and maintainers
+// of this module.
+//
+// The design for the update client consists of a number of abstractions
+// such as: task, update engine, update context, and action.
+// The execution model for these abstractions is simple. They usually expose
+// a public, non-blocking Run function, and they invoke a callback when
+// the Run function has completed.
+//
+// A task is the unit of work for the UpdateClient. A task is associated
+// with a single call of the Update function. A task represents a group
+// of CRXs that are updated together.
+//
+// The UpdateClient is responsible for the queuing of tasks, if queuing is
+// needed.
+//
+// When the task runs, it calls the update engine to handle the updates for
+// the CRXs associated with the task. The UpdateEngine is the abstraction
+// responsible for breaking down the update in a set of discrete steps, which
+// are implemented as actions, and running the actions.
+//
+// The UpdateEngine maintains a set of UpdateContext instances. Each of
+// these instances maintains the update state for all the CRXs belonging to
+// a given task. The UpdateContext contains a queue of CRX ids.
+// The UpdateEngine will handle updates for the CRXs in the order they appear
+// in the queue, until the queue is empty.
+//
+// The update state for each CRX is maintained in a container of CrxUpdateItem*.
+// As actions run, each action updates the CRX state, represented by one of
+// these CrxUpdateItem* instances.
+//
+// Although the UpdateEngine can and will run update tasks concurrently, the
+// actions of a task are run sequentially.
+//
+// The Action is a polymorphic type. There is some code reuse for convenience,
+// implemented as a mixin. The polymorphic behavior of some of the actions
+// is achieved using a template method.
+//
+// State changes of a CRX could generate events, which are observed using a
+// subject-observer interface.
+//
+// The actions chain up. In some sense, the actions implement a state machine,
+// as the CRX undergoes a series of state transitions in the process of
+// being checked for updates and applying the update.
+
+class PrefRegistrySimple;
+
+namespace base {
+class FilePath;
+}
+
+namespace crx_file {
+enum class VerifierFormat;
+}
+
+namespace update_client {
+
+class Configurator;
+enum class Error;
+struct CrxUpdateItem;
+
+enum class ComponentState {
+  kNew,
+  kChecking,
+  kCanUpdate,
+  kDownloadingDiff,
+  kDownloading,
+  kDownloaded,
+  kUpdatingDiff,
+  kUpdating,
+  kUpdated,
+  kUpToDate,
+  kUpdateError,
+  kUninstalled,
+  kRun,
+  kLastStatus
+};
+
+// Defines an interface for a generic CRX installer.
+class CrxInstaller : public base::RefCountedThreadSafe<CrxInstaller> {
+ public:
+  // Contains the result of the Install operation.
+  struct Result {
+    explicit Result(int error, int extended_error = 0)
+        : error(error), extended_error(extended_error) {}
+    explicit Result(InstallError error, int extended_error = 0)
+        : error(static_cast<int>(error)), extended_error(extended_error) {}
+    int error = 0;  // 0 indicates that install has been successful.
+    int extended_error = 0;
+  };
+
+  using Callback = base::OnceCallback<void(const Result& result)>;
+
+  // Called on the main thread when there was a problem unpacking or
+  // verifying the CRX. |error| is a non-zero value which is only meaningful
+  // to the caller.
+  virtual void OnUpdateError(int error) = 0;
+
+  // Called by the update service when a CRX has been unpacked
+  // and it is ready to be installed. |unpack_path| contains the
+  // temporary directory with all the unpacked CRX files. |pubkey| contains the
+  // public key of the CRX in the PEM format, without the header and the footer.
+  // The caller must invoke the |callback| when the install flow has completed.
+  // This method may be called from a thread other than the main thread.
+  virtual void Install(const base::FilePath& unpack_path,
+                       const std::string& public_key,
+                       Callback callback) = 0;
+
+  // Sets |installed_file| to the full path to the installed |file|. |file| is
+  // the filename of the file in this CRX. Returns false if this is
+  // not possible (the file has been removed or modified, or its current
+  // location is unknown). Otherwise, it returns true.
+  virtual bool GetInstalledFile(const std::string& file,
+                                base::FilePath* installed_file) = 0;
+
+  // Called when a CRX has been unregistered and all versions should
+  // be uninstalled from disk. Returns true if uninstallation is supported,
+  // and false otherwise.
+  virtual bool Uninstall() = 0;
+
+ protected:
+  friend class base::RefCountedThreadSafe<CrxInstaller>;
+
+  virtual ~CrxInstaller() {}
+};
+
+// A dictionary of installer-specific, arbitrary name-value pairs, which
+// may be used in the update checks requests.
+using InstallerAttributes = std::map<std::string, std::string>;
+
+struct CrxComponent {
+  CrxComponent();
+  CrxComponent(const CrxComponent& other);
+  ~CrxComponent();
+
+  // Optional SHA256 hash of the CRX's public key. If not supplied, the
+  // unpacker can accept any CRX for this app, provided that the CRX meets the
+  // VerifierFormat requirements specified by the service's configurator.
+  // Callers that know or need a specific developer signature on acceptable CRX
+  // files must provide this.
+  std::vector<uint8_t> pk_hash;
+
+  scoped_refptr<CrxInstaller> installer;
+  std::string app_id;
+
+  // The current version if the CRX is updated. Otherwise, "0" or "0.0" if
+  // the CRX is installed.
+  base::Version version;
+
+  std::string fingerprint;  // Optional.
+  std::string name;         // Optional.
+  std::vector<std::string> handled_mime_types;
+
+  // Optional.
+  // Valid values for the name part of an attribute match
+  // ^[-_a-zA-Z0-9]{1,256}$ and valid values the value part of an attribute
+  // match ^[-.,;+_=a-zA-Z0-9]{0,256}$ .
+  InstallerAttributes installer_attributes;
+
+  // Specifies that the CRX can be background-downloaded in some cases.
+  // The default for this value is |true|.
+  bool allows_background_download;
+
+  // Specifies that the update checks and pings associated with this component
+  // require confidentiality. The default for this value is |true|. As a side
+  // note, the confidentiality of the downloads is enforced by the server,
+  // which only returns secure download URLs in this case.
+  bool requires_network_encryption;
+
+  // Specifies the strength of package validation required for the item.
+  crx_file::VerifierFormat crx_format_requirement;
+
+  // True if the component allows enabling or disabling updates by group policy.
+  // This member should be set to |false| for data, non-binary components, such
+  // as CRLSet, Supervised User Whitelists, STH Set, Origin Trials, and File
+  // Type Policies.
+  bool supports_group_policy_enable_component_updates;
+
+  // Reasons why this component/extension is disabled.
+  std::vector<int> disabled_reasons;
+
+  // Information about where the component/extension was installed from.
+  // For extension, this information is set from the update service, which
+  // gets the install source from the update URL.
+  std::string install_source;
+
+  // Information about where the component/extension was loaded from.
+  // For extensions, this information is inferred from the extension
+  // registry.
+  std::string install_location;
+};
+
+// Called when a non-blocking call of UpdateClient completes.
+using Callback = base::OnceCallback<void(Error error)>;
+
+// All methods are safe to call only from the browser's main thread. Once an
+// instance of this class is created, the reference to it must be released
+// only after the thread pools of the browser process have been destroyed and
+// the browser process has gone single-threaded.
+class UpdateClient : public base::RefCounted<UpdateClient> {
+ public:
+  using CrxDataCallback =
+      base::OnceCallback<std::vector<base::Optional<CrxComponent>>(
+          const std::vector<std::string>& ids)>;
+
+  // Defines an interface to observe the UpdateClient. It provides
+  // notifications when state changes occur for the service itself or for the
+  // registered CRXs.
+  class Observer {
+   public:
+    enum class Events {
+      // Sent before the update client does an update check.
+      COMPONENT_CHECKING_FOR_UPDATES = 1,
+
+      // Sent when there is a new version of a registered CRX. After
+      // the notification is sent the CRX will be downloaded unless the
+      // update client inserts a
+      COMPONENT_UPDATE_FOUND,
+
+      // Sent when a CRX is in the update queue but it can't be acted on
+      // right away, because the update client spaces out CRX updates due to a
+      // throttling policy.
+      COMPONENT_WAIT,
+
+      // Sent after the new CRX has been downloaded but before the install
+      // or the upgrade is attempted.
+      COMPONENT_UPDATE_READY,
+
+      // Sent when a CRX has been successfully updated.
+      COMPONENT_UPDATED,
+
+      // Sent when a CRX has not been updated because there was no update
+      // available for this component.
+      COMPONENT_NOT_UPDATED,
+
+      // Sent when an error ocurred during an update for any reason, including
+      // the update check itself failed, or the download of the update payload
+      // failed, or applying the update failed.
+      COMPONENT_UPDATE_ERROR,
+
+      // Sent when CRX bytes are being downloaded.
+      COMPONENT_UPDATE_DOWNLOADING,
+    };
+
+    virtual ~Observer() {}
+
+    // Called by the update client when a state change happens.
+    // If an |id| is specified, then the event is fired on behalf of the
+    // specific CRX. The implementors of this interface are
+    // expected to filter the relevant events based on the id of the CRX.
+    virtual void OnEvent(Events event, const std::string& id) = 0;
+  };
+
+  // Adds an observer for this class. An observer should not be added more
+  // than once. The caller retains the ownership of the observer object.
+  virtual void AddObserver(Observer* observer) = 0;
+
+  // Removes an observer. It is safe for an observer to be removed while
+  // the observers are being notified.
+  virtual void RemoveObserver(Observer* observer) = 0;
+
+  // Installs the specified CRX. Calls back on |callback| after the
+  // update has been handled. The |error| parameter of the |callback|
+  // contains an error code in the case of a run-time error, or 0 if the
+  // install has been handled successfully. Overlapping calls of this function
+  // are executed concurrently, as long as the id parameter is different,
+  // meaning that installs of different components are parallelized.
+  // The |Install| function is intended to be used for foreground installs of
+  // one CRX. These cases are usually associated with on-demand install
+  // scenarios, which are triggered by user actions. Installs are never
+  // queued up.
+  virtual void Install(const std::string& id,
+                       CrxDataCallback crx_data_callback,
+                       Callback callback) = 0;
+
+  // Updates the specified CRXs. Calls back on |crx_data_callback| before the
+  // update is attempted to give the caller the opportunity to provide the
+  // instances of CrxComponent to be used for this update. The |Update| function
+  // is intended to be used for background updates of several CRXs. Overlapping
+  // calls to this function result in a queuing behavior, and the execution
+  // of each call is serialized. In addition, updates are always queued up when
+  // installs are running. The |is_foreground| parameter must be set to true if
+  // the invocation of this function is a result of a user initiated update.
+  virtual void Update(const std::vector<std::string>& ids,
+                      CrxDataCallback crx_data_callback,
+                      bool is_foreground,
+                      Callback callback) = 0;
+
+  // Sends an uninstall ping for the CRX identified by |id| and |version|. The
+  // |reason| parameter is defined by the caller. The current implementation of
+  // this function only sends a best-effort, fire-and-forget ping. It has no
+  // other side effects regarding installs or updates done through an instance
+  // of this class.
+  virtual void SendUninstallPing(const std::string& id,
+                                 const base::Version& version,
+                                 int reason,
+                                 Callback callback) = 0;
+
+  // Returns status details about a CRX update. The function returns true in
+  // case of success and false in case of errors, such as |id| was
+  // invalid or not known.
+  virtual bool GetCrxUpdateState(const std::string& id,
+                                 CrxUpdateItem* update_item) const = 0;
+
+  // Returns true if the |id| is found in any running task.
+  virtual bool IsUpdating(const std::string& id) const = 0;
+
+  // Cancels the queued updates and makes a best effort to stop updates in
+  // progress as soon as possible. Some updates may not be stopped, in which
+  // case, the updates will run to completion. Calling this function has no
+  // effect if updates are not currently executed or queued up.
+  virtual void Stop() = 0;
+
+ protected:
+  friend class base::RefCounted<UpdateClient>;
+
+  virtual ~UpdateClient() {}
+};
+
+// Creates an instance of the update client.
+scoped_refptr<UpdateClient> UpdateClientFactory(
+    scoped_refptr<Configurator> config);
+
+// This must be called prior to the construction of any Configurator that
+// contains a PrefService.
+void RegisterPrefs(PrefRegistrySimple* registry);
+
+// This must be called prior to the construction of any Configurator that
+// needs access to local user profiles.
+// This function is mostly used for ExtensionUpdater, which requires update
+// info from user profiles.
+void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_H_
diff --git a/src/components/update_client/update_client_errors.h b/src/components/update_client/update_client_errors.h
new file mode 100644
index 0000000..3ee0509
--- /dev/null
+++ b/src/components/update_client/update_client_errors.h
@@ -0,0 +1,118 @@
+// Copyright 2016 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 COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_ERRORS_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_ERRORS_H_
+
+namespace update_client {
+
+// Errors generated as a result of calling UpdateClient member functions.
+// These errors are not sent in pings.
+enum class Error {
+  NONE = 0,
+  UPDATE_IN_PROGRESS = 1,
+  UPDATE_CANCELED = 2,
+  RETRY_LATER = 3,
+  SERVICE_ERROR = 4,
+  UPDATE_CHECK_ERROR = 5,
+  CRX_NOT_FOUND = 6,
+  INVALID_ARGUMENT = 7,
+  MAX_VALUE,
+};
+
+// These errors are sent in pings. Add new values only to the bottom of
+// the enums below; the order must be kept stable.
+enum class ErrorCategory {
+  kNone = 0,
+  kDownload,
+  kUnpack,
+  kInstall,
+  kService,  // Runtime errors which occur in the service itself.
+  kUpdateCheck,
+};
+
+// These errors are returned with the |kNetworkError| error category. This
+// category could include other errors such as the errors defined by
+// the Chrome net stack.
+enum class CrxDownloaderError {
+  NONE = 0,
+  NO_URL = 10,
+  NO_HASH = 11,
+  BAD_HASH = 12,  // The downloaded file fails the hash verification.
+  // The Windows BITS queue contains to many update client jobs. The value is
+  // chosen so that it can be reported as a custom COM error on this platform.
+  BITS_TOO_MANY_JOBS = 0x0200,
+  GENERIC_ERROR = -1
+};
+
+// These errors are returned with the |kUnpack| error category and
+// indicate unpacker or patcher error.
+enum class UnpackerError {
+  kNone = 0,
+  kInvalidParams = 1,
+  kInvalidFile = 2,
+  kUnzipPathError = 3,
+  kUnzipFailed = 4,
+  // kNoManifest = 5,         // Deprecated. Never used.
+  kBadManifest = 6,
+  kBadExtension = 7,
+  // kInvalidId = 8,          // Deprecated. Combined with kInvalidFile.
+  // kInstallerError = 9,     // Deprecated. Don't use.
+  kIoError = 10,
+  kDeltaVerificationFailure = 11,
+  kDeltaBadCommands = 12,
+  kDeltaUnsupportedCommand = 13,
+  kDeltaOperationFailure = 14,
+  kDeltaPatchProcessFailure = 15,
+  kDeltaMissingExistingFile = 16,
+  // kFingerprintWriteFailed = 17,    // Deprecated. Don't use.
+};
+
+// These errors are returned with the |kService| error category and
+// are returned by the component installers.
+enum class InstallError {
+  NONE = 0,
+  FINGERPRINT_WRITE_FAILED = 2,
+  BAD_MANIFEST = 3,
+  GENERIC_ERROR = 9,  // Matches kInstallerError for compatibility.
+  MOVE_FILES_ERROR = 10,
+  SET_PERMISSIONS_FAILED = 11,
+  INVALID_VERSION = 12,
+  VERSION_NOT_UPGRADED = 13,
+  NO_DIR_COMPONENT_USER = 14,
+  CLEAN_INSTALL_DIR_FAILED = 15,
+  INSTALL_VERIFICATION_FAILED = 16,
+  CUSTOM_ERROR_BASE = 100,  // Specific installer errors go above this value.
+};
+
+// These errors are returned with the |kService| error category and
+// indicate critical or configuration errors in the update service.
+enum class ServiceError {
+  NONE = 0,
+  SERVICE_WAIT_FAILED = 1,
+  UPDATE_DISABLED = 2,
+};
+
+// These errors are related to serialization, deserialization, and parsing of
+// protocol requests.
+// The begin value for this enum is chosen not to conflict with network errors
+// defined by net/base/net_error_list.h. The callers don't have to handle this
+// error in any meaningful way, but this value may be reported in UMA stats,
+// therefore avoiding collisions with known network errors is desirable.
+enum class ProtocolError : int {
+  NONE = 0,
+  RESPONSE_NOT_TRUSTED = -10000,
+  MISSING_PUBLIC_KEY = -10001,
+  MISSING_URLS = -10002,
+  PARSE_FAILED = -10003,
+  UPDATE_RESPONSE_NOT_FOUND = -10004,
+  URL_FETCHER_FAILED = -10005,
+  UNKNOWN_APPLICATION = -10006,
+  RESTRICTED_APPLICATION = -10007,
+  INVALID_APPID = -10008,
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_ERRORS_H_
diff --git a/src/components/update_client/update_client_internal.h b/src/components/update_client/update_client_internal.h
new file mode 100644
index 0000000..2b6158c
--- /dev/null
+++ b/src/components/update_client/update_client_internal.h
@@ -0,0 +1,92 @@
+// Copyright 2015 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 COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_INTERNAL_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_INTERNAL_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/containers/circular_deque.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "components/update_client/crx_downloader.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client.h"
+
+namespace update_client {
+
+class Configurator;
+class PingManager;
+class Task;
+class UpdateEngine;
+enum class Error;
+
+class UpdateClientImpl : public UpdateClient {
+ public:
+  UpdateClientImpl(scoped_refptr<Configurator> config,
+                   scoped_refptr<PingManager> ping_manager,
+                   UpdateChecker::Factory update_checker_factory,
+                   CrxDownloader::Factory crx_downloader_factory);
+
+  // Overrides for UpdateClient.
+  void AddObserver(Observer* observer) override;
+  void RemoveObserver(Observer* observer) override;
+  void Install(const std::string& id,
+               CrxDataCallback crx_data_callback,
+               Callback callback) override;
+  void Update(const std::vector<std::string>& ids,
+              CrxDataCallback crx_data_callback,
+              bool is_foreground,
+              Callback callback) override;
+  bool GetCrxUpdateState(const std::string& id,
+                         CrxUpdateItem* update_item) const override;
+  bool IsUpdating(const std::string& id) const override;
+  void Stop() override;
+  void SendUninstallPing(const std::string& id,
+                         const base::Version& version,
+                         int reason,
+                         Callback callback) override;
+
+ private:
+  ~UpdateClientImpl() override;
+
+  void RunTask(scoped_refptr<Task> task);
+  void OnTaskComplete(Callback callback, scoped_refptr<Task> task, Error error);
+
+  void NotifyObservers(Observer::Events event, const std::string& id);
+
+  base::ThreadChecker thread_checker_;
+
+  // True if Stop method has been called.
+  bool is_stopped_ = false;
+
+  scoped_refptr<Configurator> config_;
+
+  // Contains the tasks that are pending. In the current implementation,
+  // only update tasks (background tasks) are queued up. These tasks are
+  // pending while they are in this queue. They have not been picked up yet
+  // by the update engine.
+  base::circular_deque<scoped_refptr<Task>> task_queue_;
+
+  // Contains all tasks in progress. These are the tasks that the update engine
+  // is executing at one moment. Install tasks are run concurrently, update
+  // tasks are always serialized, and update tasks are queued up if install
+  // tasks are running. In addition, concurrent install tasks for the same id
+  // are not allowed.
+  std::set<scoped_refptr<Task>> tasks_;
+  scoped_refptr<PingManager> ping_manager_;
+  scoped_refptr<UpdateEngine> update_engine_;
+  base::ObserverList<Observer>::Unchecked observer_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateClientImpl);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_INTERNAL_H_
diff --git a/src/components/update_client/update_client_unittest.cc b/src/components/update_client/update_client_unittest.cc
new file mode 100644
index 0000000..ddee228
--- /dev/null
+++ b/src/components/update_client/update_client_unittest.cc
@@ -0,0 +1,3978 @@
+// Copyright 2015 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 <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/containers/flat_map.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/test/scoped_path_override.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "build/build_config.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/update_client/component_unpacker.h"
+#include "components/update_client/crx_update_item.h"
+#include "components/update_client/network.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/ping_manager.h"
+#include "components/update_client/protocol_handler.h"
+#include "components/update_client/test_configurator.h"
+#include "components/update_client/test_installer.h"
+#include "components/update_client/unzipper.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/update_client_internal.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+namespace {
+
+using base::FilePath;
+
+// Makes a copy of the file specified by |from_path| in a temporary directory
+// and returns the path of the copy. Returns true if successful. Cleans up if
+// there was an error creating the copy.
+bool MakeTestFile(const FilePath& from_path, FilePath* to_path) {
+  FilePath temp_dir;
+  bool result =
+      CreateNewTempDirectory(FILE_PATH_LITERAL("update_client"), &temp_dir);
+  if (!result)
+    return false;
+
+  FilePath temp_file;
+  result = CreateTemporaryFileInDir(temp_dir, &temp_file);
+  if (!result)
+    return false;
+
+  result = CopyFile(from_path, temp_file);
+  if (!result) {
+    DeleteFile(temp_file, false);
+    return false;
+  }
+
+  *to_path = temp_file;
+  return true;
+}
+
+using Events = UpdateClient::Observer::Events;
+
+class MockObserver : public UpdateClient::Observer {
+ public:
+  MOCK_METHOD2(OnEvent, void(Events event, const std::string&));
+};
+
+}  // namespace
+
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::AnyNumber;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Mock;
+using ::testing::Return;
+
+using std::string;
+
+class MockPingManagerImpl : public PingManager {
+ public:
+  struct PingData {
+    std::string id;
+    base::Version previous_version;
+    base::Version next_version;
+    ErrorCategory error_category = ErrorCategory::kNone;
+    int error_code = 0;
+    int extra_code1 = 0;
+    ErrorCategory diff_error_category = ErrorCategory::kNone;
+    int diff_error_code = 0;
+    bool diff_update_failed = false;
+  };
+
+  explicit MockPingManagerImpl(scoped_refptr<Configurator> config);
+
+  void SendPing(const Component& component, Callback callback) override;
+
+  const std::vector<PingData>& ping_data() const;
+
+  const std::vector<base::Value>& events() const;
+
+ protected:
+  ~MockPingManagerImpl() override;
+
+ private:
+  std::vector<PingData> ping_data_;
+  std::vector<base::Value> events_;
+  DISALLOW_COPY_AND_ASSIGN(MockPingManagerImpl);
+};
+
+MockPingManagerImpl::MockPingManagerImpl(scoped_refptr<Configurator> config)
+    : PingManager(config) {}
+
+MockPingManagerImpl::~MockPingManagerImpl() {}
+
+void MockPingManagerImpl::SendPing(const Component& component,
+                                   Callback callback) {
+  PingData ping_data;
+  ping_data.id = component.id_;
+  ping_data.previous_version = component.previous_version_;
+  ping_data.next_version = component.next_version_;
+  ping_data.error_category = component.error_category_;
+  ping_data.error_code = component.error_code_;
+  ping_data.extra_code1 = component.extra_code1_;
+  ping_data.diff_error_category = component.diff_error_category_;
+  ping_data.diff_error_code = component.diff_error_code_;
+  ping_data.diff_update_failed = component.diff_update_failed();
+  ping_data_.push_back(ping_data);
+
+  events_ = component.GetEvents();
+
+  std::move(callback).Run(0, "");
+}
+
+const std::vector<MockPingManagerImpl::PingData>&
+MockPingManagerImpl::ping_data() const {
+  return ping_data_;
+}
+
+const std::vector<base::Value>& MockPingManagerImpl::events() const {
+  return events_;
+}
+
+class UpdateClientTest : public testing::Test {
+ public:
+  UpdateClientTest();
+  ~UpdateClientTest() override;
+
+ protected:
+  void RunThreads();
+
+  // Returns the full path to a test file.
+  static base::FilePath TestFilePath(const char* file);
+
+  scoped_refptr<update_client::TestConfigurator> config() { return config_; }
+  update_client::PersistedData* metadata() { return metadata_.get(); }
+
+  base::OnceClosure quit_closure() { return runloop_.QuitClosure(); }
+
+ private:
+  static constexpr int kNumWorkerThreads_ = 2;
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  base::RunLoop runloop_;
+
+  scoped_refptr<update_client::TestConfigurator> config_ =
+      base::MakeRefCounted<TestConfigurator>();
+  std::unique_ptr<TestingPrefServiceSimple> pref_ =
+      std::make_unique<TestingPrefServiceSimple>();
+  std::unique_ptr<update_client::PersistedData> metadata_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateClientTest);
+};
+
+constexpr int UpdateClientTest::kNumWorkerThreads_;
+
+UpdateClientTest::UpdateClientTest() {
+  PersistedData::RegisterPrefs(pref_->registry());
+  metadata_ = std::make_unique<PersistedData>(pref_.get(), nullptr);
+}
+
+UpdateClientTest::~UpdateClientTest() {}
+
+void UpdateClientTest::RunThreads() {
+  runloop_.Run();
+  scoped_task_environment_.RunUntilIdle();
+}
+
+base::FilePath UpdateClientTest::TestFilePath(const char* file) {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  return path.AppendASCII("components")
+      .AppendASCII("test")
+      .AppendASCII("data")
+      .AppendASCII("update_client")
+      .AppendASCII(file);
+}
+
+// Tests the scenario where one update check is done for one CRX. The CRX
+// has no update.
+TEST_F(UpdateClientTest, OneCrxNoUpdate) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      CrxComponent crx;
+      crx.name = "test_jebg";
+      crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx.version = base::Version("0.9");
+      crx.installer = base::MakeRefCounted<TestInstaller>();
+      crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+      std::vector<base::Optional<CrxComponent>> component = {crx};
+      return component;
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_TRUE(enabled_component_updates);
+      EXPECT_EQ(1u, ids_to_check.size());
+      const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+      EXPECT_EQ(id, ids_to_check.front());
+      EXPECT_EQ(1u, components.count(id));
+
+      auto& component = components.at(id);
+
+      EXPECT_TRUE(component->is_foreground());
+
+      ProtocolParser::Result result;
+      result.extension_id = id;
+      result.status = "noupdate";
+
+      ProtocolParser::Results results;
+      results.list.push_back(result);
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  InSequence seq;
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), true,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the scenario where two CRXs are checked for updates. On CRX has
+// an update, the other CRX does not.
+TEST_F(UpdateClientTest, TwoCrxUpdateNoUpdate) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      CrxComponent crx1;
+      crx1.name = "test_jebg";
+      crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx1.version = base::Version("0.9");
+      crx1.installer = base::MakeRefCounted<TestInstaller>();
+      crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+      CrxComponent crx2;
+      crx2.name = "test_abag";
+      crx2.pk_hash.assign(abag_hash, abag_hash + base::size(abag_hash));
+      crx2.version = base::Version("2.2");
+      crx2.installer = base::MakeRefCounted<TestInstaller>();
+      crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+      return {crx1, crx2};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      /*
+      Mock the following response:
+
+      <?xml version='1.0' encoding='UTF-8'?>
+      <response protocol='3.1'>
+        <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+          <updatecheck status='ok'>
+            <urls>
+              <url codebase='http://localhost/download/'/>
+            </urls>
+            <manifest version='1.0' prodversionmin='11.0.1.0'>
+              <packages>
+                <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+                         hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+                                      7c9b12cb7cc067667bde87'/>
+              </packages>
+            </manifest>
+          </updatecheck>
+        </app>
+        <app appid='abagagagagagagagagagagagagagagag'>
+          <updatecheck status='noupdate'/>
+        </app>
+      </response>
+      */
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_TRUE(enabled_component_updates);
+      EXPECT_EQ(2u, ids_to_check.size());
+
+      ProtocolParser::Results results;
+      {
+        const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+        EXPECT_EQ(id, ids_to_check[0]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result::Manifest::Package package;
+        package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+        package.hash_sha256 =
+            "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+        ProtocolParser::Result result;
+        result.extension_id = "jebgalgnebhfojomionfpkfelancnnkf";
+        result.status = "ok";
+        result.crx_urls.push_back(GURL("http://localhost/download/"));
+        result.manifest.version = "1.0";
+        result.manifest.browser_min_version = "11.0.1.0";
+        result.manifest.packages.push_back(package);
+        results.list.push_back(result);
+
+        EXPECT_FALSE(components.at(id)->is_foreground());
+      }
+
+      {
+        const std::string id = "abagagagagagagagagagagagagagagag";
+        EXPECT_EQ(id, ids_to_check[1]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result result;
+        result.extension_id = id;
+        result.status = "noupdate";
+        results.list.push_back(result);
+
+        EXPECT_FALSE(components.at(id)->is_foreground());
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override {
+      DownloadMetrics download_metrics;
+      download_metrics.url = url;
+      download_metrics.downloader = DownloadMetrics::kNone;
+      download_metrics.error = 0;
+      download_metrics.downloaded_bytes = 1843;
+      download_metrics.total_bytes = 1843;
+      download_metrics.download_time_ms = 1000;
+
+      FilePath path;
+      EXPECT_TRUE(MakeTestFile(
+          TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
+
+      Result result;
+      result.error = 0;
+      result.response = path;
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+                                    base::Unretained(this)));
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+                                    base::Unretained(this), true, result,
+                                    download_metrics));
+    }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
+      EXPECT_EQ(1u, ping_data.size());
+      EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+      EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+      EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+      EXPECT_EQ(0, ping_data[0].error_code);
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(AtLeast(1));
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "abagagagagagagagagagagagagagagag"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+                                  "abagagagagagagagagagagagagagagag"))
+        .Times(1);
+  }
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+                                        "abagagagagagagagagagagagagagagag"};
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the scenario where two CRXs are checked for updates. One CRX has
+// an update but the server ignores the second CRX and returns no response for
+// it. The second component gets an |UPDATE_RESPONSE_NOT_FOUND| error and
+// transitions to the error state.
+TEST_F(UpdateClientTest, TwoCrxUpdateFirstServerIgnoresSecond) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      CrxComponent crx1;
+      crx1.name = "test_jebg";
+      crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx1.version = base::Version("0.9");
+      crx1.installer = base::MakeRefCounted<TestInstaller>();
+      crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+      CrxComponent crx2;
+      crx2.name = "test_abag";
+      crx2.pk_hash.assign(abag_hash, abag_hash + base::size(abag_hash));
+      crx2.version = base::Version("2.2");
+      crx2.installer = base::MakeRefCounted<TestInstaller>();
+      crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+      return {crx1, crx2};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      /*
+      Mock the following response:
+
+      <?xml version='1.0' encoding='UTF-8'?>
+      <response protocol='3.1'>
+        <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+          <updatecheck status='ok'>
+            <urls>
+              <url codebase='http://localhost/download/'/>
+            </urls>
+            <manifest version='1.0' prodversionmin='11.0.1.0'>
+              <packages>
+                <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+                         hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+                                      7c9b12cb7cc067667bde87'/>
+              </packages>
+            </manifest>
+          </updatecheck>
+        </app>
+      </response>
+      */
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_TRUE(enabled_component_updates);
+      EXPECT_EQ(2u, ids_to_check.size());
+
+      ProtocolParser::Results results;
+      {
+        const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+        EXPECT_EQ(id, ids_to_check[0]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result::Manifest::Package package;
+        package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+        package.hash_sha256 =
+            "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+        ProtocolParser::Result result;
+        result.extension_id = "jebgalgnebhfojomionfpkfelancnnkf";
+        result.status = "ok";
+        result.crx_urls.push_back(GURL("http://localhost/download/"));
+        result.manifest.version = "1.0";
+        result.manifest.browser_min_version = "11.0.1.0";
+        result.manifest.packages.push_back(package);
+        results.list.push_back(result);
+
+        EXPECT_FALSE(components.at(id)->is_foreground());
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override {
+      DownloadMetrics download_metrics;
+      download_metrics.url = url;
+      download_metrics.downloader = DownloadMetrics::kNone;
+      download_metrics.error = 0;
+      download_metrics.downloaded_bytes = 1843;
+      download_metrics.total_bytes = 1843;
+      download_metrics.download_time_ms = 1000;
+
+      FilePath path;
+      EXPECT_TRUE(MakeTestFile(
+          TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
+
+      Result result;
+      result.error = 0;
+      result.response = path;
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+                                    base::Unretained(this)));
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+                                    base::Unretained(this), true, result,
+                                    download_metrics));
+    }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
+      EXPECT_EQ(1u, ping_data.size());
+      EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+      EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+      EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+      EXPECT_EQ(0, ping_data[0].error_code);
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(AtLeast(1));
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "abagagagagagagagagagagagagagagag"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                  "abagagagagagagagagagagagagagagag"))
+        .Times(1)
+        .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+          CrxUpdateItem item;
+          EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+          EXPECT_EQ(ComponentState::kUpdateError, item.state);
+          EXPECT_EQ(5, static_cast<int>(item.error_category));
+          EXPECT_EQ(-10004, item.error_code);
+          EXPECT_EQ(0, item.extra_code1);
+        }));
+  }
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+                                        "abagagagagagagagagagagagagagagag"};
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the update check for two CRXs scenario when the second CRX does not
+// provide a CrxComponent instance. In this case, the update is handled as
+// if only one component were provided as an argument to the |Update| call
+// with the exception that the second component still fires an event such as
+// |COMPONENT_UPDATE_ERROR|.
+TEST_F(UpdateClientTest, TwoCrxUpdateNoCrxComponentData) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      CrxComponent crx;
+      crx.name = "test_jebg";
+      crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx.version = base::Version("0.9");
+      crx.installer = base::MakeRefCounted<TestInstaller>();
+      crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+      return {crx, base::nullopt};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      /*
+      Mock the following response:
+
+      <?xml version='1.0' encoding='UTF-8'?>
+      <response protocol='3.1'>
+        <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+          <updatecheck status='ok'>
+            <urls>
+              <url codebase='http://localhost/download/'/>
+            </urls>
+            <manifest version='1.0' prodversionmin='11.0.1.0'>
+              <packages>
+                <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+                         hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+                                      7c9b12cb7cc067667bde87'/>
+              </packages>
+            </manifest>
+          </updatecheck>
+        </app>
+      </response>
+      */
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_TRUE(enabled_component_updates);
+      EXPECT_EQ(1u, ids_to_check.size());
+
+      ProtocolParser::Results results;
+      {
+        const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+        EXPECT_EQ(id, ids_to_check[0]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result::Manifest::Package package;
+        package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+        package.hash_sha256 =
+            "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+        ProtocolParser::Result result;
+        result.extension_id = id;
+        result.status = "ok";
+        result.crx_urls.push_back(GURL("http://localhost/download/"));
+        result.manifest.version = "1.0";
+        result.manifest.browser_min_version = "11.0.1.0";
+        result.manifest.packages.push_back(package);
+        results.list.push_back(result);
+
+        EXPECT_FALSE(components.at(id)->is_foreground());
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override {
+      DownloadMetrics download_metrics;
+      FilePath path;
+      Result result;
+      if (url.path() == "/download/jebgalgnebhfojomionfpkfelancnnkf.crx") {
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = 0;
+        download_metrics.downloaded_bytes = 1843;
+        download_metrics.total_bytes = 1843;
+        download_metrics.download_time_ms = 1000;
+
+        EXPECT_TRUE(MakeTestFile(
+            TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
+
+        result.error = 0;
+        result.response = path;
+      } else {
+        NOTREACHED();
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+                                    base::Unretained(this)));
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+                                    base::Unretained(this), true, result,
+                                    download_metrics));
+    }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
+      EXPECT_EQ(1u, ping_data.size());
+      EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+      EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+      EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+      EXPECT_EQ(0, ping_data[0].error_code);
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(AtLeast(1));
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+  }
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+                                        "ihfokbkgjpifnbbojhneepfflplebdkc"};
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the update check for two CRXs scenario when no CrxComponent data is
+// provided for either component. In this case, no update check occurs, and
+// |COMPONENT_UPDATE_ERROR| event fires for both components.
+TEST_F(UpdateClientTest, TwoCrxUpdateNoCrxComponentDataAtAll) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      return {base::nullopt, base::nullopt};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      NOTREACHED();
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override { NOTREACHED(); }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      EXPECT_EQ(0u, MockPingManagerImpl::ping_data().size());
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+  }
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+                                        "ihfokbkgjpifnbbojhneepfflplebdkc"};
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the scenario where there is a download timeout for the first
+// CRX. The update for the first CRX fails. The update client waits before
+// attempting the update for the second CRX. This update succeeds.
+TEST_F(UpdateClientTest, TwoCrxUpdateDownloadTimeout) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      CrxComponent crx1;
+      crx1.name = "test_jebg";
+      crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx1.version = base::Version("0.9");
+      crx1.installer = base::MakeRefCounted<TestInstaller>();
+      crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+      CrxComponent crx2;
+      crx2.name = "test_ihfo";
+      crx2.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
+      crx2.version = base::Version("0.8");
+      crx2.installer = base::MakeRefCounted<TestInstaller>();
+      crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+      return {crx1, crx2};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      /*
+      Mock the following response:
+
+      <?xml version='1.0' encoding='UTF-8'?>
+      <response protocol='3.1'>
+        <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+          <updatecheck status='ok'>
+            <urls>
+              <url codebase='http://localhost/download/'/>
+            </urls>
+            <manifest version='1.0' prodversionmin='11.0.1.0'>
+              <packages>
+                <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+                         hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+                                      7c9b12cb7cc067667bde87'/>
+              </packages>
+            </manifest>
+          </updatecheck>
+        </app>
+        <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+          <updatecheck status='ok'>
+            <urls>
+              <url codebase='http://localhost/download/'/>
+            </urls>
+            <manifest version='1.0' prodversionmin='11.0.1.0'>
+              <packages>
+                <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
+                         hash_sha256='813c59747e139a608b3b5fc49633affc6db574373f
+                                      309f156ea6d27229c0b3f9'/>
+              </packages>
+            </manifest>
+          </updatecheck>
+        </app>
+      </response>
+      */
+
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_TRUE(enabled_component_updates);
+      EXPECT_EQ(2u, ids_to_check.size());
+
+      ProtocolParser::Results results;
+      {
+        const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+        EXPECT_EQ(id, ids_to_check[0]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result::Manifest::Package package;
+        package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+        package.hash_sha256 =
+            "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+        ProtocolParser::Result result;
+        result.extension_id = id;
+        result.status = "ok";
+        result.crx_urls.push_back(GURL("http://localhost/download/"));
+        result.manifest.version = "1.0";
+        result.manifest.browser_min_version = "11.0.1.0";
+        result.manifest.packages.push_back(package);
+        results.list.push_back(result);
+      }
+
+      {
+        const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+        EXPECT_EQ(id, ids_to_check[1]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result::Manifest::Package package;
+        package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
+        package.hash_sha256 =
+            "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
+
+        ProtocolParser::Result result;
+        result.extension_id = id;
+        result.status = "ok";
+        result.crx_urls.push_back(GURL("http://localhost/download/"));
+        result.manifest.version = "1.0";
+        result.manifest.browser_min_version = "11.0.1.0";
+        result.manifest.packages.push_back(package);
+        results.list.push_back(result);
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override {
+      DownloadMetrics download_metrics;
+      FilePath path;
+      Result result;
+      if (url.path() == "/download/jebgalgnebhfojomionfpkfelancnnkf.crx") {
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = -118;
+        download_metrics.downloaded_bytes = 0;
+        download_metrics.total_bytes = 0;
+        download_metrics.download_time_ms = 1000;
+
+        // The result must not include a file path in the case of errors.
+        result.error = -118;
+      } else if (url.path() ==
+                 "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = 0;
+        download_metrics.downloaded_bytes = 53638;
+        download_metrics.total_bytes = 53638;
+        download_metrics.download_time_ms = 2000;
+
+        EXPECT_TRUE(MakeTestFile(
+            TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
+
+        result.error = 0;
+        result.response = path;
+      } else {
+        NOTREACHED();
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+                                    base::Unretained(this)));
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+                                    base::Unretained(this), true, result,
+                                    download_metrics));
+    }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
+      EXPECT_EQ(2u, ping_data.size());
+      EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+      EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+      EXPECT_EQ(1, static_cast<int>(ping_data[0].error_category));
+      EXPECT_EQ(-118, ping_data[0].error_code);
+      EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
+      EXPECT_EQ(base::Version("0.8"), ping_data[1].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[1].next_version);
+      EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
+      EXPECT_EQ(0, ping_data[1].error_code);
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(AtLeast(1));
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1)
+        .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+          CrxUpdateItem item;
+          EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+          EXPECT_EQ(ComponentState::kUpdateError, item.state);
+          EXPECT_EQ(1, static_cast<int>(item.error_category));
+          EXPECT_EQ(-118, item.error_code);
+          EXPECT_EQ(0, item.extra_code1);
+        }));
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_WAIT,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(AtLeast(1));
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+  }
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+                                        "ihfokbkgjpifnbbojhneepfflplebdkc"};
+
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the differential update scenario for one CRX.
+TEST_F(UpdateClientTest, OneCrxDiffUpdate) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      static int num_calls = 0;
+
+      // Must use the same stateful installer object.
+      static scoped_refptr<CrxInstaller> installer =
+          base::MakeRefCounted<VersionedTestInstaller>();
+
+      ++num_calls;
+
+      CrxComponent crx;
+      crx.name = "test_ihfo";
+      crx.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
+      crx.installer = installer;
+      crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+      if (num_calls == 1) {
+        crx.version = base::Version("0.8");
+      } else if (num_calls == 2) {
+        crx.version = base::Version("1.0");
+      } else {
+        NOTREACHED();
+      }
+
+      return {crx};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      EXPECT_FALSE(session_id.empty());
+
+      static int num_call = 0;
+      ++num_call;
+
+      ProtocolParser::Results results;
+
+      if (num_call == 1) {
+        /*
+        Mock the following response:
+        <?xml version='1.0' encoding='UTF-8'?>
+        <response protocol='3.1'>
+          <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+            <updatecheck status='ok'>
+              <urls>
+                <url codebase='http://localhost/download/'/>
+              </urls>
+              <manifest version='1.0' prodversionmin='11.0.1.0'>
+                <packages>
+                  <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
+                           hash_sha256='813c59747e139a608b3b5fc49633affc6db57437
+                                        3f309f156ea6d27229c0b3f9'/>
+                </packages>
+              </manifest>
+            </updatecheck>
+          </app>
+        </response>
+        */
+        const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+        EXPECT_EQ(id, ids_to_check[0]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result::Manifest::Package package;
+        package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
+        package.hash_sha256 =
+            "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
+
+        ProtocolParser::Result result;
+        result.extension_id = id;
+        result.status = "ok";
+        result.crx_urls.push_back(GURL("http://localhost/download/"));
+        result.manifest.version = "1.0";
+        result.manifest.browser_min_version = "11.0.1.0";
+        result.manifest.packages.push_back(package);
+        results.list.push_back(result);
+      } else if (num_call == 2) {
+        /*
+        Mock the following response:
+        <?xml version='1.0' encoding='UTF-8'?>
+        <response protocol='3.1'>
+          <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+            <updatecheck status='ok'>
+              <urls>
+                <url codebase='http://localhost/download/'/>
+                <url codebasediff='http://localhost/download/'/>
+              </urls>
+              <manifest version='2.0' prodversionmin='11.0.1.0'>
+                <packages>
+                  <package name='ihfokbkgjpifnbbojhneepfflplebdkc_2.crx'
+                           namediff='ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx'
+                           hash_sha256='1af337fbd19c72db0f870753bcd7711c3ae9dcaa
+                                        0ecde26c262bad942b112990'
+                           fp='22'
+                           hashdiff_sha256='73c6e2d4f783fc4ca5481e89e0b8bfce7aec
+                                            8ead3686290c94792658ec06f2f2'/>
+                </packages>
+              </manifest>
+            </updatecheck>
+          </app>
+        </response>
+        */
+        const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+        EXPECT_EQ(id, ids_to_check[0]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result::Manifest::Package package;
+        package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_2.crx";
+        package.namediff = "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx";
+        package.hash_sha256 =
+            "1af337fbd19c72db0f870753bcd7711c3ae9dcaa0ecde26c262bad942b112990";
+        package.hashdiff_sha256 =
+            "73c6e2d4f783fc4ca5481e89e0b8bfce7aec8ead3686290c94792658ec06f2f2";
+        package.fingerprint = "22";
+
+        ProtocolParser::Result result;
+        result.extension_id = id;
+        result.status = "ok";
+        result.crx_urls.push_back(GURL("http://localhost/download/"));
+        result.crx_diffurls.push_back(GURL("http://localhost/download/"));
+        result.manifest.version = "2.0";
+        result.manifest.browser_min_version = "11.0.1.0";
+        result.manifest.packages.push_back(package);
+        results.list.push_back(result);
+      } else {
+        NOTREACHED();
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override {
+      DownloadMetrics download_metrics;
+      FilePath path;
+      Result result;
+      if (url.path() == "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = 0;
+        download_metrics.downloaded_bytes = 53638;
+        download_metrics.total_bytes = 53638;
+        download_metrics.download_time_ms = 2000;
+
+        EXPECT_TRUE(MakeTestFile(
+            TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
+
+        result.error = 0;
+        result.response = path;
+      } else if (url.path() ==
+                 "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx") {
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = 0;
+        download_metrics.downloaded_bytes = 2105;
+        download_metrics.total_bytes = 2105;
+        download_metrics.download_time_ms = 1000;
+
+        EXPECT_TRUE(MakeTestFile(
+            TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"), &path));
+
+        result.error = 0;
+        result.response = path;
+      } else {
+        NOTREACHED();
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+                                    base::Unretained(this)));
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+                                    base::Unretained(this), true, result,
+                                    download_metrics));
+    }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
+      EXPECT_EQ(2u, ping_data.size());
+      EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[0].id);
+      EXPECT_EQ(base::Version("0.8"), ping_data[0].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+      EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+      EXPECT_EQ(0, ping_data[0].error_code);
+      EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
+      EXPECT_EQ(base::Version("1.0"), ping_data[1].previous_version);
+      EXPECT_EQ(base::Version("2.0"), ping_data[1].next_version);
+      EXPECT_FALSE(ping_data[1].diff_update_failed);
+      EXPECT_EQ(0, static_cast<int>(ping_data[1].diff_error_category));
+      EXPECT_EQ(0, ping_data[1].diff_error_code);
+      EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
+      EXPECT_EQ(0, ping_data[1].error_code);
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(AtLeast(1));
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(AtLeast(1));
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+  }
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"ihfokbkgjpifnbbojhneepfflplebdkc"};
+  {
+    base::RunLoop runloop;
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+                          false,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
+                                         runloop.QuitClosure()));
+    runloop.Run();
+  }
+
+  {
+    base::RunLoop runloop;
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+                          false,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
+                                         runloop.QuitClosure()));
+    runloop.Run();
+  }
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the update scenario for one CRX where the CRX installer returns
+// an error. Tests that the |unpack_path| argument refers to a valid path
+// then |Install| is called, then tests that the |unpack| path is deleted
+// by the |update_client| code before the test ends.
+TEST_F(UpdateClientTest, OneCrxInstallError) {
+  class MockInstaller : public CrxInstaller {
+   public:
+    MOCK_METHOD1(OnUpdateError, void(int error));
+    MOCK_METHOD2(DoInstall,
+                 void(const base::FilePath& unpack_path,
+                      const Callback& callback));
+    MOCK_METHOD2(GetInstalledFile,
+                 bool(const std::string& file, base::FilePath* installed_file));
+    MOCK_METHOD0(Uninstall, bool());
+
+    void Install(const base::FilePath& unpack_path,
+                 const std::string& public_key,
+                 Callback callback) override {
+      DoInstall(unpack_path, std::move(callback));
+
+      unpack_path_ = unpack_path;
+      EXPECT_TRUE(base::DirectoryExists(unpack_path_));
+      base::PostTaskWithTraits(
+          FROM_HERE, {base::MayBlock()},
+          base::BindOnce(std::move(callback),
+                         CrxInstaller::Result(InstallError::GENERIC_ERROR)));
+    }
+
+   protected:
+    ~MockInstaller() override {
+      // The unpack path is deleted unconditionally by the component state code,
+      // which is driving this installer. Therefore, the unpack path must not
+      // exist when this object is destroyed.
+      if (!unpack_path_.empty())
+        EXPECT_FALSE(base::DirectoryExists(unpack_path_));
+    }
+
+   private:
+    // Contains the |unpack_path| argument of the Install call.
+    base::FilePath unpack_path_;
+  };
+
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      scoped_refptr<MockInstaller> installer =
+          base::MakeRefCounted<MockInstaller>();
+
+      EXPECT_CALL(*installer, OnUpdateError(_)).Times(0);
+      EXPECT_CALL(*installer, DoInstall(_, _)).Times(1);
+      EXPECT_CALL(*installer, GetInstalledFile(_, _)).Times(0);
+      EXPECT_CALL(*installer, Uninstall()).Times(0);
+
+      CrxComponent crx;
+      crx.name = "test_jebg";
+      crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx.version = base::Version("0.9");
+      crx.installer = installer;
+      crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+      return {crx};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      /*
+      Mock the following response:
+
+      <?xml version='1.0' encoding='UTF-8'?>
+      <response protocol='3.1'>
+        <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+          <updatecheck status='ok'>
+            <urls>
+              <url codebase='http://localhost/download/'/>
+            </urls>
+            <manifest version='1.0' prodversionmin='11.0.1.0'>
+              <packages>
+                <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+                         hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+                                      7c9b12cb7cc067667bde87'/>
+              </packages>
+            </manifest>
+          </updatecheck>
+        </app>
+      </response>
+      */
+      EXPECT_FALSE(session_id.empty());
+
+      const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+      EXPECT_EQ(id, ids_to_check[0]);
+      EXPECT_EQ(1u, components.count(id));
+
+      ProtocolParser::Result::Manifest::Package package;
+      package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+      package.hash_sha256 =
+          "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+      ProtocolParser::Result result;
+      result.extension_id = id;
+      result.status = "ok";
+      result.crx_urls.push_back(GURL("http://localhost/download/"));
+      result.manifest.version = "1.0";
+      result.manifest.browser_min_version = "11.0.1.0";
+      result.manifest.packages.push_back(package);
+
+      ProtocolParser::Results results;
+      results.list.push_back(result);
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override {
+      DownloadMetrics download_metrics;
+      download_metrics.url = url;
+      download_metrics.downloader = DownloadMetrics::kNone;
+      download_metrics.error = 0;
+      download_metrics.downloaded_bytes = 1843;
+      download_metrics.total_bytes = 1843;
+      download_metrics.download_time_ms = 1000;
+
+      FilePath path;
+      EXPECT_TRUE(MakeTestFile(
+          TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
+
+      Result result;
+      result.error = 0;
+      result.response = path;
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+                                    base::Unretained(this)));
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+                                    base::Unretained(this), true, result,
+                                    download_metrics));
+    }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
+      EXPECT_EQ(1u, ping_data.size());
+      EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+      EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+      EXPECT_EQ(3, static_cast<int>(ping_data[0].error_category));  // kInstall.
+      EXPECT_EQ(9, ping_data[0].error_code);  // kInstallerError.
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(AtLeast(1));
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+  }
+
+  update_client->AddObserver(&observer);
+
+  std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the fallback from differential to full update scenario for one CRX.
+TEST_F(UpdateClientTest, OneCrxDiffUpdateFailsFullUpdateSucceeds) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      static int num_calls = 0;
+
+      // Must use the same stateful installer object.
+      static scoped_refptr<CrxInstaller> installer =
+          base::MakeRefCounted<VersionedTestInstaller>();
+
+      ++num_calls;
+
+      CrxComponent crx;
+      crx.name = "test_ihfo";
+      crx.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
+      crx.installer = installer;
+      crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+      if (num_calls == 1) {
+        crx.version = base::Version("0.8");
+      } else if (num_calls == 2) {
+        crx.version = base::Version("1.0");
+      } else {
+        NOTREACHED();
+      }
+
+      return {crx};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      EXPECT_FALSE(session_id.empty());
+
+      static int num_call = 0;
+      ++num_call;
+
+      ProtocolParser::Results results;
+
+      if (num_call == 1) {
+        /*
+        Mock the following response:
+        <?xml version='1.0' encoding='UTF-8'?>
+        <response protocol='3.1'>
+          <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+            <updatecheck status='ok'>
+              <urls>
+                <url codebase='http://localhost/download/'/>
+              </urls>
+              <manifest version='1.0' prodversionmin='11.0.1.0'>
+                <packages>
+                  <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
+                           hash_sha256='813c59747e139a608b3b5fc49633affc6db57437
+                                        3f309f156ea6d27229c0b3f9'
+                           fp='1'/>
+                </packages>
+              </manifest>
+            </updatecheck>
+          </app>
+        </response>
+        */
+        const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+        EXPECT_EQ(id, ids_to_check[0]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result::Manifest::Package package;
+        package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
+        package.hash_sha256 =
+            "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
+        package.fingerprint = "1";
+
+        ProtocolParser::Result result;
+        result.extension_id = id;
+        result.status = "ok";
+        result.crx_urls.push_back(GURL("http://localhost/download/"));
+        result.manifest.version = "1.0";
+        result.manifest.browser_min_version = "11.0.1.0";
+        result.manifest.packages.push_back(package);
+        results.list.push_back(result);
+      } else if (num_call == 2) {
+        /*
+        Mock the following response:
+        <?xml version='1.0' encoding='UTF-8'?>
+        <response protocol='3.1'>
+          <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+            <updatecheck status='ok'>
+              <urls>
+                <url codebase='http://localhost/download/'/>
+                <url codebasediff='http://localhost/download/'/>
+              </urls>
+              <manifest version='2.0' prodversionmin='11.0.1.0'>
+                <packages>
+                  <package name='ihfokbkgjpifnbbojhneepfflplebdkc_2.crx'
+                           namediff='ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx'
+                           hash_sha256='1af337fbd19c72db0f870753bcd7711c3ae9dcaa
+                                        0ecde26c262bad942b112990'
+                           fp='22'
+                           hashdiff_sha256='73c6e2d4f783fc4ca5481e89e0b8bfce7aec
+                                            8ead3686290c94792658ec06f2f2'/>
+                </packages>
+              </manifest>
+            </updatecheck>
+          </app>
+        </response>
+        */
+        const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+        EXPECT_EQ(id, ids_to_check[0]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result::Manifest::Package package;
+        package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_2.crx";
+        package.namediff = "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx";
+        package.hash_sha256 =
+            "1af337fbd19c72db0f870753bcd7711c3ae9dcaa0ecde26c262bad942b112990";
+        package.hashdiff_sha256 =
+            "73c6e2d4f783fc4ca5481e89e0b8bfce7aec8ead3686290c94792658ec06f2f2";
+        package.fingerprint = "22";
+
+        ProtocolParser::Result result;
+        result.extension_id = id;
+        result.status = "ok";
+        result.crx_urls.push_back(GURL("http://localhost/download/"));
+        result.crx_diffurls.push_back(GURL("http://localhost/download/"));
+        result.manifest.version = "2.0";
+        result.manifest.browser_min_version = "11.0.1.0";
+        result.manifest.packages.push_back(package);
+        results.list.push_back(result);
+      } else {
+        NOTREACHED();
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override {
+      DownloadMetrics download_metrics;
+      FilePath path;
+      Result result;
+      if (url.path() == "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = 0;
+        download_metrics.downloaded_bytes = 53638;
+        download_metrics.total_bytes = 53638;
+        download_metrics.download_time_ms = 2000;
+
+        EXPECT_TRUE(MakeTestFile(
+            TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
+
+        result.error = 0;
+        result.response = path;
+      } else if (url.path() ==
+                 "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx") {
+        // A download error is injected on this execution path.
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = -1;
+        download_metrics.downloaded_bytes = 0;
+        download_metrics.total_bytes = 2105;
+        download_metrics.download_time_ms = 1000;
+
+        // The response must not include a file path in the case of errors.
+        result.error = -1;
+      } else if (url.path() ==
+                 "/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx") {
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = 0;
+        download_metrics.downloaded_bytes = 53855;
+        download_metrics.total_bytes = 53855;
+        download_metrics.download_time_ms = 1000;
+
+        EXPECT_TRUE(MakeTestFile(
+            TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"), &path));
+
+        result.error = 0;
+        result.response = path;
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+                                    base::Unretained(this)));
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+                                    base::Unretained(this), true, result,
+                                    download_metrics));
+    }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
+      EXPECT_EQ(2u, ping_data.size());
+      EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[0].id);
+      EXPECT_EQ(base::Version("0.8"), ping_data[0].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+      EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+      EXPECT_EQ(0, ping_data[0].error_code);
+      EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
+      EXPECT_EQ(base::Version("1.0"), ping_data[1].previous_version);
+      EXPECT_EQ(base::Version("2.0"), ping_data[1].next_version);
+      EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
+      EXPECT_EQ(0, ping_data[1].error_code);
+      EXPECT_TRUE(ping_data[1].diff_update_failed);
+      EXPECT_EQ(1, static_cast<int>(ping_data[1].diff_error_category));
+      EXPECT_EQ(-1, ping_data[1].diff_error_code);
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(AtLeast(1));
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(AtLeast(1));
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+  }
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"ihfokbkgjpifnbbojhneepfflplebdkc"};
+
+  {
+    base::RunLoop runloop;
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+                          false,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
+                                         runloop.QuitClosure()));
+    runloop.Run();
+  }
+
+  {
+    base::RunLoop runloop;
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+                          false,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
+                                         runloop.QuitClosure()));
+    runloop.Run();
+  }
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the queuing of update checks. In this scenario, two update checks are
+// done for one CRX. The second update check call is queued up and will run
+// after the first check has completed. The CRX has no updates.
+TEST_F(UpdateClientTest, OneCrxNoUpdateQueuedCall) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      CrxComponent crx;
+      crx.name = "test_jebg";
+      crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx.version = base::Version("0.9");
+      crx.installer = base::MakeRefCounted<TestInstaller>();
+      crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+      return {crx};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      static int num_call = 0;
+      ++num_call;
+
+      EXPECT_EQ(Error::NONE, error);
+
+      if (num_call == 2)
+        std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_TRUE(enabled_component_updates);
+      EXPECT_EQ(1u, ids_to_check.size());
+      const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+      EXPECT_EQ(id, ids_to_check.front());
+      EXPECT_EQ(1u, components.count(id));
+
+      auto& component = components.at(id);
+
+      EXPECT_FALSE(component->is_foreground());
+
+      ProtocolParser::Result result;
+      result.extension_id = id;
+      result.status = "noupdate";
+      ProtocolParser::Results results;
+      results.list.push_back(result);
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  InSequence seq;
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the install of one CRX.
+TEST_F(UpdateClientTest, OneCrxInstall) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      CrxComponent crx;
+      crx.name = "test_jebg";
+      crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx.version = base::Version("0.0");
+      crx.installer = base::MakeRefCounted<TestInstaller>();
+      crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+      return {crx};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      /*
+      Mock the following response:
+
+      <?xml version='1.0' encoding='UTF-8'?>
+      <response protocol='3.1'>
+        <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+          <updatecheck status='ok'>
+            <urls>
+              <url codebase='http://localhost/download/'/>
+            </urls>
+            <manifest version='1.0' prodversionmin='11.0.1.0'>
+              <packages>
+                <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+                         hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+                                      7c9b12cb7cc067667bde87'/>
+              </packages>
+            </manifest>
+          </updatecheck>
+        </app>
+      </response>
+      */
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_TRUE(enabled_component_updates);
+      EXPECT_EQ(1u, ids_to_check.size());
+
+      const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+      EXPECT_EQ(id, ids_to_check[0]);
+      EXPECT_EQ(1u, components.count(id));
+
+      ProtocolParser::Result::Manifest::Package package;
+      package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+      package.hash_sha256 =
+          "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+      ProtocolParser::Result result;
+      result.extension_id = id;
+      result.status = "ok";
+      result.crx_urls.push_back(GURL("http://localhost/download/"));
+      result.manifest.version = "1.0";
+      result.manifest.browser_min_version = "11.0.1.0";
+      result.manifest.packages.push_back(package);
+
+      ProtocolParser::Results results;
+      results.list.push_back(result);
+
+      // Verify that calling Install sets ondemand.
+      EXPECT_TRUE(components.at(id)->is_foreground());
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override {
+      DownloadMetrics download_metrics;
+      FilePath path;
+      Result result;
+      if (url.path() == "/download/jebgalgnebhfojomionfpkfelancnnkf.crx") {
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = 0;
+        download_metrics.downloaded_bytes = 1843;
+        download_metrics.total_bytes = 1843;
+        download_metrics.download_time_ms = 1000;
+
+        EXPECT_TRUE(MakeTestFile(
+            TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
+
+        result.error = 0;
+        result.response = path;
+      } else {
+        NOTREACHED();
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+                                    base::Unretained(this)));
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+                                    base::Unretained(this), true, result,
+                                    download_metrics));
+    }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
+      EXPECT_EQ(1u, ping_data.size());
+      EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+      EXPECT_EQ(base::Version("0.0"), ping_data[0].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+      EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+      EXPECT_EQ(0, ping_data[0].error_code);
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  InSequence seq;
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(AtLeast(1));
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+
+  update_client->AddObserver(&observer);
+
+  update_client->Install(
+      std::string("jebgalgnebhfojomionfpkfelancnnkf"),
+      base::BindOnce(&DataCallbackMock::Callback),
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the install of one CRX when no component data is provided. This
+// results in an install error.
+TEST_F(UpdateClientTest, OneCrxInstallNoCrxComponentData) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      return {base::nullopt};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      NOTREACHED();
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override { NOTREACHED(); }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      EXPECT_EQ(0u, MockPingManagerImpl::ping_data().size());
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  InSequence seq;
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1)
+      .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+        // Tests that the state of the component when the CrxComponent data
+        // is not provided. In this case, the optional |item.component| instance
+        // is not present.
+        CrxUpdateItem item;
+        EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+        EXPECT_EQ(ComponentState::kUpdateError, item.state);
+        EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", item.id.c_str());
+        EXPECT_FALSE(item.component);
+        EXPECT_EQ(ErrorCategory::kService, item.error_category);
+        EXPECT_EQ(static_cast<int>(Error::CRX_NOT_FOUND), item.error_code);
+        EXPECT_EQ(0, item.extra_code1);
+      }));
+
+  update_client->AddObserver(&observer);
+
+  update_client->Install(
+      std::string("jebgalgnebhfojomionfpkfelancnnkf"),
+      base::BindOnce(&DataCallbackMock::Callback),
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests that overlapping installs of the same CRX result in an error.
+TEST_F(UpdateClientTest, ConcurrentInstallSameCRX) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      CrxComponent crx;
+      crx.name = "test_jebg";
+      crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx.version = base::Version("0.0");
+      crx.installer = base::MakeRefCounted<TestInstaller>();
+      crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+      return {crx};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      static int num_call = 0;
+      ++num_call;
+
+      EXPECT_LE(num_call, 2);
+
+      if (num_call == 1) {
+        EXPECT_EQ(Error::UPDATE_IN_PROGRESS, error);
+        return;
+      }
+      if (num_call == 2) {
+        EXPECT_EQ(Error::NONE, error);
+        std::move(quit_closure).Run();
+      }
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_TRUE(enabled_component_updates);
+      EXPECT_EQ(1u, ids_to_check.size());
+      const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+      EXPECT_EQ(id, ids_to_check.front());
+      EXPECT_EQ(1u, components.count(id));
+
+      ProtocolParser::Result result;
+      result.extension_id = id;
+      result.status = "noupdate";
+
+      ProtocolParser::Results results;
+      results.list.push_back(result);
+
+      // Verify that calling Install sets |is_foreground| for the component.
+      EXPECT_TRUE(components.at(id)->is_foreground());
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+
+  update_client->AddObserver(&observer);
+
+  update_client->Install(
+      std::string("jebgalgnebhfojomionfpkfelancnnkf"),
+      base::BindOnce(&DataCallbackMock::Callback),
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  update_client->Install(
+      std::string("jebgalgnebhfojomionfpkfelancnnkf"),
+      base::BindOnce(&DataCallbackMock::Callback),
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests that UpdateClient::Update returns Error::INVALID_ARGUMENT when
+// the |ids| parameter is empty.
+TEST_F(UpdateClientTest, EmptyIdList) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      return {};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      DCHECK_EQ(Error::INVALID_ARGUMENT, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      NOTREACHED();
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  const std::vector<std::string> empty_id_list;
+  update_client->Update(
+      empty_id_list, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+  RunThreads();
+}
+
+TEST_F(UpdateClientTest, SendUninstallPing) {
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return nullptr;
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      NOTREACHED();
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return nullptr;
+    }
+
+   private:
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+    ~MockCrxDownloader() override {}
+
+    void DoStartDownload(const GURL& url) override {}
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
+      EXPECT_EQ(1u, ping_data.size());
+      EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+      EXPECT_EQ(base::Version("1.2.3.4"), ping_data[0].previous_version);
+      EXPECT_EQ(base::Version("0"), ping_data[0].next_version);
+      EXPECT_EQ(10, ping_data[0].extra_code1);
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  update_client->SendUninstallPing(
+      "jebgalgnebhfojomionfpkfelancnnkf", base::Version("1.2.3.4"), 10,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+}
+
+TEST_F(UpdateClientTest, RetryAfter) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      CrxComponent crx;
+      crx.name = "test_jebg";
+      crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx.version = base::Version("0.9");
+      crx.installer = base::MakeRefCounted<TestInstaller>();
+      crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+      return {crx};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      static int num_call = 0;
+      ++num_call;
+
+      EXPECT_LE(num_call, 4);
+
+      if (num_call == 1) {
+        EXPECT_EQ(Error::NONE, error);
+      } else if (num_call == 2) {
+        // This request is throttled since the update engine received a
+        // positive |retry_after_sec| value in the update check response.
+        EXPECT_EQ(Error::RETRY_LATER, error);
+      } else if (num_call == 3) {
+        // This request is a foreground Install, which is never throttled.
+        // The update engine received a |retry_after_sec| value of 0, which
+        // resets the throttling.
+        EXPECT_EQ(Error::NONE, error);
+      } else if (num_call == 4) {
+        // This request succeeds since there is no throttling in effect.
+        EXPECT_EQ(Error::NONE, error);
+      }
+
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      EXPECT_FALSE(session_id.empty());
+
+      static int num_call = 0;
+      ++num_call;
+
+      EXPECT_LE(num_call, 3);
+
+      int retry_after_sec(0);
+      if (num_call == 1) {
+        // Throttle the next call.
+        retry_after_sec = 60 * 60;  // 1 hour.
+      }
+
+      EXPECT_EQ(1u, ids_to_check.size());
+      const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+      EXPECT_EQ(id, ids_to_check.front());
+      EXPECT_EQ(1u, components.count(id));
+
+      ProtocolParser::Result result;
+      result.extension_id = id;
+      result.status = "noupdate";
+
+      ProtocolParser::Results results;
+      results.list.push_back(result);
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, retry_after_sec));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+
+  InSequence seq;
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
+  {
+    // The engine handles this Update call but responds with a valid
+    // |retry_after_sec|, which causes subsequent calls to fail.
+    base::RunLoop runloop;
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+                          false,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
+                                         runloop.QuitClosure()));
+    runloop.Run();
+  }
+
+  {
+    // This call will result in a completion callback invoked with
+    // Error::ERROR_UPDATE_RETRY_LATER.
+    base::RunLoop runloop;
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+                          false,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
+                                         runloop.QuitClosure()));
+    runloop.Run();
+  }
+
+  {
+    // The Install call is handled, and the throttling is reset due to
+    // the value of |retry_after_sec| in the completion callback.
+    base::RunLoop runloop;
+    update_client->Install(std::string("jebgalgnebhfojomionfpkfelancnnkf"),
+                           base::BindOnce(&DataCallbackMock::Callback),
+                           base::BindOnce(&CompletionCallbackMock::Callback,
+                                          runloop.QuitClosure()));
+    runloop.Run();
+  }
+
+  {
+    // This call succeeds.
+    base::RunLoop runloop;
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+                          false,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
+                                         runloop.QuitClosure()));
+    runloop.Run();
+  }
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the update check for two CRXs scenario. The first component supports
+// the group policy to enable updates, and has its updates disabled. The second
+// component has an update. The server does not honor the "updatedisabled"
+// attribute and returns updates for both components. However, the update for
+// the first component is not applied and the client responds with a
+// (SERVICE_ERROR, UPDATE_DISABLED)
+TEST_F(UpdateClientTest, TwoCrxUpdateOneUpdateDisabled) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      CrxComponent crx1;
+      crx1.name = "test_jebg";
+      crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx1.version = base::Version("0.9");
+      crx1.installer = base::MakeRefCounted<TestInstaller>();
+      crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+      crx1.supports_group_policy_enable_component_updates = true;
+
+      CrxComponent crx2;
+      crx2.name = "test_ihfo";
+      crx2.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
+      crx2.version = base::Version("0.8");
+      crx2.installer = base::MakeRefCounted<TestInstaller>();
+      crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+      return {crx1, crx2};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      /*
+      Mock the following response:
+
+      <?xml version='1.0' encoding='UTF-8'?>
+      <response protocol='3.1'>
+        <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+          <updatecheck status='ok'>
+            <urls>
+              <url codebase='http://localhost/download/'/>
+            </urls>
+            <manifest version='1.0' prodversionmin='11.0.1.0'>
+              <packages>
+                <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+                         hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+                                      7c9b12cb7cc067667bde87'/>
+              </packages>
+            </manifest>
+          </updatecheck>
+        </app>
+        <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+          <updatecheck status='ok'>
+            <urls>
+              <url codebase='http://localhost/download/'/>
+            </urls>
+            <manifest version='1.0' prodversionmin='11.0.1.0'>
+              <packages>
+                <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
+                         hash_sha256='813c59747e139a608b3b5fc49633affc6db574373f
+                                      309f156ea6d27229c0b3f9'/>
+              </packages>
+            </manifest>
+          </updatecheck>
+        </app>
+      </response>
+      */
+
+      // UpdateClient reads the state of |enabled_component_updates| from the
+      // configurator instance, persists its value in the corresponding
+      // update context, and propagates it down to each of the update actions,
+      // and further down to the UpdateChecker instance.
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_FALSE(enabled_component_updates);
+      EXPECT_EQ(2u, ids_to_check.size());
+
+      ProtocolParser::Results results;
+      {
+        const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+        EXPECT_EQ(id, ids_to_check[0]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result::Manifest::Package package;
+        package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+        package.hash_sha256 =
+            "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+        ProtocolParser::Result result;
+        result.extension_id = id;
+        result.status = "ok";
+        result.crx_urls.push_back(GURL("http://localhost/download/"));
+        result.manifest.version = "1.0";
+        result.manifest.browser_min_version = "11.0.1.0";
+        result.manifest.packages.push_back(package);
+        results.list.push_back(result);
+      }
+
+      {
+        const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+        EXPECT_EQ(id, ids_to_check[1]);
+        EXPECT_EQ(1u, components.count(id));
+
+        ProtocolParser::Result::Manifest::Package package;
+        package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
+        package.hash_sha256 =
+            "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
+
+        ProtocolParser::Result result;
+        result.extension_id = id;
+        result.status = "ok";
+        result.crx_urls.push_back(GURL("http://localhost/download/"));
+        result.manifest.version = "1.0";
+        result.manifest.browser_min_version = "11.0.1.0";
+        result.manifest.packages.push_back(package);
+        results.list.push_back(result);
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override {
+      DownloadMetrics download_metrics;
+      FilePath path;
+      Result result;
+      if (url.path() == "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = 0;
+        download_metrics.downloaded_bytes = 53638;
+        download_metrics.total_bytes = 53638;
+        download_metrics.download_time_ms = 2000;
+
+        EXPECT_TRUE(MakeTestFile(
+            TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
+
+        result.error = 0;
+        result.response = path;
+      } else {
+        NOTREACHED();
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+                                    base::Unretained(this)));
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+                                    base::Unretained(this), true, result,
+                                    download_metrics));
+    }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
+      EXPECT_EQ(2u, ping_data.size());
+      EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+      EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+      EXPECT_EQ(4, static_cast<int>(ping_data[0].error_category));
+      EXPECT_EQ(2, ping_data[0].error_code);
+      EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
+      EXPECT_EQ(base::Version("0.8"), ping_data[1].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[1].next_version);
+      EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
+      EXPECT_EQ(0, ping_data[1].error_code);
+    }
+  };
+
+  // Disables updates for the components declaring support for the group policy.
+  config()->SetEnabledComponentUpdates(false);
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(AtLeast(1));
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+  }
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+                                        "ihfokbkgjpifnbbojhneepfflplebdkc"};
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the scenario where the update check fails.
+TEST_F(UpdateClientTest, OneCrxUpdateCheckFails) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      CrxComponent crx;
+      crx.name = "test_jebg";
+      crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+      crx.version = base::Version("0.9");
+      crx.installer = base::MakeRefCounted<TestInstaller>();
+      crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+      return {crx};
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::UPDATE_CHECK_ERROR, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_TRUE(enabled_component_updates);
+      EXPECT_EQ(1u, ids_to_check.size());
+      const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+      EXPECT_EQ(id, ids_to_check.front());
+      EXPECT_EQ(1u, components.count(id));
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE,
+          base::BindOnce(std::move(update_check_callback), base::nullopt,
+                         ErrorCategory::kUpdateCheck, -1, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  InSequence seq;
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1);
+  EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                "jebgalgnebhfojomionfpkfelancnnkf"))
+      .Times(1)
+      .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+        CrxUpdateItem item;
+        EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+        EXPECT_EQ(ComponentState::kUpdateError, item.state);
+        EXPECT_EQ(5, static_cast<int>(item.error_category));
+        EXPECT_EQ(-1, item.error_code);
+        EXPECT_EQ(0, item.extra_code1);
+      }));
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+// Tests the scenario where the server responds with different values for
+// application status.
+TEST_F(UpdateClientTest, OneCrxErrorUnknownApp) {
+  class DataCallbackMock {
+   public:
+    static std::vector<base::Optional<CrxComponent>> Callback(
+        const std::vector<std::string>& ids) {
+      std::vector<base::Optional<CrxComponent>> component;
+      {
+        CrxComponent crx;
+        crx.name = "test_jebg";
+        crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+        crx.version = base::Version("0.9");
+        crx.installer = base::MakeRefCounted<TestInstaller>();
+        crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+        component.push_back(crx);
+      }
+      {
+        CrxComponent crx;
+        crx.name = "test_abag";
+        crx.pk_hash.assign(abag_hash, abag_hash + base::size(abag_hash));
+        crx.version = base::Version("0.1");
+        crx.installer = base::MakeRefCounted<TestInstaller>();
+        crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+        component.push_back(crx);
+      }
+      {
+        CrxComponent crx;
+        crx.name = "test_ihfo";
+        crx.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
+        crx.version = base::Version("0.2");
+        crx.installer = base::MakeRefCounted<TestInstaller>();
+        crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+        component.push_back(crx);
+      }
+      {
+        CrxComponent crx;
+        crx.name = "test_gjpm";
+        crx.pk_hash.assign(gjpm_hash, gjpm_hash + base::size(gjpm_hash));
+        crx.version = base::Version("0.3");
+        crx.installer = base::MakeRefCounted<TestInstaller>();
+        crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+        component.push_back(crx);
+      }
+      return component;
+    }
+  };
+
+  class CompletionCallbackMock {
+   public:
+    static void Callback(base::OnceClosure quit_closure, Error error) {
+      EXPECT_EQ(Error::NONE, error);
+      std::move(quit_closure).Run();
+    }
+  };
+
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_TRUE(enabled_component_updates);
+      EXPECT_EQ(4u, ids_to_check.size());
+
+      const std::string update_response =
+          ")]}'"
+          R"({"response": {)"
+          R"( "protocol": "3.1",)"
+          R"( "app": [)"
+          R"({"appid": "jebgalgnebhfojomionfpkfelancnnkf",)"
+          R"( "status": "error-unknownApplication"},)"
+          R"({"appid": "abagagagagagagagagagagagagagagag",)"
+          R"( "status": "restricted"},)"
+          R"({"appid": "ihfokbkgjpifnbbojhneepfflplebdkc",)"
+          R"( "status": "error-invalidAppId"},)"
+          R"({"appid": "gjpmebpgbhcamgdgjcmnjfhggjpgcimm",)"
+          R"( "status": "error-foobarApp"})"
+          R"(]}})";
+
+      const auto parser = ProtocolHandlerFactoryJSON().CreateParser();
+      EXPECT_TRUE(parser->Parse(update_response));
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE,
+          base::BindOnce(std::move(update_check_callback), parser->results(),
+                         ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  MockObserver observer;
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                  "jebgalgnebhfojomionfpkfelancnnkf"))
+        .Times(1)
+        .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+          CrxUpdateItem item;
+          EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+          EXPECT_EQ(ComponentState::kUpdateError, item.state);
+          EXPECT_EQ(5, static_cast<int>(item.error_category));
+          EXPECT_EQ(-10006, item.error_code);  // UNKNOWN_APPPLICATION.
+          EXPECT_EQ(0, item.extra_code1);
+        }));
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "abagagagagagagagagagagagagagagag"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                  "abagagagagagagagagagagagagagagag"))
+        .Times(1)
+        .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+          CrxUpdateItem item;
+          EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+          EXPECT_EQ(ComponentState::kUpdateError, item.state);
+          EXPECT_EQ(5, static_cast<int>(item.error_category));
+          EXPECT_EQ(-10007, item.error_code);  // RESTRICTED_APPLICATION.
+          EXPECT_EQ(0, item.extra_code1);
+        }));
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                  "ihfokbkgjpifnbbojhneepfflplebdkc"))
+        .Times(1)
+        .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+          CrxUpdateItem item;
+          EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+          EXPECT_EQ(ComponentState::kUpdateError, item.state);
+          EXPECT_EQ(5, static_cast<int>(item.error_category));
+          EXPECT_EQ(-10008, item.error_code);  // INVALID_APPID.
+          EXPECT_EQ(0, item.extra_code1);
+        }));
+  }
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+                                  "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"))
+        .Times(1);
+    EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+                                  "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"))
+        .Times(1)
+        .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+          CrxUpdateItem item;
+          EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+          EXPECT_EQ(ComponentState::kUpdateError, item.state);
+          EXPECT_EQ(5, static_cast<int>(item.error_category));
+          EXPECT_EQ(-10004, item.error_code);  // UPDATE_RESPONSE_NOT_FOUND.
+          EXPECT_EQ(0, item.extra_code1);
+        }));
+  }
+
+  update_client->AddObserver(&observer);
+
+  const std::vector<std::string> ids = {
+      "jebgalgnebhfojomionfpkfelancnnkf", "abagagagagagagagagagagagagagagag",
+      "ihfokbkgjpifnbbojhneepfflplebdkc", "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"};
+  update_client->Update(
+      ids, base::BindOnce(&DataCallbackMock::Callback), true,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+  RunThreads();
+
+  update_client->RemoveObserver(&observer);
+}
+
+#if defined(OS_WIN)  // ActionRun is only implemented on Windows.
+
+// Tests that a run action in invoked in the CRX install scenario.
+TEST_F(UpdateClientTest, ActionRun_Install) {
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      /*
+      Mock the following response:
+
+      <?xml version='1.0' encoding='UTF-8'?>
+      <response protocol='3.1'>
+        <app appid='gjpmebpgbhcamgdgjcmnjfhggjpgcimm'>
+          <updatecheck status='ok'>
+            <urls>
+              <url codebase='http://localhost/download/'/>
+            </urls>
+            <manifest version='1.0' prodversionmin='11.0.1.0'>
+              <packages>
+                <package name='runaction_test_win.crx3'
+                         hash_sha256='89290a0d2ff21ca5b45e109c6cc859ab5fe294e19c102d54acd321429c372cea'/>
+              </packages>
+            </manifest>
+            <actions>"
+             <action run='ChromeRecovery.crx3'/>"
+            </actions>"
+          </updatecheck>
+        </app>
+      </response>
+      */
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_TRUE(enabled_component_updates);
+      EXPECT_EQ(1u, ids_to_check.size());
+
+      const std::string id = "gjpmebpgbhcamgdgjcmnjfhggjpgcimm";
+      EXPECT_EQ(id, ids_to_check[0]);
+      EXPECT_EQ(1u, components.count(id));
+
+      ProtocolParser::Result::Manifest::Package package;
+      package.name = "runaction_test_win.crx3";
+      package.hash_sha256 =
+          "89290a0d2ff21ca5b45e109c6cc859ab5fe294e19c102d54acd321429c372cea";
+
+      ProtocolParser::Result result;
+      result.extension_id = id;
+      result.status = "ok";
+      result.crx_urls.push_back(GURL("http://localhost/download/"));
+      result.manifest.version = "1.0";
+      result.manifest.browser_min_version = "11.0.1.0";
+      result.manifest.packages.push_back(package);
+      result.action_run = "ChromeRecovery.crx3";
+
+      ProtocolParser::Results results;
+      results.list.push_back(result);
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override {
+      DownloadMetrics download_metrics;
+      FilePath path;
+      Result result;
+      if (url.path() == "/download/runaction_test_win.crx3") {
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = 0;
+        download_metrics.downloaded_bytes = 1843;
+        download_metrics.total_bytes = 1843;
+        download_metrics.download_time_ms = 1000;
+
+        EXPECT_TRUE(
+            MakeTestFile(TestFilePath("runaction_test_win.crx3"), &path));
+
+        result.error = 0;
+        result.response = path;
+      } else {
+        NOTREACHED();
+      }
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+                                    base::Unretained(this), true, result,
+                                    download_metrics));
+    }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      EXPECT_EQ(3u, events().size());
+
+      /*
+      "<event eventtype="14" eventresult="1" downloader="unknown" "
+      "url="http://localhost/download/runaction_test_win.crx3"
+      "downloaded=1843 "
+      "total=1843 download_time_ms="1000" previousversion="0.0" "
+      "nextversion="1.0"/>"
+      */
+      const auto& event0 = events()[0];
+      EXPECT_EQ(14, event0.FindKey("eventtype")->GetInt());
+      EXPECT_EQ(1, event0.FindKey("eventresult")->GetInt());
+      EXPECT_EQ("unknown", event0.FindKey("downloader")->GetString());
+      EXPECT_EQ("http://localhost/download/runaction_test_win.crx3",
+                event0.FindKey("url")->GetString());
+      EXPECT_EQ(1843, event0.FindKey("downloaded")->GetDouble());
+      EXPECT_EQ(1843, event0.FindKey("total")->GetDouble());
+      EXPECT_EQ(1000, event0.FindKey("download_time_ms")->GetDouble());
+      EXPECT_EQ("0.0", event0.FindKey("previousversion")->GetString());
+      EXPECT_EQ("1.0", event0.FindKey("nextversion")->GetString());
+
+      // "<event eventtype="42" eventresult="1" errorcode="1877345072"/>"
+      const auto& event1 = events()[1];
+      EXPECT_EQ(42, event1.FindKey("eventtype")->GetInt());
+      EXPECT_EQ(1, event1.FindKey("eventresult")->GetInt());
+      EXPECT_EQ(1877345072, event1.FindKey("errorcode")->GetInt());
+
+      // "<event eventtype=\"3\" eventresult=\"1\" previousversion=\"0.0\" "
+      // "nextversion=\"1.0\"/>",
+      const auto& event2 = events()[2];
+      EXPECT_EQ(3, event2.FindKey("eventtype")->GetInt());
+      EXPECT_EQ(1, event1.FindKey("eventresult")->GetInt());
+      EXPECT_EQ("0.0", event0.FindKey("previousversion")->GetString());
+      EXPECT_EQ("1.0", event0.FindKey("nextversion")->GetString());
+    }
+  };
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  // The action is a program which returns 1877345072 as a hardcoded value.
+  update_client->Install(
+      std::string("gjpmebpgbhcamgdgjcmnjfhggjpgcimm"),
+      base::BindOnce([](const std::vector<std::string>& ids) {
+        CrxComponent crx;
+        crx.name = "test_niea";
+        crx.pk_hash.assign(gjpm_hash, gjpm_hash + base::size(gjpm_hash));
+        crx.version = base::Version("0.0");
+        crx.installer = base::MakeRefCounted<VersionedTestInstaller>();
+        crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+        return std::vector<base::Optional<CrxComponent>>{crx};
+      }),
+      base::BindOnce(
+          [](base::OnceClosure quit_closure, Error error) {
+            EXPECT_EQ(Error::NONE, error);
+            std::move(quit_closure).Run();
+          },
+          quit_closure()));
+
+  RunThreads();
+}
+
+// Tests that a run action is invoked in an update scenario when there was
+// no update.
+TEST_F(UpdateClientTest, ActionRun_NoUpdate) {
+  class MockUpdateChecker : public UpdateChecker {
+   public:
+    static std::unique_ptr<UpdateChecker> Create(
+        scoped_refptr<Configurator> config,
+        PersistedData* metadata) {
+      return std::make_unique<MockUpdateChecker>();
+    }
+
+    void CheckForUpdates(
+        const std::string& session_id,
+        const std::vector<std::string>& ids_to_check,
+        const IdToComponentPtrMap& components,
+        const base::flat_map<std::string, std::string>& additional_attributes,
+        bool enabled_component_updates,
+        UpdateCheckCallback update_check_callback) override {
+      /*
+      Mock the following response:
+
+      <?xml version='1.0' encoding='UTF-8'?>
+      <response protocol='3.1'>
+        <app appid='gjpmebpgbhcamgdgjcmnjfhggjpgcimm'>
+          <updatecheck status='noupdate'>
+            <actions>"
+             <action run=ChromeRecovery.crx3'/>"
+            </actions>"
+          </updatecheck>
+        </app>
+      </response>
+      */
+      EXPECT_FALSE(session_id.empty());
+      EXPECT_EQ(1u, ids_to_check.size());
+      const std::string id = "gjpmebpgbhcamgdgjcmnjfhggjpgcimm";
+      EXPECT_EQ(id, ids_to_check[0]);
+      EXPECT_EQ(1u, components.count(id));
+
+      ProtocolParser::Result result;
+      result.extension_id = id;
+      result.status = "noupdate";
+      result.action_run = "ChromeRecovery.crx3";
+
+      ProtocolParser::Results results;
+      results.list.push_back(result);
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+                                    ErrorCategory::kNone, 0, 0));
+    }
+  };
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    static std::unique_ptr<CrxDownloader> Create(
+        bool is_background_download,
+        scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+      return std::make_unique<MockCrxDownloader>();
+    }
+
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+   private:
+    void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      EXPECT_EQ(1u, events().size());
+
+      // "<event eventtype="42" eventresult="1" errorcode="1877345072"/>"
+      const auto& event = events()[0];
+      EXPECT_EQ(42, event.FindKey("eventtype")->GetInt());
+      EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+      EXPECT_EQ(1877345072, event.FindKey("errorcode")->GetInt());
+    }
+  };
+
+  // Unpack the CRX to mock an existing install to be updated. The payload to
+  // run is going to be picked up from this directory.
+  base::FilePath unpack_path;
+  {
+    base::RunLoop runloop;
+    base::OnceClosure quit_closure = runloop.QuitClosure();
+
+    auto config = base::MakeRefCounted<TestConfigurator>();
+    auto component_unpacker = base::MakeRefCounted<ComponentUnpacker>(
+        std::vector<uint8_t>(std::begin(gjpm_hash), std::end(gjpm_hash)),
+        TestFilePath("runaction_test_win.crx3"), nullptr,
+        config->GetUnzipperFactory()->Create(),
+        config->GetPatcherFactory()->Create(),
+        crx_file::VerifierFormat::CRX2_OR_CRX3);
+
+    component_unpacker->Unpack(base::BindOnce(
+        [](base::FilePath* unpack_path, base::OnceClosure quit_closure,
+           const ComponentUnpacker::Result& result) {
+          EXPECT_EQ(UnpackerError::kNone, result.error);
+          EXPECT_EQ(0, result.extended_error);
+          *unpack_path = result.unpack_path;
+          std::move(quit_closure).Run();
+        },
+        &unpack_path, runloop.QuitClosure()));
+
+    runloop.Run();
+  }
+
+  EXPECT_FALSE(unpack_path.empty());
+  EXPECT_TRUE(base::DirectoryExists(unpack_path));
+  int64_t file_size = 0;
+  EXPECT_TRUE(base::GetFileSize(unpack_path.AppendASCII("ChromeRecovery.crx3"),
+                                &file_size));
+  EXPECT_EQ(44582, file_size);
+
+  base::ScopedTempDir unpack_path_owner;
+  EXPECT_TRUE(unpack_path_owner.Set(unpack_path));
+
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+  // The action is a program which returns 1877345072 as a hardcoded value.
+  const std::vector<std::string> ids = {"gjpmebpgbhcamgdgjcmnjfhggjpgcimm"};
+  update_client->Update(
+      ids,
+      base::BindOnce(
+          [](const base::FilePath& unpack_path,
+             const std::vector<std::string>& ids) {
+            CrxComponent crx;
+            crx.name = "test_niea";
+            crx.pk_hash.assign(gjpm_hash, gjpm_hash + base::size(gjpm_hash));
+            crx.version = base::Version("1.0");
+            crx.installer =
+                base::MakeRefCounted<ReadOnlyTestInstaller>(unpack_path);
+            crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+            return std::vector<base::Optional<CrxComponent>>{crx};
+          },
+          unpack_path),
+      false,
+      base::BindOnce(
+          [](base::OnceClosure quit_closure, Error error) {
+            EXPECT_EQ(Error::NONE, error);
+            std::move(quit_closure).Run();
+          },
+          quit_closure()));
+
+  RunThreads();
+}
+
+#endif  // OS_WIN
+
+}  // namespace update_client
diff --git a/src/components/update_client/update_engine.cc b/src/components/update_client/update_engine.cc
new file mode 100644
index 0000000..a6d6920
--- /dev/null
+++ b/src/components/update_client/update_engine.cc
@@ -0,0 +1,438 @@
+// Copyright 2015 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 "components/update_client/update_engine.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/guid.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/optional.h"
+#include "base/stl_util.h"
+#include "base/strings/strcat.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/prefs/pref_service.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/crx_update_item.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/protocol_parser.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+
+namespace update_client {
+
+UpdateContext::UpdateContext(
+    scoped_refptr<Configurator> config,
+    bool is_foreground,
+    const std::vector<std::string>& ids,
+    UpdateClient::CrxDataCallback crx_data_callback,
+    const UpdateEngine::NotifyObserversCallback& notify_observers_callback,
+    UpdateEngine::Callback callback,
+    CrxDownloader::Factory crx_downloader_factory)
+    : config(config),
+      is_foreground(is_foreground),
+      enabled_component_updates(config->EnabledComponentUpdates()),
+      ids(ids),
+      crx_data_callback(std::move(crx_data_callback)),
+      notify_observers_callback(notify_observers_callback),
+      callback(std::move(callback)),
+      crx_downloader_factory(crx_downloader_factory),
+      session_id(base::StrCat({"{", base::GenerateGUID(), "}"})) {
+  for (const auto& id : ids) {
+    components.insert(
+        std::make_pair(id, std::make_unique<Component>(*this, id)));
+  }
+}
+
+UpdateContext::~UpdateContext() {}
+
+UpdateEngine::UpdateEngine(
+    scoped_refptr<Configurator> config,
+    UpdateChecker::Factory update_checker_factory,
+    CrxDownloader::Factory crx_downloader_factory,
+    scoped_refptr<PingManager> ping_manager,
+    const NotifyObserversCallback& notify_observers_callback)
+    : config_(config),
+      update_checker_factory_(update_checker_factory),
+      crx_downloader_factory_(crx_downloader_factory),
+      ping_manager_(ping_manager),
+      metadata_(
+          std::make_unique<PersistedData>(config->GetPrefService(),
+                                          config->GetActivityDataService())),
+      notify_observers_callback_(notify_observers_callback) {}
+
+UpdateEngine::~UpdateEngine() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void UpdateEngine::Update(bool is_foreground,
+                          const std::vector<std::string>& ids,
+                          UpdateClient::CrxDataCallback crx_data_callback,
+                          Callback callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (ids.empty()) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(callback), Error::INVALID_ARGUMENT));
+    return;
+  }
+
+  if (IsThrottled(is_foreground)) {
+    // TODO(xiaochu): remove this log after https://crbug.com/851151 is fixed.
+    VLOG(1) << "Background update is throttled for following components:";
+    for (const auto& id : ids) {
+      VLOG(1) << "id:" << id;
+    }
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), Error::RETRY_LATER));
+    return;
+  }
+
+  const auto update_context = base::MakeRefCounted<UpdateContext>(
+      config_, is_foreground, ids, std::move(crx_data_callback),
+      notify_observers_callback_, std::move(callback), crx_downloader_factory_);
+  DCHECK(!update_context->session_id.empty());
+
+  const auto result = update_contexts_.insert(
+      std::make_pair(update_context->session_id, update_context));
+  DCHECK(result.second);
+
+  // Calls out to get the corresponding CrxComponent data for the CRXs in this
+  // update context.
+  const auto crx_components =
+      std::move(update_context->crx_data_callback).Run(update_context->ids);
+  DCHECK_EQ(update_context->ids.size(), crx_components.size());
+
+  for (size_t i = 0; i != update_context->ids.size(); ++i) {
+    const auto& id = update_context->ids[i];
+
+    DCHECK(update_context->components[id]->state() == ComponentState::kNew);
+
+    const auto crx_component = crx_components[i];
+    if (crx_component) {
+      // This component can be checked for updates.
+      DCHECK_EQ(id, GetCrxComponentID(*crx_component));
+      auto& component = update_context->components[id];
+      component->set_crx_component(*crx_component);
+      component->set_previous_version(component->crx_component()->version);
+      component->set_previous_fp(component->crx_component()->fingerprint);
+      update_context->components_to_check_for_updates.push_back(id);
+    } else {
+      // |CrxDataCallback| did not return a CrxComponent instance for this
+      // component, which most likely, has been uninstalled. This component
+      // is going to be transitioned to an error state when the its |Handle|
+      // method is called later on by the engine.
+      update_context->component_queue.push(id);
+    }
+  }
+
+  if (update_context->components_to_check_for_updates.empty()) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
+                                  base::Unretained(this), update_context));
+    return;
+  }
+
+  for (const auto& id : update_context->components_to_check_for_updates)
+    update_context->components[id]->Handle(
+        base::BindOnce(&UpdateEngine::ComponentCheckingForUpdatesStart,
+                       base::Unretained(this), update_context, id));
+}
+
+void UpdateEngine::ComponentCheckingForUpdatesStart(
+    scoped_refptr<UpdateContext> update_context,
+    const std::string& id) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(update_context);
+
+  DCHECK_EQ(1u, update_context->components.count(id));
+  DCHECK(update_context->components.at(id));
+
+  // Handle |kChecking| state.
+  auto& component = *update_context->components.at(id);
+  component.Handle(
+      base::BindOnce(&UpdateEngine::ComponentCheckingForUpdatesComplete,
+                     base::Unretained(this), update_context));
+
+  ++update_context->num_components_ready_to_check;
+  if (update_context->num_components_ready_to_check <
+      update_context->components_to_check_for_updates.size()) {
+    return;
+  }
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&UpdateEngine::DoUpdateCheck,
+                                base::Unretained(this), update_context));
+}
+
+void UpdateEngine::DoUpdateCheck(scoped_refptr<UpdateContext> update_context) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(update_context);
+
+  update_context->update_checker =
+      update_checker_factory_(config_, metadata_.get());
+
+  update_context->update_checker->CheckForUpdates(
+      update_context->session_id,
+      update_context->components_to_check_for_updates,
+      update_context->components, config_->ExtraRequestParams(),
+      update_context->enabled_component_updates,
+      base::BindOnce(&UpdateEngine::UpdateCheckResultsAvailable,
+                     base::Unretained(this), update_context));
+}
+
+void UpdateEngine::UpdateCheckResultsAvailable(
+    scoped_refptr<UpdateContext> update_context,
+    const base::Optional<ProtocolParser::Results>& results,
+    ErrorCategory error_category,
+    int error,
+    int retry_after_sec) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(update_context);
+
+  update_context->retry_after_sec = retry_after_sec;
+
+  // Only positive values for throttle_sec are effective. 0 means that no
+  // throttling occurs and it resets |throttle_updates_until_|.
+  // Negative values are not trusted and are ignored.
+  constexpr int kMaxRetryAfterSec = 24 * 60 * 60;  // 24 hours.
+  const int throttle_sec =
+      std::min(update_context->retry_after_sec, kMaxRetryAfterSec);
+  if (throttle_sec >= 0) {
+    throttle_updates_until_ =
+        throttle_sec ? base::TimeTicks::Now() +
+                           base::TimeDelta::FromSeconds(throttle_sec)
+                     : base::TimeTicks();
+  }
+
+  update_context->update_check_error = error;
+
+  if (error) {
+    DCHECK(!results);
+    for (const auto& id : update_context->components_to_check_for_updates) {
+      DCHECK_EQ(1u, update_context->components.count(id));
+      auto& component = update_context->components.at(id);
+      component->SetUpdateCheckResult(base::nullopt,
+                                      ErrorCategory::kUpdateCheck, error);
+    }
+    return;
+  }
+
+  DCHECK(results);
+  DCHECK_EQ(0, error);
+
+  std::map<std::string, ProtocolParser::Result> id_to_result;
+  for (const auto& result : results->list)
+    id_to_result[result.extension_id] = result;
+
+  for (const auto& id : update_context->components_to_check_for_updates) {
+    DCHECK_EQ(1u, update_context->components.count(id));
+    auto& component = update_context->components.at(id);
+    const auto& it = id_to_result.find(id);
+    if (it != id_to_result.end()) {
+      const auto result = it->second;
+      const auto error = [](const std::string& status) {
+        // First, handle app status literals which can be folded down as an
+        // updatecheck status
+        if (status == "error-unknownApplication")
+          return std::make_pair(ErrorCategory::kUpdateCheck,
+                                ProtocolError::UNKNOWN_APPLICATION);
+        if (status == "restricted")
+          return std::make_pair(ErrorCategory::kUpdateCheck,
+                                ProtocolError::RESTRICTED_APPLICATION);
+        if (status == "error-invalidAppId")
+          return std::make_pair(ErrorCategory::kUpdateCheck,
+                                ProtocolError::INVALID_APPID);
+        // If the parser has return a valid result and the status is not one of
+        // the literals above, then this must be a success an not a parse error.
+        return std::make_pair(ErrorCategory::kNone, ProtocolError::NONE);
+      }(result.status);
+      component->SetUpdateCheckResult(result, error.first,
+                                      static_cast<int>(error.second));
+    } else {
+      component->SetUpdateCheckResult(
+          base::nullopt, ErrorCategory::kUpdateCheck,
+          static_cast<int>(ProtocolError::UPDATE_RESPONSE_NOT_FOUND));
+    }
+  }
+}
+
+void UpdateEngine::ComponentCheckingForUpdatesComplete(
+    scoped_refptr<UpdateContext> update_context) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(update_context);
+
+  ++update_context->num_components_checked;
+  if (update_context->num_components_checked <
+      update_context->components_to_check_for_updates.size()) {
+    return;
+  }
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&UpdateEngine::UpdateCheckComplete,
+                                base::Unretained(this), update_context));
+}
+
+void UpdateEngine::UpdateCheckComplete(
+    scoped_refptr<UpdateContext> update_context) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(update_context);
+
+  for (const auto& id : update_context->components_to_check_for_updates)
+    update_context->component_queue.push(id);
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
+                                base::Unretained(this), update_context));
+}
+
+void UpdateEngine::HandleComponent(
+    scoped_refptr<UpdateContext> update_context) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(update_context);
+
+  auto& queue = update_context->component_queue;
+
+  if (queue.empty()) {
+    const Error error = update_context->update_check_error
+                            ? Error::UPDATE_CHECK_ERROR
+                            : Error::NONE;
+
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&UpdateEngine::UpdateComplete, base::Unretained(this),
+                       update_context, error));
+    return;
+  }
+
+  const auto& id = queue.front();
+  DCHECK_EQ(1u, update_context->components.count(id));
+  const auto& component = update_context->components.at(id);
+  DCHECK(component);
+
+  auto& next_update_delay = update_context->next_update_delay;
+  if (!next_update_delay.is_zero() && component->IsUpdateAvailable()) {
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&UpdateEngine::HandleComponent, base::Unretained(this),
+                       update_context),
+        next_update_delay);
+    next_update_delay = base::TimeDelta();
+
+    notify_observers_callback_.Run(
+        UpdateClient::Observer::Events::COMPONENT_WAIT, id);
+    return;
+  }
+
+  component->Handle(base::BindOnce(&UpdateEngine::HandleComponentComplete,
+                                   base::Unretained(this), update_context));
+}
+
+void UpdateEngine::HandleComponentComplete(
+    scoped_refptr<UpdateContext> update_context) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(update_context);
+
+  auto& queue = update_context->component_queue;
+  DCHECK(!queue.empty());
+
+  const auto& id = queue.front();
+  DCHECK_EQ(1u, update_context->components.count(id));
+  const auto& component = update_context->components.at(id);
+  DCHECK(component);
+
+  if (component->IsHandled()) {
+    update_context->next_update_delay = component->GetUpdateDuration();
+
+    if (!component->events().empty()) {
+      ping_manager_->SendPing(*component,
+                              base::BindOnce([](int, const std::string&) {}));
+    }
+
+    queue.pop();
+  }
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
+                                base::Unretained(this), update_context));
+}
+
+void UpdateEngine::UpdateComplete(scoped_refptr<UpdateContext> update_context,
+                                  Error error) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(update_context);
+
+  const auto num_erased = update_contexts_.erase(update_context->session_id);
+  DCHECK_EQ(1u, num_erased);
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(update_context->callback), error));
+}
+
+bool UpdateEngine::GetUpdateState(const std::string& id,
+                                  CrxUpdateItem* update_item) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  for (const auto& context : update_contexts_) {
+    const auto& components = context.second->components;
+    const auto it = components.find(id);
+    if (it != components.end()) {
+      *update_item = it->second->GetCrxUpdateItem();
+      return true;
+    }
+  }
+  return false;
+}
+
+bool UpdateEngine::IsThrottled(bool is_foreground) const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (is_foreground || throttle_updates_until_.is_null())
+    return false;
+
+  const auto now(base::TimeTicks::Now());
+
+  // Throttle the calls in the interval (t - 1 day, t) to limit the effect of
+  // unset clocks or clock drift.
+  return throttle_updates_until_ - base::TimeDelta::FromDays(1) < now &&
+         now < throttle_updates_until_;
+}
+
+void UpdateEngine::SendUninstallPing(const std::string& id,
+                                     const base::Version& version,
+                                     int reason,
+                                     Callback callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  const auto update_context = base::MakeRefCounted<UpdateContext>(
+      config_, false, std::vector<std::string>{id},
+      UpdateClient::CrxDataCallback(), UpdateEngine::NotifyObserversCallback(),
+      std::move(callback), nullptr);
+  DCHECK(!update_context->session_id.empty());
+
+  const auto result = update_contexts_.insert(
+      std::make_pair(update_context->session_id, update_context));
+  DCHECK(result.second);
+
+  DCHECK(update_context);
+  DCHECK_EQ(1u, update_context->ids.size());
+  DCHECK_EQ(1u, update_context->components.count(id));
+  const auto& component = update_context->components.at(id);
+
+  component->Uninstall(version, reason);
+
+  update_context->component_queue.push(id);
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
+                                base::Unretained(this), update_context));
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/update_engine.h b/src/components/update_client/update_engine.h
new file mode 100644
index 0000000..9601873
--- /dev/null
+++ b/src/components/update_client/update_engine.h
@@ -0,0 +1,201 @@
+// Copyright 2015 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 COMPONENTS_UPDATE_CLIENT_UPDATE_ENGINE_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_ENGINE_H_
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/queue.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/update_client/component.h"
+#include "components/update_client/crx_downloader.h"
+#include "components/update_client/crx_update_item.h"
+#include "components/update_client/ping_manager.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client.h"
+
+namespace base {
+class TimeTicks;
+}  // namespace base
+
+namespace update_client {
+
+class Configurator;
+struct UpdateContext;
+
+// Handles updates for a group of components. Updates for different groups
+// are run concurrently but within the same group of components, updates are
+// applied one at a time.
+class UpdateEngine : public base::RefCounted<UpdateEngine> {
+ public:
+  using Callback = base::OnceCallback<void(Error error)>;
+  using NotifyObserversCallback =
+      base::Callback<void(UpdateClient::Observer::Events event,
+                          const std::string& id)>;
+  using CrxDataCallback = UpdateClient::CrxDataCallback;
+
+  UpdateEngine(scoped_refptr<Configurator> config,
+               UpdateChecker::Factory update_checker_factory,
+               CrxDownloader::Factory crx_downloader_factory,
+               scoped_refptr<PingManager> ping_manager,
+               const NotifyObserversCallback& notify_observers_callback);
+
+  // Returns true and the state of the component identified by |id|, if the
+  // component is found in any update context. Returns false if the component
+  // is not found.
+  bool GetUpdateState(const std::string& id, CrxUpdateItem* update_state);
+
+  void Update(bool is_foreground,
+              const std::vector<std::string>& ids,
+              UpdateClient::CrxDataCallback crx_data_callback,
+              Callback update_callback);
+
+  void SendUninstallPing(const std::string& id,
+                         const base::Version& version,
+                         int reason,
+                         Callback update_callback);
+
+ private:
+  friend class base::RefCounted<UpdateEngine>;
+  ~UpdateEngine();
+
+  using UpdateContexts = std::map<std::string, scoped_refptr<UpdateContext>>;
+
+  void UpdateComplete(scoped_refptr<UpdateContext> update_context, Error error);
+
+  void ComponentCheckingForUpdatesStart(
+      scoped_refptr<UpdateContext> update_context,
+      const std::string& id);
+  void ComponentCheckingForUpdatesComplete(
+      scoped_refptr<UpdateContext> update_context);
+  void UpdateCheckComplete(scoped_refptr<UpdateContext> update_context);
+
+  void DoUpdateCheck(scoped_refptr<UpdateContext> update_context);
+  void UpdateCheckResultsAvailable(
+      scoped_refptr<UpdateContext> update_context,
+      const base::Optional<ProtocolParser::Results>& results,
+      ErrorCategory error_category,
+      int error,
+      int retry_after_sec);
+
+  void HandleComponent(scoped_refptr<UpdateContext> update_context);
+  void HandleComponentComplete(scoped_refptr<UpdateContext> update_context);
+
+  // Returns true if the update engine rejects this update call because it
+  // occurs too soon.
+  bool IsThrottled(bool is_foreground) const;
+
+  base::ThreadChecker thread_checker_;
+  scoped_refptr<Configurator> config_;
+  UpdateChecker::Factory update_checker_factory_;
+  CrxDownloader::Factory crx_downloader_factory_;
+  scoped_refptr<PingManager> ping_manager_;
+  std::unique_ptr<PersistedData> metadata_;
+
+  // Called when CRX state changes occur.
+  const NotifyObserversCallback notify_observers_callback_;
+
+  // Contains the contexts associated with each update in progress.
+  UpdateContexts update_contexts_;
+
+  // Implements a rate limiting mechanism for background update checks. Has the
+  // effect of rejecting the update call if the update call occurs before
+  // a certain time, which is negotiated with the server as part of the
+  // update protocol. See the comments for X-Retry-After header.
+  base::TimeTicks throttle_updates_until_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateEngine);
+};
+
+// Describes a group of components which are installed or updated together.
+struct UpdateContext : public base::RefCounted<UpdateContext> {
+  UpdateContext(
+      scoped_refptr<Configurator> config,
+      bool is_foreground,
+      const std::vector<std::string>& ids,
+      UpdateClient::CrxDataCallback crx_data_callback,
+      const UpdateEngine::NotifyObserversCallback& notify_observers_callback,
+      UpdateEngine::Callback callback,
+      CrxDownloader::Factory crx_downloader_factory);
+
+  scoped_refptr<Configurator> config;
+
+  // True if the component is updated as a result of user interaction.
+  bool is_foreground = false;
+
+  // True if the component updates are enabled in this context.
+  const bool enabled_component_updates;
+
+  // Contains the ids of all CRXs in this context in the order specified
+  // by the caller of |UpdateClient::Update| or |UpdateClient:Install|.
+  const std::vector<std::string> ids;
+
+  // Contains the map of ids to components for all the CRX in this context.
+  IdToComponentPtrMap components;
+
+  // Called before an update check, when update metadata is needed.
+  UpdateEngine::CrxDataCallback crx_data_callback;
+
+  // Called when there is a state change for any update in this context.
+  const UpdateEngine::NotifyObserversCallback notify_observers_callback;
+
+  // Called when the all updates associated with this context have completed.
+  UpdateEngine::Callback callback;
+
+  // Creates instances of CrxDownloader;
+  CrxDownloader::Factory crx_downloader_factory;
+
+  std::unique_ptr<UpdateChecker> update_checker;
+
+  // The time in seconds to wait until doing further update checks.
+  int retry_after_sec = 0;
+
+  // Contains the ids of the components to check for updates. It is possible
+  // for a component to be uninstalled after it has been added in this context
+  // but before an update check is made. When this happens, the component won't
+  // have a CrxComponent instance, therefore, it can't be included in an
+  // update check.
+  std::vector<std::string> components_to_check_for_updates;
+
+  // The error reported by the update checker.
+  int update_check_error = 0;
+
+  size_t num_components_ready_to_check = 0;
+  size_t num_components_checked = 0;
+
+  // Contains the ids of the components that the state machine must handle.
+  base::queue<std::string> component_queue;
+
+  // The time to wait before handling the update for a component.
+  // The wait time is proportional with the cost incurred by updating
+  // the component. The more time it takes to download and apply the
+  // update for the current component, the longer the wait until the engine
+  // is handling the next component in the queue.
+  base::TimeDelta next_update_delay;
+
+  // The unique session id of this context. The session id is serialized in
+  // every protocol request. It is also used as a key in various data stuctures
+  // to uniquely identify an update context.
+  const std::string session_id;
+
+ private:
+  friend class base::RefCounted<UpdateContext>;
+  ~UpdateContext();
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateContext);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_UPDATE_ENGINE_H_
diff --git a/src/components/update_client/update_query_params.cc b/src/components/update_client/update_query_params.cc
new file mode 100644
index 0000000..f921da1
--- /dev/null
+++ b/src/components/update_client/update_query_params.cc
@@ -0,0 +1,160 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/update_query_params.h"
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "build/build_config.h"
+#include "components/update_client/update_query_params_delegate.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace update_client {
+
+namespace {
+
+const char kUnknown[] = "unknown";
+
+// The request extra information is the OS and architecture, this helps
+// the server select the right package to be delivered.
+const char kOs[] =
+#if defined(OS_MACOSX)
+    "mac";
+#elif defined(OS_WIN)
+    "win";
+#elif defined(OS_ANDROID)
+    "android";
+#elif defined(OS_CHROMEOS)
+    "cros";
+#elif defined(OS_LINUX)
+    "linux";
+#elif defined(OS_FUCHSIA)
+    "fuchsia";
+#elif defined(OS_OPENBSD)
+    "openbsd";
+#elif defined(OS_STARBOARD)
+    "starboard";
+#else
+#error "unknown os"
+#endif
+
+const char kArch[] =
+#if defined(__amd64__) || defined(_WIN64)
+    "x64";
+#elif defined(__i386__) || defined(_WIN32)
+    "x86";
+#elif defined(__arm__)
+    "arm";
+#elif defined(__aarch64__)
+    "arm64";
+#elif defined(__mips__) && (__mips == 64)
+    "mips64el";
+#elif defined(__mips__)
+    "mipsel";
+#elif defined(__powerpc64__)
+    "ppc64";
+#else
+#error "unknown arch"
+#endif
+
+const char kChrome[] = "chrome";
+
+#if defined(GOOGLE_CHROME_BUILD)
+const char kChromeCrx[] = "chromecrx";
+#else
+const char kChromiumCrx[] = "chromiumcrx";
+#endif  // defined(GOOGLE_CHROME_BUILD)
+
+UpdateQueryParamsDelegate* g_delegate = nullptr;
+
+}  // namespace
+
+// static
+std::string UpdateQueryParams::Get(ProdId prod) {
+  return base::StringPrintf(
+      "os=%s&arch=%s&os_arch=%s&nacl_arch=%s&prod=%s%s&acceptformat=crx2,crx3",
+      kOs, kArch,
+#if !defined(OS_STARBOARD)
+      base::SysInfo().OperatingSystemArchitecture().c_str(),
+#else
+      "",
+#endif
+      GetNaclArch(), GetProdIdString(prod),
+      g_delegate ? g_delegate->GetExtraParams().c_str() : "");
+}
+
+// static
+const char* UpdateQueryParams::GetProdIdString(UpdateQueryParams::ProdId prod) {
+  switch (prod) {
+    case UpdateQueryParams::CHROME:
+      return kChrome;
+      break;
+    case UpdateQueryParams::CRX:
+#if defined(GOOGLE_CHROME_BUILD)
+      return kChromeCrx;
+#else
+      return kChromiumCrx;
+#endif
+      break;
+  }
+  return kUnknown;
+}
+
+// static
+const char* UpdateQueryParams::GetOS() {
+  return kOs;
+}
+
+// static
+const char* UpdateQueryParams::GetArch() {
+  return kArch;
+}
+
+// static
+const char* UpdateQueryParams::GetNaclArch() {
+#if defined(ARCH_CPU_X86_FAMILY)
+#if defined(ARCH_CPU_X86_64)
+  return "x86-64";
+#elif defined(OS_WIN)
+  bool x86_64 = (base::win::OSInfo::GetInstance()->wow64_status() ==
+                 base::win::OSInfo::WOW64_ENABLED);
+  return x86_64 ? "x86-64" : "x86-32";
+#else
+  return "x86-32";
+#endif
+#elif defined(ARCH_CPU_ARMEL)
+  return "arm";
+#elif defined(ARCH_CPU_ARM64)
+  return "arm64";
+#elif defined(ARCH_CPU_MIPSEL)
+  return "mips32";
+#elif defined(ARCH_CPU_MIPS64EL)
+  return "mips64";
+#elif defined(ARCH_CPU_PPC64)
+  return "ppc64";
+#else
+// NOTE: when adding new values here, please remember to update the
+// comment in the .h file about possible return values from this function.
+#error "You need to add support for your architecture here"
+#endif
+}
+
+// static
+std::string UpdateQueryParams::GetProdVersion() {
+  // TODO: fill in prod versoin number
+  // return version_info::GetVersionNumber();
+  return "0.0.1";
+}
+
+// static
+void UpdateQueryParams::SetDelegate(UpdateQueryParamsDelegate* delegate) {
+  DCHECK(!g_delegate || !delegate || (delegate == g_delegate));
+  g_delegate = delegate;
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/update_query_params.h b/src/components/update_client/update_query_params.h
new file mode 100644
index 0000000..e315e14
--- /dev/null
+++ b/src/components/update_client/update_query_params.h
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace update_client {
+
+class UpdateQueryParamsDelegate;
+
+// Generates a string of URL query parameters to be used when getting
+// component and extension updates. These parameters generally remain
+// fixed for a particular build. Embedders can use the delegate to
+// define different implementations. This should be used only in the
+// browser process.
+class UpdateQueryParams {
+ public:
+  enum ProdId {
+    CHROME = 0,
+    CRX,
+  };
+
+  // Generates a string of URL query parameters for Omaha. Includes the
+  // following fields: "os", "arch", "nacl_arch", "prod", "prodchannel",
+  // "prodversion", and "lang"
+  static std::string Get(ProdId prod);
+
+  // Returns the value we use for the "prod=" parameter. Possible return values
+  // include "chrome", "chromecrx", "chromiumcrx", and "unknown".
+  static const char* GetProdIdString(ProdId prod);
+
+  // Returns the value we use for the "os=" parameter. Possible return values
+  // include: "mac", "win", "android", "cros", "linux", and "openbsd".
+  static const char* GetOS();
+
+  // Returns the value we use for the "arch=" parameter. Possible return values
+  // include: "x86", "x64", and "arm".
+  static const char* GetArch();
+
+  // Returns the value we use for the "nacl_arch" parameter. Note that this may
+  // be different from the "arch" parameter above (e.g. one may be 32-bit and
+  // the other 64-bit). Possible return values include: "x86-32", "x86-64",
+  // "arm", "mips32", and "ppc64".
+  static const char* GetNaclArch();
+
+  // Returns the current version of Chrome/Chromium.
+  static std::string GetProdVersion();
+
+  // Use this delegate.
+  static void SetDelegate(UpdateQueryParamsDelegate* delegate);
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(UpdateQueryParams);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_H_
diff --git a/src/components/update_client/update_query_params_delegate.cc b/src/components/update_client/update_query_params_delegate.cc
new file mode 100644
index 0000000..458ae00
--- /dev/null
+++ b/src/components/update_client/update_query_params_delegate.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/update_query_params_delegate.h"
+
+namespace update_client {
+
+UpdateQueryParamsDelegate::UpdateQueryParamsDelegate() {}
+
+UpdateQueryParamsDelegate::~UpdateQueryParamsDelegate() {}
+
+}  // namespace update_client
diff --git a/src/components/update_client/update_query_params_delegate.h b/src/components/update_client/update_query_params_delegate.h
new file mode 100644
index 0000000..6230e2e
--- /dev/null
+++ b/src/components/update_client/update_query_params_delegate.h
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_DELEGATE_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_DELEGATE_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace update_client {
+
+// Embedders can specify an UpdateQueryParamsDelegate to provide additional
+// custom parameters. If not specified (Set is never called), no additional
+// parameters are added.
+class UpdateQueryParamsDelegate {
+ public:
+  UpdateQueryParamsDelegate();
+  virtual ~UpdateQueryParamsDelegate();
+
+  // Returns additional parameters, if any. If there are any parameters, the
+  // string should begin with a & character.
+  virtual std::string GetExtraParams() = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UpdateQueryParamsDelegate);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_DELEGATE_H_
diff --git a/src/components/update_client/update_query_params_unittest.cc b/src/components/update_client/update_query_params_unittest.cc
new file mode 100644
index 0000000..0e5701a
--- /dev/null
+++ b/src/components/update_client/update_query_params_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/update_query_params.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "components/update_client/update_query_params_delegate.h"
+#include "components/version_info/version_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPrintf;
+
+namespace update_client {
+
+namespace {
+
+bool Contains(const std::string& source, const std::string& target) {
+  return source.find(target) != std::string::npos;
+}
+
+class TestUpdateQueryParamsDelegate : public UpdateQueryParamsDelegate {
+  std::string GetExtraParams() override { return "&cat=dog"; }
+};
+
+}  // namespace
+
+void TestParams(UpdateQueryParams::ProdId prod_id, bool extra_params) {
+  std::string params = UpdateQueryParams::Get(prod_id);
+
+  // This doesn't so much test what the values are (since that would be an
+  // almost exact duplication of code with update_query_params.cc, and wouldn't
+  // really test anything) as it is a verification that all the params are
+  // present in the generated string.
+  EXPECT_TRUE(
+      Contains(params, StringPrintf("os=%s", UpdateQueryParams::GetOS())));
+  EXPECT_TRUE(
+      Contains(params, StringPrintf("arch=%s", UpdateQueryParams::GetArch())));
+  EXPECT_TRUE(Contains(
+      params,
+      StringPrintf("os_arch=%s",
+                   base::SysInfo().OperatingSystemArchitecture().c_str())));
+  EXPECT_TRUE(Contains(
+      params,
+      StringPrintf("prod=%s", UpdateQueryParams::GetProdIdString(prod_id))));
+  if (extra_params)
+    EXPECT_TRUE(Contains(params, "cat=dog"));
+}
+
+void TestProdVersion() {
+  EXPECT_EQ(version_info::GetVersionNumber(),
+            UpdateQueryParams::GetProdVersion());
+}
+
+TEST(UpdateQueryParamsTest, GetParams) {
+  TestProdVersion();
+
+  TestParams(UpdateQueryParams::CRX, false);
+  TestParams(UpdateQueryParams::CHROME, false);
+
+  TestUpdateQueryParamsDelegate delegate;
+  UpdateQueryParams::SetDelegate(&delegate);
+
+  TestParams(UpdateQueryParams::CRX, true);
+  TestParams(UpdateQueryParams::CHROME, true);
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/updater_state.cc b/src/components/update_client/updater_state.cc
new file mode 100644
index 0000000..89df8cd
--- /dev/null
+++ b/src/components/update_client/updater_state.cc
@@ -0,0 +1,103 @@
+
+// Copyright (c) 2016 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 "components/update_client/updater_state.h"
+
+#include <utility>
+
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
+#include "base/enterprise_util.h"
+#endif  // OS_WIN or Mac
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+
+namespace update_client {
+
+// The value of this constant does not reflect its name (i.e. "domainjoined"
+// vs something like "isenterprisemanaged") because it is used with omaha.
+// After discussion with omaha team it was decided to leave the value as is to
+// keep continuity with previous chrome versions.
+const char UpdaterState::kIsEnterpriseManaged[] = "domainjoined";
+
+UpdaterState::UpdaterState(bool is_machine) : is_machine_(is_machine) {}
+
+UpdaterState::~UpdaterState() {}
+
+std::unique_ptr<UpdaterState::Attributes> UpdaterState::GetState(
+    bool is_machine) {
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
+  UpdaterState updater_state(is_machine);
+  updater_state.ReadState();
+  return std::make_unique<Attributes>(updater_state.BuildAttributes());
+#else
+  return nullptr;
+#endif  // OS_WIN or Mac
+}
+
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
+void UpdaterState::ReadState() {
+  is_enterprise_managed_ = base::IsMachineExternallyManaged();
+
+#if defined(GOOGLE_CHROME_BUILD)
+  updater_name_ = GetUpdaterName();
+  updater_version_ = GetUpdaterVersion(is_machine_);
+  last_autoupdate_started_ = GetUpdaterLastStartedAU(is_machine_);
+  last_checked_ = GetUpdaterLastChecked(is_machine_);
+  is_autoupdate_check_enabled_ = IsAutoupdateCheckEnabled();
+  update_policy_ = GetUpdatePolicy();
+#endif  // GOOGLE_CHROME_BUILD
+}
+#endif  // OS_WIN or Mac
+
+UpdaterState::Attributes UpdaterState::BuildAttributes() const {
+  Attributes attributes;
+
+#if defined(OS_WIN)
+  // Only Windows implements this attribute in a meaningful way.
+  attributes["ismachine"] = is_machine_ ? "1" : "0";
+#endif  // OS_WIN
+  attributes[kIsEnterpriseManaged] = is_enterprise_managed_ ? "1" : "0";
+
+  attributes["name"] = updater_name_;
+
+  if (updater_version_.IsValid())
+    attributes["version"] = updater_version_.GetString();
+
+  const base::Time now = base::Time::NowFromSystemTime();
+  if (!last_autoupdate_started_.is_null())
+    attributes["laststarted"] =
+        NormalizeTimeDelta(now - last_autoupdate_started_);
+  if (!last_checked_.is_null())
+    attributes["lastchecked"] = NormalizeTimeDelta(now - last_checked_);
+
+  attributes["autoupdatecheckenabled"] =
+      is_autoupdate_check_enabled_ ? "1" : "0";
+
+  DCHECK((update_policy_ >= 0 && update_policy_ <= 3) || update_policy_ == -1);
+  attributes["updatepolicy"] = base::NumberToString(update_policy_);
+
+  return attributes;
+}
+
+std::string UpdaterState::NormalizeTimeDelta(const base::TimeDelta& delta) {
+  const base::TimeDelta two_weeks = base::TimeDelta::FromDays(14);
+  const base::TimeDelta two_months = base::TimeDelta::FromDays(60);
+
+  std::string val;  // Contains the value to return in hours.
+  if (delta <= two_weeks) {
+    val = "0";
+  } else if (two_weeks < delta && delta <= two_months) {
+    val = "408";  // 2 weeks in hours.
+  } else {
+    val = "1344";  // 2*28 days in hours.
+  }
+
+  DCHECK(!val.empty());
+  return val;
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/updater_state.h b/src/components/update_client/updater_state.h
new file mode 100644
index 0000000..ec11d4a
--- /dev/null
+++ b/src/components/update_client/updater_state.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2016 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 COMPONENTS_UPDATE_CLIENT_UPDATER_STATE_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATER_STATE_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/time/time.h"
+#include "base/version.h"
+
+namespace update_client {
+
+class UpdaterState {
+ public:
+  using Attributes = std::map<std::string, std::string>;
+
+  static const char kIsEnterpriseManaged[];
+
+  // Returns a map of items representing the state of an updater.
+  // If |is_machine| is true, this indicates that the updater state corresponds
+  // to the machine instance of the updater. Returns nullptr on
+  // the platforms and builds where this feature is not supported.
+  static std::unique_ptr<Attributes> GetState(bool is_machine);
+
+  ~UpdaterState();
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(UpdaterStateTest, Serialize);
+
+  explicit UpdaterState(bool is_machine);
+
+  // This function is best-effort. It updates the class members with
+  // the relevant values that could be retrieved.
+  void ReadState();
+
+  // Builds the map of state attributes by serializing this object state.
+  Attributes BuildAttributes() const;
+
+  static std::string GetUpdaterName();
+  static base::Version GetUpdaterVersion(bool is_machine);
+  static bool IsAutoupdateCheckEnabled();
+  static base::Time GetUpdaterLastStartedAU(bool is_machine);
+  static base::Time GetUpdaterLastChecked(bool is_machine);
+
+  static int GetUpdatePolicy();
+
+  static std::string NormalizeTimeDelta(const base::TimeDelta& delta);
+
+  // True if the Omaha updater is installed per-machine.
+  // The MacOS implementation ignores the value of this member but this may
+  // change in the future.
+  bool is_machine_;
+  std::string updater_name_;
+  base::Version updater_version_;
+  base::Time last_autoupdate_started_;
+  base::Time last_checked_;
+  bool is_enterprise_managed_ = false;
+  bool is_autoupdate_check_enabled_ = false;
+  int update_policy_ = 0;
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_UPDATER_STATE_H_
diff --git a/src/components/update_client/updater_state_mac.mm b/src/components/update_client/updater_state_mac.mm
new file mode 100644
index 0000000..28c8c8b
--- /dev/null
+++ b/src/components/update_client/updater_state_mac.mm
@@ -0,0 +1,117 @@
+// 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 <Foundation/Foundation.h>
+
+#include "base/enterprise_util.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/version.h"
+#include "components/update_client/updater_state.h"
+
+namespace update_client {
+
+namespace {
+
+const base::FilePath::CharType kKeystonePlist[] = FILE_PATH_LITERAL(
+    "Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/"
+    "Contents/Info.plist");
+
+// Gets a value from the updater settings. Returns a retained object.
+// T should be a toll-free Foundation framework type. See Apple's
+// documentation for toll-free bridging.
+template <class T>
+base::scoped_nsobject<T> GetUpdaterSettingsValue(NSString* value_name) {
+  CFStringRef app_id = CFSTR("com.google.Keystone.Agent");
+  base::ScopedCFTypeRef<CFPropertyListRef> plist(
+      CFPreferencesCopyAppValue(base::mac::NSToCFCast(value_name), app_id));
+  return base::scoped_nsobject<T>(
+      base::mac::ObjCCastStrict<T>(static_cast<id>(plist.get())),
+      base::scoped_policy::RETAIN);
+}
+
+base::Time GetUpdaterSettingsTime(NSString* value_name) {
+  base::scoped_nsobject<NSDate> date =
+      GetUpdaterSettingsValue<NSDate>(value_name);
+  base::Time result =
+      base::Time::FromCFAbsoluteTime([date timeIntervalSinceReferenceDate]);
+
+  return result;
+}
+
+base::Version GetVersionFromPlist(const base::FilePath& info_plist) {
+  base::mac::ScopedNSAutoreleasePool scoped_pool;
+  NSData* data =
+      [NSData dataWithContentsOfFile:base::mac::FilePathToNSString(info_plist)];
+  if ([data length] == 0) {
+    return base::Version();
+  }
+  NSDictionary* all_keys = base::mac::ObjCCastStrict<NSDictionary>(
+      [NSPropertyListSerialization propertyListWithData:data
+                                                options:NSPropertyListImmutable
+                                                 format:nil
+                                                  error:nil]);
+  if (all_keys == nil) {
+    return base::Version();
+  }
+  CFStringRef version = base::mac::GetValueFromDictionary<CFStringRef>(
+      base::mac::NSToCFCast(all_keys), kCFBundleVersionKey);
+  if (version == NULL) {
+    return base::Version();
+  }
+  return base::Version(base::SysCFStringRefToUTF8(version));
+}
+
+}  // namespace
+
+std::string UpdaterState::GetUpdaterName() {
+  return std::string("Keystone");
+}
+
+base::Version UpdaterState::GetUpdaterVersion(bool /*is_machine*/) {
+  // System Keystone trumps user one, so check this one first
+  base::FilePath local_library;
+  bool success =
+      base::mac::GetLocalDirectory(NSLibraryDirectory, &local_library);
+  DCHECK(success);
+  base::FilePath system_bundle_plist = local_library.Append(kKeystonePlist);
+  base::Version system_keystone = GetVersionFromPlist(system_bundle_plist);
+  if (system_keystone.IsValid()) {
+    return system_keystone;
+  }
+
+  base::FilePath user_bundle_plist =
+      base::mac::GetUserLibraryPath().Append(kKeystonePlist);
+  return GetVersionFromPlist(user_bundle_plist);
+}
+
+base::Time UpdaterState::GetUpdaterLastStartedAU(bool /*is_machine*/) {
+  return GetUpdaterSettingsTime(@"lastCheckStartDate");
+}
+
+base::Time UpdaterState::GetUpdaterLastChecked(bool /*is_machine*/) {
+  return GetUpdaterSettingsTime(@"lastServerCheckDate");
+}
+
+bool UpdaterState::IsAutoupdateCheckEnabled() {
+  // Auto-update check period override (in seconds).
+  // Applies only to older versions of Keystone.
+  base::scoped_nsobject<NSNumber> timeInterval =
+      GetUpdaterSettingsValue<NSNumber>(@"checkInterval");
+  if (!timeInterval.get())
+    return true;
+  int value = [timeInterval intValue];
+
+  return 0 < value && value < (24 * 60 * 60);
+}
+
+int UpdaterState::GetUpdatePolicy() {
+  return -1;  // Keystone does not support update policies.
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/updater_state_unittest.cc b/src/components/update_client/updater_state_unittest.cc
new file mode 100644
index 0000000..81c9964
--- /dev/null
+++ b/src/components/update_client/updater_state_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright 2016 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 "components/update_client/updater_state.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "base/version.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+class UpdaterStateTest : public testing::Test {
+ public:
+  UpdaterStateTest() {}
+  ~UpdaterStateTest() override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UpdaterStateTest);
+};
+
+TEST_F(UpdaterStateTest, Serialize) {
+  UpdaterState updater_state(false);
+
+  updater_state.updater_name_ = "the updater";
+  updater_state.updater_version_ = base::Version("1.0");
+  updater_state.last_autoupdate_started_ = base::Time::NowFromSystemTime();
+  updater_state.last_checked_ = base::Time::NowFromSystemTime();
+  updater_state.is_enterprise_managed_ = false;
+  updater_state.is_autoupdate_check_enabled_ = true;
+  updater_state.update_policy_ = 1;
+
+  auto attributes = updater_state.BuildAttributes();
+
+  // Sanity check all members.
+  EXPECT_STREQ("the updater", attributes.at("name").c_str());
+  EXPECT_STREQ("1.0", attributes.at("version").c_str());
+  EXPECT_STREQ("0", attributes.at("laststarted").c_str());
+  EXPECT_STREQ("0", attributes.at("lastchecked").c_str());
+  EXPECT_STREQ("0", attributes.at("domainjoined").c_str());
+  EXPECT_STREQ("1", attributes.at("autoupdatecheckenabled").c_str());
+  EXPECT_STREQ("1", attributes.at("updatepolicy").c_str());
+
+#if defined(GOOGLE_CHROME_BUILD)
+#if defined(OS_WIN)
+  // The value of "ismachine".
+  EXPECT_STREQ("0", UpdaterState::GetState(false)->at("ismachine").c_str());
+  EXPECT_STREQ("1", UpdaterState::GetState(true)->at("ismachine").c_str());
+
+  // The name of the Windows updater for Chrome.
+  EXPECT_STREQ("Omaha", UpdaterState::GetState(false)->at("name").c_str());
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+  // MacOS does not serialize "ismachine".
+  EXPECT_EQ(0UL, UpdaterState::GetState(false)->count("ismachine"));
+  EXPECT_EQ(0UL, UpdaterState::GetState(true)->count("ismachine"));
+  EXPECT_STREQ("Keystone", UpdaterState::GetState(false)->at("name").c_str());
+#endif  // OS_WIN
+#endif  // GOOGLE_CHROME_BUILD
+
+  // Tests some of the remaining values.
+  updater_state = UpdaterState(false);
+
+  // Don't serialize an invalid version if it could not be read.
+  updater_state.updater_version_ = base::Version();
+  attributes = updater_state.BuildAttributes();
+  EXPECT_EQ(0u, attributes.count("version"));
+
+  updater_state.updater_version_ = base::Version("0.0.0.0");
+  attributes = updater_state.BuildAttributes();
+  EXPECT_STREQ("0.0.0.0", attributes.at("version").c_str());
+
+  updater_state.last_autoupdate_started_ =
+      base::Time::NowFromSystemTime() - base::TimeDelta::FromDays(15);
+  attributes = updater_state.BuildAttributes();
+  EXPECT_STREQ("408", attributes.at("laststarted").c_str());
+
+  updater_state.last_autoupdate_started_ =
+      base::Time::NowFromSystemTime() - base::TimeDelta::FromDays(90);
+  attributes = updater_state.BuildAttributes();
+  EXPECT_STREQ("1344", attributes.at("laststarted").c_str());
+
+  // Don't serialize the time if it could not be read.
+  updater_state.last_autoupdate_started_ = base::Time();
+  attributes = updater_state.BuildAttributes();
+  EXPECT_EQ(0u, attributes.count("laststarted"));
+
+  updater_state.last_checked_ =
+      base::Time::NowFromSystemTime() - base::TimeDelta::FromDays(15);
+  attributes = updater_state.BuildAttributes();
+  EXPECT_STREQ("408", attributes.at("lastchecked").c_str());
+
+  updater_state.last_checked_ =
+      base::Time::NowFromSystemTime() - base::TimeDelta::FromDays(90);
+  attributes = updater_state.BuildAttributes();
+  EXPECT_STREQ("1344", attributes.at("lastchecked").c_str());
+
+  // Don't serialize the time if it could not be read (the value is invalid).
+  updater_state.last_checked_ = base::Time();
+  attributes = updater_state.BuildAttributes();
+  EXPECT_EQ(0u, attributes.count("lastchecked"));
+
+  updater_state.is_enterprise_managed_ = true;
+  attributes = updater_state.BuildAttributes();
+  EXPECT_STREQ("1", attributes.at("domainjoined").c_str());
+
+  updater_state.is_autoupdate_check_enabled_ = false;
+  attributes = updater_state.BuildAttributes();
+  EXPECT_STREQ("0", attributes.at("autoupdatecheckenabled").c_str());
+
+  updater_state.update_policy_ = 0;
+  attributes = updater_state.BuildAttributes();
+  EXPECT_STREQ("0", attributes.at("updatepolicy").c_str());
+
+  updater_state.update_policy_ = -1;
+  attributes = updater_state.BuildAttributes();
+  EXPECT_STREQ("-1", attributes.at("updatepolicy").c_str());
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/updater_state_win.cc b/src/components/update_client/updater_state_win.cc
new file mode 100644
index 0000000..89c2c18
--- /dev/null
+++ b/src/components/update_client/updater_state_win.cc
@@ -0,0 +1,136 @@
+// Copyright 2016 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 "components/update_client/updater_state.h"
+
+#include <windows.h>
+
+#include <string>
+#include <utility>
+
+#include "base/enterprise_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/registry.h"
+#include "base/win/win_util.h"
+
+// TODO(sorin): implement this in terms of
+// chrome/installer/util/google_update_settings (crbug.com/615187).
+
+namespace update_client {
+
+namespace {
+
+// Google Update group policy settings.
+const wchar_t kGoogleUpdatePoliciesKey[] =
+    L"SOFTWARE\\Policies\\Google\\Update";
+const wchar_t kCheckPeriodOverrideMinutes[] = L"AutoUpdateCheckPeriodMinutes";
+const wchar_t kUpdatePolicyValue[] = L"UpdateDefault";
+const wchar_t kChromeUpdatePolicyOverride[] =
+    L"Update{8A69D345-D564-463C-AFF1-A69D9E530F96}";
+
+// Don't allow update periods longer than six weeks (Chrome release cadence).
+const int kCheckPeriodOverrideMinutesMax = 60 * 24 * 7 * 6;
+
+// Google Update registry settings.
+const wchar_t kRegPathGoogleUpdate[] = L"Software\\Google\\Update";
+const wchar_t kRegPathClientsGoogleUpdate[] =
+    L"Software\\Google\\Update\\Clients\\"
+    L"{430FD4D0-B729-4F61-AA34-91526481799D}";
+const wchar_t kRegValueGoogleUpdatePv[] = L"pv";
+const wchar_t kRegValueLastStartedAU[] = L"LastStartedAU";
+const wchar_t kRegValueLastChecked[] = L"LastChecked";
+
+base::Time GetUpdaterTimeValue(bool is_machine, const wchar_t* value_name) {
+  const HKEY root_key = is_machine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
+  base::win::RegKey update_key;
+
+  if (update_key.Open(root_key, kRegPathGoogleUpdate,
+                      KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
+    DWORD value(0);
+    if (update_key.ReadValueDW(value_name, &value) == ERROR_SUCCESS) {
+      return base::Time::FromTimeT(value);
+    }
+  }
+
+  return base::Time();
+}
+
+}  // namespace
+
+std::string UpdaterState::GetUpdaterName() {
+  return std::string("Omaha");
+}
+
+base::Version UpdaterState::GetUpdaterVersion(bool is_machine) {
+  const HKEY root_key = is_machine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
+  base::string16 version;
+  base::win::RegKey key;
+
+  if (key.Open(root_key, kRegPathClientsGoogleUpdate,
+               KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
+      key.ReadValue(kRegValueGoogleUpdatePv, &version) == ERROR_SUCCESS) {
+    return base::Version(base::UTF16ToUTF8(version));
+  }
+
+  return base::Version();
+}
+
+base::Time UpdaterState::GetUpdaterLastStartedAU(bool is_machine) {
+  return GetUpdaterTimeValue(is_machine, kRegValueLastStartedAU);
+}
+
+base::Time UpdaterState::GetUpdaterLastChecked(bool is_machine) {
+  return GetUpdaterTimeValue(is_machine, kRegValueLastChecked);
+}
+
+bool UpdaterState::IsAutoupdateCheckEnabled() {
+  // Check the auto-update check period override. If it is 0 or exceeds the
+  // maximum timeout, then for all intents and purposes auto updates are
+  // disabled.
+  base::win::RegKey policy_key;
+  DWORD value = 0;
+  if (policy_key.Open(HKEY_LOCAL_MACHINE, kGoogleUpdatePoliciesKey,
+                      KEY_QUERY_VALUE) == ERROR_SUCCESS &&
+      policy_key.ReadValueDW(kCheckPeriodOverrideMinutes, &value) ==
+          ERROR_SUCCESS &&
+      (value == 0 || value > kCheckPeriodOverrideMinutesMax)) {
+    return false;
+  }
+
+  return true;
+}
+
+// Returns -1 if the policy is not found or the value was invalid. Otherwise,
+// returns a value in the [0, 3] range, representing the value of the
+// Chrome update group policy.
+int UpdaterState::GetUpdatePolicy() {
+  const int kMaxUpdatePolicyValue = 3;
+
+  base::win::RegKey policy_key;
+
+  if (policy_key.Open(HKEY_LOCAL_MACHINE, kGoogleUpdatePoliciesKey,
+                      KEY_QUERY_VALUE) != ERROR_SUCCESS) {
+    return -1;
+  }
+
+  DWORD value = 0;
+  // First try to read the Chrome-specific override.
+  if (policy_key.ReadValueDW(kChromeUpdatePolicyOverride, &value) ==
+          ERROR_SUCCESS &&
+      value <= kMaxUpdatePolicyValue) {
+    return value;
+  }
+
+  // Try to read default override.
+  if (policy_key.ReadValueDW(kUpdatePolicyValue, &value) == ERROR_SUCCESS &&
+      value <= kMaxUpdatePolicyValue) {
+    return value;
+  }
+
+  return -1;
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/url_fetcher_downloader.cc b/src/components/update_client/url_fetcher_downloader.cc
new file mode 100644
index 0000000..61530d7
--- /dev/null
+++ b/src/components/update_client/url_fetcher_downloader.cc
@@ -0,0 +1,237 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/url_fetcher_downloader.h"
+
+#include <stdint.h>
+#include <stack>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "components/update_client/network.h"
+#include "components/update_client/utils.h"
+#include "starboard/configuration_constants.h"
+#include "url/gurl.h"
+
+namespace {
+
+#if defined(OS_STARBOARD)
+void CleanupDirectory(base::FilePath& dir) {
+  std::stack<std::string> directories;
+  base::FileEnumerator file_enumerator(
+      dir, true,
+      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+  for (auto path = file_enumerator.Next(); !path.value().empty();
+       path = file_enumerator.Next()) {
+    base::FileEnumerator::FileInfo info(file_enumerator.GetInfo());
+
+    if (info.IsDirectory()) {
+      directories.push(path.value());
+    } else {
+      SbFileDelete(path.value().c_str());
+    }
+  }
+  while (!directories.empty()) {
+    SbFileDelete(directories.top().c_str());
+    directories.pop();
+  }
+}
+#endif
+
+const base::TaskTraits kTaskTraits = {
+    base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+    base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
+
+}  // namespace
+
+namespace update_client {
+
+UrlFetcherDownloader::UrlFetcherDownloader(
+    std::unique_ptr<CrxDownloader> successor,
+    scoped_refptr<NetworkFetcherFactory> network_fetcher_factory)
+    : CrxDownloader(std::move(successor)),
+      network_fetcher_factory_(network_fetcher_factory) {}
+
+UrlFetcherDownloader::~UrlFetcherDownloader() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+void UrlFetcherDownloader::DoStartDownload(const GURL& url) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  base::PostTaskWithTraitsAndReply(
+      FROM_HERE, kTaskTraits,
+      base::BindOnce(&UrlFetcherDownloader::CreateDownloadDir,
+                     base::Unretained(this)),
+      base::BindOnce(&UrlFetcherDownloader::StartURLFetch,
+                     base::Unretained(this), url));
+}
+
+void UrlFetcherDownloader::CreateDownloadDir() {
+#if !defined(OS_STARBOARD)
+  base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_url_fetcher_"),
+                               &download_dir_);
+#else
+  const CobaltExtensionInstallationManagerApi* installation_api =
+      static_cast<const CobaltExtensionInstallationManagerApi*>(
+          SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
+  if (!installation_api) {
+    SB_LOG(ERROR) << "Failed to get installation manager";
+    return;
+  }
+  // Get new installation index.
+  installation_index_ = installation_api->SelectNewInstallationIndex();
+  SB_DLOG(INFO) << "installation_index = " << installation_index_;
+  if (installation_index_ == IM_EXT_ERROR) {
+    SB_LOG(ERROR) << "Failed to get installation index";
+    return;
+  }
+
+  // Get the path to new installation.
+  std::vector<char> installation_path(kSbFileMaxPath);
+  if (installation_api->GetInstallationPath(
+          installation_index_, installation_path.data(),
+          installation_path.size()) == IM_EXT_ERROR) {
+    SB_LOG(ERROR) << "Failed to get installation path";
+    return;
+  }
+
+  SB_DLOG(INFO) << "installation_path = " << installation_path.data();
+  download_dir_ = base::FilePath(
+      std::string(installation_path.begin(), installation_path.end()));
+
+  // Cleanup the download dir.
+  CleanupDirectory(download_dir_);
+#endif
+}
+
+void UrlFetcherDownloader::StartURLFetch(const GURL& url) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (download_dir_.empty()) {
+    Result result;
+    result.error = -1;
+
+    DownloadMetrics download_metrics;
+    download_metrics.url = url;
+    download_metrics.downloader = DownloadMetrics::kUrlFetcher;
+    download_metrics.error = -1;
+    download_metrics.downloaded_bytes = -1;
+    download_metrics.total_bytes = -1;
+    download_metrics.download_time_ms = 0;
+
+    main_task_runner()->PostTask(
+        FROM_HERE, base::BindOnce(&UrlFetcherDownloader::OnDownloadComplete,
+                                  base::Unretained(this), false, result,
+                                  download_metrics));
+    return;
+  }
+
+  const auto file_path = download_dir_.AppendASCII(url.ExtractFileName());
+  network_fetcher_ = network_fetcher_factory_->Create();
+  network_fetcher_->DownloadToFile(
+      url, file_path,
+      base::BindOnce(&UrlFetcherDownloader::OnResponseStarted,
+                     base::Unretained(this)),
+      base::BindRepeating(&UrlFetcherDownloader::OnDownloadProgress,
+                          base::Unretained(this)),
+      base::BindOnce(&UrlFetcherDownloader::OnNetworkFetcherComplete,
+                     base::Unretained(this)));
+
+  download_start_time_ = base::TimeTicks::Now();
+}
+
+void UrlFetcherDownloader::OnNetworkFetcherComplete(base::FilePath file_path,
+                                                    int net_error,
+                                                    int64_t content_size) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  const base::TimeTicks download_end_time(base::TimeTicks::Now());
+  const base::TimeDelta download_time =
+      download_end_time >= download_start_time_
+          ? download_end_time - download_start_time_
+          : base::TimeDelta();
+
+  // Consider a 5xx response from the server as an indication to terminate
+  // the request and avoid overloading the server in this case.
+  // is not accepting requests for the moment.
+  int error = -1;
+  if (!file_path.empty() && response_code_ == 200) {
+    DCHECK_EQ(0, net_error);
+    error = 0;
+  } else if (response_code_ != -1) {
+    error = response_code_;
+  } else {
+    error = net_error;
+  }
+
+  const bool is_handled = error == 0 || IsHttpServerError(error);
+
+  Result result;
+  result.error = error;
+  if (!error) {
+    result.response = file_path;
+#if defined(OS_STARBOARD)
+    result.installation_index = installation_index_;
+#endif
+  }
+
+  DownloadMetrics download_metrics;
+  download_metrics.url = url();
+  download_metrics.downloader = DownloadMetrics::kUrlFetcher;
+  download_metrics.error = error;
+  // Tests expected -1, in case of failures and no content is available.
+  download_metrics.downloaded_bytes = error ? -1 : content_size;
+  download_metrics.total_bytes = total_bytes_;
+  download_metrics.download_time_ms = download_time.InMilliseconds();
+
+  VLOG(1) << "Downloaded " << content_size << " bytes in "
+          << download_time.InMilliseconds() << "ms from " << final_url_.spec()
+          << " to " << result.response.value();
+
+#if !defined(OS_STARBOARD)
+  // Delete the download directory in the error cases.
+  if (error && !download_dir_.empty())
+    base::PostTaskWithTraits(
+        FROM_HERE, kTaskTraits,
+        base::BindOnce(IgnoreResult(&base::DeleteFile), download_dir_, true));
+#else
+  if (error && !download_dir_.empty()) {
+    // Cleanup the download dir.
+    CleanupDirectory(download_dir_);
+  }
+#endif
+
+  main_task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&UrlFetcherDownloader::OnDownloadComplete,
+                                base::Unretained(this), is_handled, result,
+                                download_metrics));
+}
+
+// This callback is used to indicate that a download has been started.
+void UrlFetcherDownloader::OnResponseStarted(const GURL& final_url,
+                                             int response_code,
+                                             int64_t content_length) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  VLOG(1) << "url fetcher response started for: " << final_url.spec();
+
+  final_url_ = final_url;
+  response_code_ = response_code;
+  total_bytes_ = content_length;
+}
+
+void UrlFetcherDownloader::OnDownloadProgress(int64_t current) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  CrxDownloader::OnDownloadProgress();
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/url_fetcher_downloader.h b/src/components/update_client/url_fetcher_downloader.h
new file mode 100644
index 0000000..10b5503
--- /dev/null
+++ b/src/components/update_client/url_fetcher_downloader.h
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_URL_FETCHER_DOWNLOADER_H_
+#define COMPONENTS_UPDATE_CLIENT_URL_FETCHER_DOWNLOADER_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/update_client/crx_downloader.h"
+
+#if defined(OS_STARBOARD)
+#include "cobalt/extension/installation_manager.h"
+#endif
+
+namespace update_client {
+
+class NetworkFetcher;
+class NetworkFetcherFactory;
+
+// Implements a CRX downloader using a NetworkFetcher object.
+class UrlFetcherDownloader : public CrxDownloader {
+ public:
+  UrlFetcherDownloader(
+      std::unique_ptr<CrxDownloader> successor,
+      scoped_refptr<NetworkFetcherFactory> network_fetcher_factory);
+  ~UrlFetcherDownloader() override;
+
+ private:
+  // Overrides for CrxDownloader.
+  void DoStartDownload(const GURL& url) override;
+
+  void CreateDownloadDir();
+  void StartURLFetch(const GURL& url);
+  void OnNetworkFetcherComplete(base::FilePath file_path,
+                                int net_error,
+                                int64_t content_size);
+  void OnResponseStarted(const GURL& final_url,
+                         int response_code,
+                         int64_t content_length);
+  void OnDownloadProgress(int64_t content_length);
+
+  THREAD_CHECKER(thread_checker_);
+
+  scoped_refptr<NetworkFetcherFactory> network_fetcher_factory_;
+  std::unique_ptr<NetworkFetcher> network_fetcher_;
+
+  // Contains a temporary download directory for the downloaded file.
+  base::FilePath download_dir_;
+
+  base::TimeTicks download_start_time_;
+
+  GURL final_url_;
+  int response_code_ = -1;
+  int64_t total_bytes_ = -1;
+
+#if defined(OS_STARBOARD)
+  int installation_index_ = IM_EXT_INVALID_INDEX;
+#endif
+
+  DISALLOW_COPY_AND_ASSIGN(UrlFetcherDownloader);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_URL_FETCHER_DOWNLOADER_H_
diff --git a/src/components/update_client/utils.cc b/src/components/update_client/utils.cc
new file mode 100644
index 0000000..bcedb8a
--- /dev/null
+++ b/src/components/update_client/utils.cc
@@ -0,0 +1,212 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/update_client/utils.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstring>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "components/crx_file/id_util.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/network.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+bool HasDiffUpdate(const Component& component) {
+  return !component.crx_diffurls().empty();
+}
+
+bool IsHttpServerError(int status_code) {
+  return 500 <= status_code && status_code < 600;
+}
+
+bool DeleteFileAndEmptyParentDirectory(const base::FilePath& filepath) {
+  if (!base::DeleteFile(filepath, false))
+    return false;
+
+  const base::FilePath dirname(filepath.DirName());
+  if (!base::IsDirectoryEmpty(dirname))
+    return true;
+
+  return base::DeleteFile(dirname, false);
+}
+
+std::string GetCrxComponentID(const CrxComponent& component) {
+  return component.app_id.empty() ? GetCrxIdFromPublicKeyHash(component.pk_hash)
+                                  : component.app_id;
+}
+
+std::string GetCrxIdFromPublicKeyHash(const std::vector<uint8_t>& pk_hash) {
+  const std::string result =
+      crx_file::id_util::GenerateIdFromHash(&pk_hash[0], pk_hash.size());
+  DCHECK(crx_file::id_util::IdIsValid(result));
+  return result;
+}
+
+#if defined(OS_STARBOARD)
+bool VerifyFileHash256(const base::FilePath& filepath,
+                       const std::string& expected_hash_str) {
+  std::vector<uint8_t> expected_hash;
+  if (!base::HexStringToBytes(expected_hash_str, &expected_hash) ||
+      expected_hash.size() != crypto::kSHA256Length) {
+    return false;
+  }
+
+  base::File source_file(filepath,
+                         base::File::FLAG_OPEN | base::File::FLAG_READ);
+  if (!source_file.IsValid()) {
+    DPLOG(ERROR) << "VerifyFileHash256(): Unable to open source file: "
+                 << filepath.value();
+    return false;
+  }
+
+  const size_t kBufferSize = 32768;
+  std::vector<char> buffer(kBufferSize);
+  uint8_t actual_hash[crypto::kSHA256Length] = {0};
+  std::unique_ptr<crypto::SecureHash> hasher(
+      crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+
+  while (true) {
+    int bytes_read = source_file.ReadAtCurrentPos(&buffer[0], buffer.size());
+    if (bytes_read < 0) {
+      DPLOG(ERROR) << "VerifyFileHash256(): error reading from source file: "
+                   << filepath.value();
+
+      return false;
+    }
+
+    if (bytes_read == 0) {
+      break;
+    }
+
+    hasher->Update(&buffer[0], bytes_read);
+  }
+
+  hasher->Finish(actual_hash, sizeof(actual_hash));
+
+  return memcmp(actual_hash, &expected_hash[0], sizeof(actual_hash)) == 0;
+}
+#else
+bool VerifyFileHash256(const base::FilePath& filepath,
+                       const std::string& expected_hash_str) {
+  std::vector<uint8_t> expected_hash;
+  if (!base::HexStringToBytes(expected_hash_str, &expected_hash) ||
+      expected_hash.size() != crypto::kSHA256Length) {
+    return false;
+  }
+  base::MemoryMappedFile mmfile;
+  if (!mmfile.Initialize(filepath))
+    return false;
+
+  uint8_t actual_hash[crypto::kSHA256Length] = {0};
+  std::unique_ptr<crypto::SecureHash> hasher(
+      crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+  hasher->Update(mmfile.data(), mmfile.length());
+  hasher->Finish(actual_hash, sizeof(actual_hash));
+
+  return memcmp(actual_hash, &expected_hash[0], sizeof(actual_hash)) == 0;
+}
+#endif
+
+bool IsValidBrand(const std::string& brand) {
+  const size_t kMaxBrandSize = 4;
+  if (!brand.empty() && brand.size() != kMaxBrandSize)
+    return false;
+
+  return std::find_if_not(brand.begin(), brand.end(), [](char ch) {
+           return base::IsAsciiAlpha(ch);
+         }) == brand.end();
+}
+
+// Helper function.
+// Returns true if |part| matches the expression
+// ^[<special_chars>a-zA-Z0-9]{min_length,max_length}$
+bool IsValidInstallerAttributePart(const std::string& part,
+                                   const std::string& special_chars,
+                                   size_t min_length,
+                                   size_t max_length) {
+  if (part.size() < min_length || part.size() > max_length)
+    return false;
+
+  return std::find_if_not(part.begin(), part.end(), [&special_chars](char ch) {
+           if (base::IsAsciiAlpha(ch) || base::IsAsciiDigit(ch))
+             return true;
+
+           for (auto c : special_chars) {
+             if (c == ch)
+               return true;
+           }
+
+           return false;
+         }) == part.end();
+}
+
+// Returns true if the |name| parameter matches ^[-_a-zA-Z0-9]{1,256}$ .
+bool IsValidInstallerAttributeName(const std::string& name) {
+  return IsValidInstallerAttributePart(name, "-_", 1, 256);
+}
+
+// Returns true if the |value| parameter matches ^[-.,;+_=a-zA-Z0-9]{0,256}$ .
+bool IsValidInstallerAttributeValue(const std::string& value) {
+  return IsValidInstallerAttributePart(value, "-.,;+_=", 0, 256);
+}
+
+bool IsValidInstallerAttribute(const InstallerAttribute& attr) {
+  return IsValidInstallerAttributeName(attr.first) &&
+         IsValidInstallerAttributeValue(attr.second);
+}
+
+void RemoveUnsecureUrls(std::vector<GURL>* urls) {
+  DCHECK(urls);
+  base::EraseIf(*urls,
+                [](const GURL& url) { return !url.SchemeIsCryptographic(); });
+}
+
+CrxInstaller::Result InstallFunctionWrapper(
+    base::OnceCallback<bool()> callback) {
+  return CrxInstaller::Result(std::move(callback).Run()
+                                  ? InstallError::NONE
+                                  : InstallError::GENERIC_ERROR);
+}
+
+// TODO(cpu): add a specific attribute check to a component json that the
+// extension unpacker will reject, so that a component cannot be installed
+// as an extension.
+std::unique_ptr<base::DictionaryValue> ReadManifest(
+    const base::FilePath& unpack_path) {
+  base::FilePath manifest =
+      unpack_path.Append(FILE_PATH_LITERAL("manifest.json"));
+  if (!base::PathExists(manifest))
+    return std::unique_ptr<base::DictionaryValue>();
+  JSONFileValueDeserializer deserializer(manifest);
+  std::string error;
+  std::unique_ptr<base::Value> root = deserializer.Deserialize(nullptr, &error);
+  if (!root)
+    return std::unique_ptr<base::DictionaryValue>();
+  if (!root->is_dict())
+    return std::unique_ptr<base::DictionaryValue>();
+  return std::unique_ptr<base::DictionaryValue>(
+      static_cast<base::DictionaryValue*>(root.release()));
+}
+
+}  // namespace update_client
diff --git a/src/components/update_client/utils.h b/src/components/update_client/utils.h
new file mode 100644
index 0000000..8beebe8
--- /dev/null
+++ b/src/components/update_client/utils.h
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_UTILS_H_
+#define COMPONENTS_UPDATE_CLIENT_UTILS_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/update_client.h"
+
+class GURL;
+
+namespace base {
+class DictionaryValue;
+class FilePath;
+}  // namespace base
+
+namespace update_client {
+
+class Component;
+struct CrxComponent;
+
+// Defines a name-value pair that represents an installer attribute.
+// Installer attributes are component-specific metadata, which may be serialized
+// in an update check request.
+using InstallerAttribute = std::pair<std::string, std::string>;
+
+// Returns true if the |component| contains a valid differential update url.
+bool HasDiffUpdate(const Component& component);
+
+// Returns true if the |status_code| represents a server error 5xx.
+bool IsHttpServerError(int status_code);
+
+// Deletes the file and its directory, if the directory is empty. If the
+// parent directory is not empty, the function ignores deleting the directory.
+// Returns true if the file and the empty directory are deleted.
+bool DeleteFileAndEmptyParentDirectory(const base::FilePath& filepath);
+
+// Returns the component id of the |component|. The component id is in a
+// format similar with the format of an extension id.
+std::string GetCrxComponentID(const CrxComponent& component);
+
+// Returns a CRX id from a public key hash.
+std::string GetCrxIdFromPublicKeyHash(const std::vector<uint8_t>& pk_hash);
+
+// Returns true if the actual SHA-256 hash of the |filepath| matches the
+// |expected_hash|.
+bool VerifyFileHash256(const base::FilePath& filepath,
+                       const std::string& expected_hash);
+
+// Returns true if the |brand| parameter matches ^[a-zA-Z]{4}?$ .
+bool IsValidBrand(const std::string& brand);
+
+// Returns true if the name part of the |attr| parameter matches
+// ^[-_a-zA-Z0-9]{1,256}$ and the value part of the |attr| parameter
+// matches ^[-.,;+_=a-zA-Z0-9]{0,256}$ .
+bool IsValidInstallerAttribute(const InstallerAttribute& attr);
+
+// Removes the unsecure urls in the |urls| parameter.
+void RemoveUnsecureUrls(std::vector<GURL>* urls);
+
+// Adapter function for the old definitions of CrxInstaller::Install until the
+// component installer code is migrated to use a Result instead of bool.
+CrxInstaller::Result InstallFunctionWrapper(
+    base::OnceCallback<bool()> callback);
+
+// Deserializes the CRX manifest. The top level must be a dictionary.
+std::unique_ptr<base::DictionaryValue> ReadManifest(
+    const base::FilePath& unpack_path);
+
+// Converts a custom, specific installer error (and optionally extended error)
+// to an installer result.
+template <typename T>
+CrxInstaller::Result ToInstallerResult(const T& error, int extended_error = 0) {
+  static_assert(std::is_enum<T>::value,
+                "Use an enum class to define custom installer errors");
+  return CrxInstaller::Result(
+      static_cast<int>(update_client::InstallError::CUSTOM_ERROR_BASE) +
+          static_cast<int>(error),
+      extended_error);
+}
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_UTILS_H_
diff --git a/src/components/update_client/utils_unittest.cc b/src/components/update_client/utils_unittest.cc
new file mode 100644
index 0000000..5045e2a
--- /dev/null
+++ b/src/components/update_client/utils_unittest.cc
@@ -0,0 +1,205 @@
+// Copyright 2016 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 "components/update_client/utils.h"
+
+#include <iterator>
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "components/update_client/updater_state.h"
+#include "nb/cpp14oncpp11.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using std::string;
+
+namespace {
+
+base::FilePath MakeTestFilePath(const char* file) {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  return path.AppendASCII("components/test/data/update_client")
+      .AppendASCII(file);
+}
+
+}  // namespace
+
+namespace update_client {
+
+TEST(UpdateClientUtils, VerifyFileHash256) {
+  EXPECT_TRUE(VerifyFileHash256(
+      MakeTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"),
+      std::string(
+          "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87")));
+
+  EXPECT_FALSE(VerifyFileHash256(
+      MakeTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"),
+      std::string("")));
+
+  EXPECT_FALSE(VerifyFileHash256(
+      MakeTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"),
+      std::string("abcd")));
+
+  EXPECT_FALSE(VerifyFileHash256(
+      MakeTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"),
+      std::string(
+          "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")));
+}
+
+// Tests that the brand matches ^[a-zA-Z]{4}?$
+TEST(UpdateClientUtils, IsValidBrand) {
+  // The valid brand code must be empty or exactly 4 chars long.
+  EXPECT_TRUE(IsValidBrand(std::string("")));
+  EXPECT_TRUE(IsValidBrand(std::string("TEST")));
+  EXPECT_TRUE(IsValidBrand(std::string("test")));
+  EXPECT_TRUE(IsValidBrand(std::string("TEst")));
+
+  EXPECT_FALSE(IsValidBrand(std::string("T")));      // Too short.
+  EXPECT_FALSE(IsValidBrand(std::string("TE")));     //
+  EXPECT_FALSE(IsValidBrand(std::string("TES")));    //
+  EXPECT_FALSE(IsValidBrand(std::string("TESTS")));  // Too long.
+  EXPECT_FALSE(IsValidBrand(std::string("TES1")));   // Has digit.
+  EXPECT_FALSE(IsValidBrand(std::string(" TES")));   // Begins with white space.
+  EXPECT_FALSE(IsValidBrand(std::string("TES ")));   // Ends with white space.
+  EXPECT_FALSE(IsValidBrand(std::string("T ES")));   // Contains white space.
+  EXPECT_FALSE(IsValidBrand(std::string("<TE")));    // Has <.
+  EXPECT_FALSE(IsValidBrand(std::string("TE>")));    // Has >.
+  EXPECT_FALSE(IsValidBrand(std::string("\"")));     // Has "
+  EXPECT_FALSE(IsValidBrand(std::string("\\")));     // Has backslash.
+  EXPECT_FALSE(IsValidBrand(std::string("\xaa")));   // Has non-ASCII char.
+}
+
+TEST(UpdateClientUtils, GetCrxComponentId) {
+  static const uint8_t kHash[16] = {
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+  };
+  CrxComponent component;
+  component.pk_hash.assign(kHash, kHash + sizeof(kHash));
+
+  EXPECT_EQ(std::string("abcdefghijklmnopabcdefghijklmnop"),
+            GetCrxComponentID(component));
+}
+
+TEST(UpdateClientUtils, GetCrxIdFromPublicKeyHash) {
+  static const uint8_t kHash[16] = {
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+  };
+
+  EXPECT_EQ(std::string("abcdefghijklmnopabcdefghijklmnop"),
+            GetCrxIdFromPublicKeyHash({std::cbegin(kHash), std::cend(kHash)}));
+}
+
+// Tests that the name of an InstallerAttribute matches ^[-_=a-zA-Z0-9]{1,256}$
+TEST(UpdateClientUtils, IsValidInstallerAttributeName) {
+  // Test the length boundaries.
+  EXPECT_FALSE(IsValidInstallerAttribute(
+      make_pair(std::string(0, 'a'), std::string("value"))));
+  EXPECT_TRUE(IsValidInstallerAttribute(
+      make_pair(std::string(1, 'a'), std::string("value"))));
+  EXPECT_TRUE(IsValidInstallerAttribute(
+      make_pair(std::string(256, 'a'), std::string("value"))));
+  EXPECT_FALSE(IsValidInstallerAttribute(
+      make_pair(std::string(257, 'a'), std::string("value"))));
+
+  const char* const valid_names[] = {"A", "Z", "a", "a-b", "A_B",
+                                     "z", "0", "9", "-_"};
+  for (const char* name : valid_names)
+    EXPECT_TRUE(IsValidInstallerAttribute(
+        make_pair(std::string(name), std::string("value"))));
+
+  const char* const invalid_names[] = {
+      "",   "a=1", " name", "name ", "na me", "<name", "name>",
+      "\"", "\\",  "\xaa",  ".",     ",",     ";",     "+"};
+  for (const char* name : invalid_names)
+    EXPECT_FALSE(IsValidInstallerAttribute(
+        make_pair(std::string(name), std::string("value"))));
+}
+
+// Tests that the value of an InstallerAttribute matches
+// ^[-.,;+_=a-zA-Z0-9]{0,256}$
+TEST(UpdateClientUtils, IsValidInstallerAttributeValue) {
+  // Test the length boundaries.
+  EXPECT_TRUE(IsValidInstallerAttribute(
+      make_pair(std::string("name"), std::string(0, 'a'))));
+  EXPECT_TRUE(IsValidInstallerAttribute(
+      make_pair(std::string("name"), std::string(256, 'a'))));
+  EXPECT_FALSE(IsValidInstallerAttribute(
+      make_pair(std::string("name"), std::string(257, 'a'))));
+
+  const char* const valid_values[] = {"",  "a=1", "A", "Z",      "a",
+                                      "z", "0",   "9", "-.,;+_="};
+  for (const char* value : valid_values)
+    EXPECT_TRUE(IsValidInstallerAttribute(
+        make_pair(std::string("name"), std::string(value))));
+
+  const char* const invalid_values[] = {" ap", "ap ", "a p", "<ap",
+                                        "ap>", "\"",  "\\",  "\xaa"};
+  for (const char* value : invalid_values)
+    EXPECT_FALSE(IsValidInstallerAttribute(
+        make_pair(std::string("name"), std::string(value))));
+}
+
+TEST(UpdateClientUtils, RemoveUnsecureUrls) {
+  const GURL test1[] = {GURL("http://foo"), GURL("https://foo")};
+  std::vector<GURL> urls(std::begin(test1), std::end(test1));
+  RemoveUnsecureUrls(&urls);
+  EXPECT_EQ(1u, urls.size());
+  EXPECT_EQ(urls[0], GURL("https://foo"));
+
+  const GURL test2[] = {GURL("https://foo"), GURL("http://foo")};
+  urls.assign(std::begin(test2), std::end(test2));
+  RemoveUnsecureUrls(&urls);
+  EXPECT_EQ(1u, urls.size());
+  EXPECT_EQ(urls[0], GURL("https://foo"));
+
+  const GURL test3[] = {GURL("https://foo"), GURL("https://bar")};
+  urls.assign(std::begin(test3), std::end(test3));
+  RemoveUnsecureUrls(&urls);
+  EXPECT_EQ(2u, urls.size());
+  EXPECT_EQ(urls[0], GURL("https://foo"));
+  EXPECT_EQ(urls[1], GURL("https://bar"));
+
+  const GURL test4[] = {GURL("http://foo")};
+  urls.assign(std::begin(test4), std::end(test4));
+  RemoveUnsecureUrls(&urls);
+  EXPECT_EQ(0u, urls.size());
+
+  const GURL test5[] = {GURL("http://foo"), GURL("http://bar")};
+  urls.assign(std::begin(test5), std::end(test5));
+  RemoveUnsecureUrls(&urls);
+  EXPECT_EQ(0u, urls.size());
+}
+
+TEST(UpdateClientUtils, ToInstallerResult) {
+  enum EnumA {
+    ENTRY0 = 10,
+    ENTRY1 = 20,
+  };
+
+  enum class EnumB {
+    ENTRY0 = 0,
+    ENTRY1,
+  };
+
+  const auto result1 = ToInstallerResult(EnumA::ENTRY0);
+  EXPECT_EQ(110, result1.error);
+  EXPECT_EQ(0, result1.extended_error);
+
+  const auto result2 = ToInstallerResult(ENTRY1, 10000);
+  EXPECT_EQ(120, result2.error);
+  EXPECT_EQ(10000, result2.extended_error);
+
+  const auto result3 = ToInstallerResult(EnumB::ENTRY0);
+  EXPECT_EQ(100, result3.error);
+  EXPECT_EQ(0, result3.extended_error);
+
+  const auto result4 = ToInstallerResult(EnumB::ENTRY1, 20000);
+  EXPECT_EQ(101, result4.error);
+  EXPECT_EQ(20000, result4.extended_error);
+}
+
+}  // namespace update_client