Update chrome_cleaner/chrome_utils to remove force-installed extensions.

Also includes changes to parsers/ and test/ that chrome_utils depends on.

R=csharp

Bug: 830892
Change-Id: Idaba2972a0fbc2c5e896c9403442c24c8b8c6a60
Reviewed-on: https://chromium-review.googlesource.com/c/1336047
Commit-Queue: Joe Mason <joenotcharles@google.com>
Reviewed-by: Chris Sharp <csharp@chromium.org>
Cr-Commit-Position: refs/heads/master@{#608249}
diff --git a/chrome/chrome_cleaner/chrome_utils/BUILD.gn b/chrome/chrome_cleaner/chrome_utils/BUILD.gn
index d9fc74c..f7a99c0 100644
--- a/chrome/chrome_cleaner/chrome_utils/BUILD.gn
+++ b/chrome/chrome_cleaner/chrome_utils/BUILD.gn
@@ -23,9 +23,25 @@
 
   deps = [
     ":chrome_util_lib",
+    ":force_installed_extension",
     "//base:base",
+    "//chrome/chrome_cleaner/logging/proto:shared_data_proto",
     "//chrome/chrome_cleaner/os:common_os",
     "//chrome/chrome_cleaner/parsers/json_parser",
+    "//chrome/chrome_cleaner/parsers/json_parser:json_splicer",
+    "//chrome/chrome_cleaner/parsers/parser_utils:parse_tasks_remaining_counter",
+  ]
+}
+
+source_set("force_installed_extension") {
+  sources = [
+    "force_installed_extension.cc",
+    "force_installed_extension.h",
+  ]
+
+  deps = [
+    ":extension_id",
+    "//chrome/chrome_cleaner/logging/proto:shared_data_proto",
   ]
 }
 
@@ -41,7 +57,19 @@
     "//base:base",
     "//base/test:test_support",
     "//chrome/chrome_cleaner/parsers/json_parser",
+    "//chrome/chrome_cleaner/test:test_extensions",
     "//chrome/chrome_cleaner/test:test_util",
     "//testing/gtest",
   ]
 }
+
+source_set("extension_id") {
+  sources = [
+    "extension_id.cc",
+    "extension_id.h",
+  ]
+
+  deps = [
+    "//base:base",
+  ]
+}
diff --git a/chrome/chrome_cleaner/chrome_utils/extension_id.cc b/chrome/chrome_cleaner/chrome_utils/extension_id.cc
new file mode 100644
index 0000000..91d4444
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/extension_id.cc
@@ -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.
+
+#include "chrome/chrome_cleaner/chrome_utils/extension_id.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/optional.h"
+
+namespace chrome_cleaner {
+
+base::Optional<ExtensionID> ExtensionID::Create(const std::string& value) {
+  if (ExtensionID::IsValidID(value)) {
+    return {ExtensionID(value)};
+  }
+  return {};
+}
+
+ExtensionID::ExtensionID(const std::string& value) : value_(value) {}
+
+bool ExtensionID::IsValidID(const std::string& value) {
+  if (value.length() < 32) {
+    return false;
+  }
+  for (const auto& character : value) {
+    if (character < 'a' || character > 'p') {
+      return false;
+    }
+  }
+  return true;
+}
+
+std::string ExtensionID::AsString() const {
+  return value_;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/chrome_utils/extension_id.h b/chrome/chrome_cleaner/chrome_utils/extension_id.h
new file mode 100644
index 0000000..03e4753
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/extension_id.h
@@ -0,0 +1,47 @@
+// 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 CHROME_CHROME_CLEANER_CHROME_UTILS_EXTENSION_ID_H_
+#define CHROME_CHROME_CLEANER_CHROME_UTILS_EXTENSION_ID_H_
+
+#include <string>
+
+#include "base/optional.h"
+
+namespace chrome_cleaner {
+
+// A wrapper around std::string which upholds the extension id invariants.
+// An extension must be a unique identifier that is 32 characters long between
+// 'a' - 'p' for each character.
+class ExtensionID {
+ public:
+  // Creates an ExtensionID if the |value| is a valid extension id.
+  // If the extension id is invalid no ExtensionID is stored in the optional.
+  static base::Optional<ExtensionID> Create(const std::string& value);
+  // Determines if the |value| is a valid extension ID.
+  static bool IsValidID(const std::string& value);
+
+  std::string AsString() const;
+
+  bool operator==(const ExtensionID& other) const {
+    return value_ == other.value_;
+  }
+
+  bool operator<(const ExtensionID& other) const {
+    return value_ < other.value_;
+  }
+
+  bool operator>(const ExtensionID& other) const {
+    return value_ > other.value_;
+  }
+
+ private:
+  explicit ExtensionID(const std::string& value);
+
+  std::string value_;
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_CHROME_UTILS_EXTENSION_ID_H_
diff --git a/chrome/chrome_cleaner/chrome_utils/extensions_util.cc b/chrome/chrome_cleaner/chrome_utils/extensions_util.cc
index ac79c873..35d0874 100644
--- a/chrome/chrome_cleaner/chrome_utils/extensions_util.cc
+++ b/chrome/chrome_cleaner/chrome_utils/extensions_util.cc
@@ -10,6 +10,8 @@
 #include <memory>
 #include <set>
 #include <string>
+#include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
@@ -28,6 +30,8 @@
 #include "chrome/chrome_cleaner/os/registry.h"
 #include "chrome/chrome_cleaner/os/registry_util.h"
 #include "chrome/chrome_cleaner/os/system_util.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/json_splicer.h"
+#include "chrome/chrome_cleaner/parsers/parser_utils/parse_tasks_remaining_counter.h"
 
 using base::WaitableEvent;
 
@@ -36,36 +40,7 @@
 
 const int kExtensionIdLength = 32;
 
-class ParseTasksRemainingCounter
-    : public base::RefCountedThreadSafe<ParseTasksRemainingCounter> {
- public:
-  ParseTasksRemainingCounter(size_t count, WaitableEvent* done)
-      : count_(count), done_(done) {
-    DCHECK(count_ > 0) << "Must be constructed with a positive count.";
-  }
-
-  void Increment() {
-    DCHECK(count_ > 0)
-        << "Once decremented to zero, Increment should never be called.";
-    count_++;
-  }
-
-  void Decrement() {
-    DCHECK(count_);
-    count_--;
-    if (count_ == 0) {
-      done_->Signal();
-    }
-  }
-
- private:
-  friend class base::RefCountedThreadSafe<ParseTasksRemainingCounter>;
-  ~ParseTasksRemainingCounter() = default;
-
-  size_t count_;
-  WaitableEvent* done_;
-};
-
+// TODO(joenotcharles): Use RegKeyPath instead.
 struct RegistryKey {
   HKEY hkey;
   const wchar_t* path;
@@ -95,6 +70,21 @@
 
 const wchar_t kMasterPreferencesFileName[] = L"master_preferences";
 
+// Removes the extension from the JSON. If the extension is not associated
+// wih a valid file and JSON value then this function returns false and
+// |json_result| is not modified.
+bool RemoveExtensionFromJson(const ForceInstalledExtension& extension,
+                             base::Value* json_result) {
+  DCHECK(json_result);
+  DCHECK(extension.policy_file);
+  if (!extension.policy_file->json) {
+    return false;
+  }
+
+  bool result = RemoveKeyFromDictionary(json_result, extension.id.AsString());
+  return result;
+}
+
 void GetForcelistPoliciesForAccessMask(
     REGSAM access_mask,
     std::vector<ExtensionPolicyRegistryEntry>* policies) {
@@ -114,16 +104,50 @@
 
         policies->emplace_back(extension_id, extension_forcelist_keys[i].hkey,
                                extension_forcelist_keys[i].path,
-                               forcelist_it.Name());
+                               forcelist_it.Name(), forcelist_it.Type(),
+                               nullptr);
       }
     }
   }
 }
 
+bool RemoveForcelistPolicyExtensionForAccessMask(
+    REGSAM access_mask,
+    const ForceInstalledExtension& extension) {
+  for (size_t i = 0; i < base::size(extension_forcelist_keys); ++i) {
+    std::vector<base::string16> keys;
+    base::win::RegistryValueIterator forcelist_it(
+        extension_forcelist_keys[i].hkey, extension_forcelist_keys[i].path,
+        access_mask);
+    for (; forcelist_it.Valid(); ++forcelist_it) {
+      base::string16 entry;
+      GetRegistryValueAsString(forcelist_it.Value(), forcelist_it.ValueSize(),
+                               forcelist_it.Type(), &entry);
+      if (base::UTF16ToUTF8(entry.substr(0, kExtensionIdLength)) ==
+          extension.id.AsString()) {
+        keys.push_back(forcelist_it.Name());
+      }
+    }
+    base::win::RegKey key;
+    key.Open(extension_forcelist_keys[i].hkey, extension_forcelist_keys[i].path,
+             access_mask | KEY_WRITE);
+    for (base::string16& key_name : keys) {
+      LONG result = key.DeleteValue(key_name.c_str());
+      if (result != ERROR_SUCCESS) {
+        LOG(WARNING) << "Could not delete value at key " << key_name
+                     << ", error code: " << result;
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
 void GetExtensionSettingsPoliciesFromParsedJson(
     const RegistryKey& registry_key,
     std::vector<ExtensionPolicyRegistryEntry>* policies,
     scoped_refptr<ParseTasksRemainingCounter> counter,
+    ContentType type,
     base::Optional<base::Value> json,
     const base::Optional<std::string>& error) {
   base::ScopedClosureRunner closure(
@@ -140,6 +164,8 @@
     return;
   }
 
+  scoped_refptr<RefValue> saved_json =
+      base::WrapRefCounted(new RefValue(json->Clone()));
   for (const auto& entry : *extension_settings) {
     const base::string16& extension_id = base::UTF8ToUTF16(entry.first);
     const std::unique_ptr<base::Value>& settings_value = entry.second;
@@ -150,9 +176,9 @@
       if (installation_mode != nullptr &&
           installation_mode->GetString() ==
               kExtensionSettingsForceInstalledValue) {
-        policies->emplace_back(extension_id, registry_key.hkey,
-                               registry_key.path,
-                               kExtensionSettingsRegistryEntryName);
+        policies->emplace_back(
+            extension_id, registry_key.hkey, registry_key.path,
+            kExtensionSettingsRegistryEntryName, type, saved_json);
       }
     }
   }
@@ -168,7 +194,7 @@
                    extension_settings_keys[i].path, access_mask);
     base::string16 extension_settings;
     RegistryError error;
-    uint32_t type;
+    ContentType type;
     ReadRegistryValue(key, kExtensionSettingsRegistryEntryName,
                       &extension_settings, &type, &error);
 
@@ -185,7 +211,7 @@
     json_parser->Parse(
         base::UTF16ToUTF8(extension_settings),
         base::BindOnce(&GetExtensionSettingsPoliciesFromParsedJson,
-                       extension_settings_keys[i], policies, counter));
+                       extension_settings_keys[i], policies, counter, type));
   }
 }
 
@@ -208,10 +234,13 @@
     return;
   }
 
+  scoped_refptr<RefValue> saved_json =
+      base::WrapRefCounted(new RefValue(json->Clone()));
   for (const auto& entry : *default_extensions) {
     base::string16 extension_id = base::UTF8ToUTF16(entry.first);
-    if (!base::ContainsValue(default_extension_whitelist, extension_id))
-      policies->emplace_back(extension_id, extensions_file);
+    if (!base::ContainsValue(default_extension_whitelist, extension_id)) {
+      policies->emplace_back(extension_id, extensions_file, saved_json);
+    }
   }
 }
 
@@ -241,9 +270,11 @@
 
   base::DictionaryValue* extension_settings_dictionary;
   extension_settings->GetAsDictionary(&extension_settings_dictionary);
+  scoped_refptr<RefValue> saved_json =
+      base::WrapRefCounted(new RefValue(json->Clone()));
   for (const auto& entry : *extension_settings_dictionary) {
     base::string16 extension_id = base::UTF8ToUTF16(entry.first);
-    policies->emplace_back(extension_id, extensions_file);
+    policies->emplace_back(extension_id, extensions_file, saved_json);
   }
 }
 
@@ -253,21 +284,33 @@
     const base::string16& extension_id,
     HKEY hkey,
     const base::string16& path,
-    const base::string16& name)
-    : extension_id(extension_id), hkey(hkey), path(path), name(name) {}
+    const base::string16& name,
+    ContentType content_type,
+    scoped_refptr<RefValue> json)
+    : extension_id(extension_id),
+      hkey(hkey),
+      path(path),
+      name(name),
+      content_type(content_type),
+      json(std::move(json)) {}
 
 ExtensionPolicyRegistryEntry::ExtensionPolicyRegistryEntry(
     ExtensionPolicyRegistryEntry&&) = default;
 
+ExtensionPolicyRegistryEntry::~ExtensionPolicyRegistryEntry() = default;
+
 ExtensionPolicyRegistryEntry& ExtensionPolicyRegistryEntry::operator=(
     ExtensionPolicyRegistryEntry&&) = default;
 
 ExtensionPolicyFile::ExtensionPolicyFile(const base::string16& extension_id,
-                                         const base::FilePath& path)
-    : extension_id(extension_id), path(path) {}
+                                         const base::FilePath& path,
+                                         scoped_refptr<RefValue> json)
+    : extension_id(extension_id), path(path), json(std::move(json)) {}
 
 ExtensionPolicyFile::ExtensionPolicyFile(ExtensionPolicyFile&&) = default;
 
+ExtensionPolicyFile::~ExtensionPolicyFile() = default;
+
 ExtensionPolicyFile& ExtensionPolicyFile::operator=(ExtensionPolicyFile&&) =
     default;
 
@@ -278,6 +321,19 @@
     GetForcelistPoliciesForAccessMask(KEY_WOW64_64KEY, policies);
 }
 
+bool RemoveForcelistPolicyExtension(const ForceInstalledExtension& extension) {
+  DCHECK(extension.install_method == POLICY_EXTENSION_FORCELIST);
+  // No need to check for policy_registry_entry, as it's not used in deletion.
+
+  bool result =
+      RemoveForcelistPolicyExtensionForAccessMask(KEY_WOW64_32KEY, extension);
+  if (IsX64Architecture() && result) {
+    result =
+        RemoveForcelistPolicyExtensionForAccessMask(KEY_WOW64_64KEY, extension);
+  }
+  return result;
+}
+
 void GetNonWhitelistedDefaultExtensions(
     JsonParserAPI* json_parser,
     std::vector<ExtensionPolicyFile>* policies,
@@ -317,6 +373,12 @@
   }
 }
 
+bool RemoveDefaultExtension(const ForceInstalledExtension& extension,
+                            base::Value* json_result) {
+  DCHECK(extension.install_method == DEFAULT_APPS_EXTENSION);
+  return RemoveExtensionFromJson(extension, json_result);
+}
+
 void GetExtensionSettingsForceInstalledExtensions(
     JsonParserAPI* json_parser,
     std::vector<ExtensionPolicyRegistryEntry>* policies,
@@ -335,6 +397,19 @@
   counter->Decrement();
 }
 
+bool RemoveExtensionSettingsPoliciesExtension(
+    const ForceInstalledExtension& extension,
+    base::Value* json_result) {
+  DCHECK(extension.install_method == POLICY_EXTENSION_SETTINGS);
+  DCHECK(extension.policy_registry_entry);
+  DCHECK(json_result);
+
+  if (!extension.policy_registry_entry->json.get()) {
+    return false;
+  }
+  return RemoveKeyFromDictionary(json_result, extension.id.AsString());
+}
+
 void GetMasterPreferencesExtensions(JsonParserAPI* json_parser,
                                     std::vector<ExtensionPolicyFile>* policies,
                                     base::WaitableEvent* done) {
@@ -373,4 +448,15 @@
   }
 }
 
+bool RemoveMasterPreferencesExtension(const ForceInstalledExtension& extension,
+                                      base::Value* json_result) {
+  DCHECK(extension.install_method == POLICY_MASTER_PREFERENCES);
+  DCHECK(json_result);
+  DCHECK(json_result->is_dict());
+  // The extensions are stored in ["extensions"]["settings"]
+  base::Value* sub_dictionary =
+      json_result->FindPath({"extensions", "settings"});
+  return RemoveExtensionFromJson(extension, sub_dictionary);
+}
+
 }  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/chrome_utils/extensions_util.h b/chrome/chrome_cleaner/chrome_utils/extensions_util.h
index 37a3efc..f29628a 100644
--- a/chrome/chrome_cleaner/chrome_utils/extensions_util.h
+++ b/chrome/chrome_cleaner/chrome_utils/extensions_util.h
@@ -9,25 +9,39 @@
 
 #include "base/files/file_path.h"
 #include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
 #include "base/strings/string16.h"
 #include "base/synchronization/waitable_event.h"
+#include "chrome/chrome_cleaner/chrome_utils/force_installed_extension.h"
 #include "chrome/chrome_cleaner/os/registry_util.h"
 #include "chrome/chrome_cleaner/parsers/json_parser/json_parser_api.h"
 
 namespace chrome_cleaner {
 
+typedef base::RefCountedData<base::Value> RefValue;
+typedef uint32_t ContentType;
+
+constexpr int64_t kParseAttemptTimeoutMilliseconds = 10000;
+
 // A registry key that holds some form of policy for |extension_id|.
 struct ExtensionPolicyRegistryEntry {
   base::string16 extension_id;
   HKEY hkey;
   base::string16 path;
   base::string16 name;
+  ContentType content_type;
+  scoped_refptr<RefValue> json;
 
   ExtensionPolicyRegistryEntry(const base::string16& extension_id,
                                HKEY hkey,
                                const base::string16& path,
-                               const base::string16& name);
+                               const base::string16& name,
+                               ContentType content_type,
+                               scoped_refptr<RefValue>);
   ExtensionPolicyRegistryEntry(ExtensionPolicyRegistryEntry&&);
+  ~ExtensionPolicyRegistryEntry();
   ExtensionPolicyRegistryEntry& operator=(ExtensionPolicyRegistryEntry&&);
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionPolicyRegistryEntry);
@@ -37,10 +51,13 @@
 struct ExtensionPolicyFile {
   base::string16 extension_id;
   base::FilePath path;
+  scoped_refptr<RefValue> json;
 
   ExtensionPolicyFile(const base::string16& extension_id,
-                      const base::FilePath& path);
+                      const base::FilePath& path,
+                      scoped_refptr<RefValue> json);
   ExtensionPolicyFile(ExtensionPolicyFile&&);
+  ~ExtensionPolicyFile();
   ExtensionPolicyFile& operator=(ExtensionPolicyFile&&);
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionPolicyFile);
@@ -75,6 +92,32 @@
                                     std::vector<ExtensionPolicyFile>* policies,
                                     base::WaitableEvent* done);
 
+// Attempts to remove an |extension| installed through the whitelist.
+// The extension id will be removed from the |json_result| passed in, so that
+// the caller can build up the new JSON value before writing it to the disk.
+// On failure returns false and doesn't modify the |json_result|.
+bool RemoveDefaultExtension(const ForceInstalledExtension& extension,
+                            base::Value* json_result);
+
+// Attempts to remove an extension installed through the forcelist.
+// Return True on success.
+bool RemoveForcelistPolicyExtension(const ForceInstalledExtension& extension);
+
+// Attempts to remove an extension installed from the policy settings
+// The extension id will be removed from the |json_result| passed in so that
+// the caller can build up a new JSON value before writing it to the registry.
+// On failure returns false and does not modify the |json_result|.
+bool RemoveExtensionSettingsPoliciesExtension(
+    const ForceInstalledExtension& extension,
+    base::Value* json_result);
+
+// Attempts to remove an extension installed through the master preferences.
+// The extension id will be removed from the |json_result| passed in so that the
+// caller can build up the a new JSON value before writing it to the disk.
+// On failure returns false and does not modify the |json_result|.
+bool RemoveMasterPreferencesExtension(const ForceInstalledExtension& extension,
+                                      base::Value* json_result);
+
 }  // namespace chrome_cleaner
 
 #endif  // CHROME_CHROME_CLEANER_CHROME_UTILS_EXTENSIONS_UTIL_H_
diff --git a/chrome/chrome_cleaner/chrome_utils/extensions_util_unittest.cc b/chrome/chrome_cleaner/chrome_utils/extensions_util_unittest.cc
index 1160219..c658152 100644
--- a/chrome/chrome_cleaner/chrome_utils/extensions_util_unittest.cc
+++ b/chrome/chrome_cleaner/chrome_utils/extensions_util_unittest.cc
@@ -4,12 +4,18 @@
 
 #include "chrome/chrome_cleaner/chrome_utils/extensions_util.h"
 
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <utility>
 #include <vector>
 
 #include "base/base_paths_win.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/json/json_string_value_serializer.h"
 #include "base/path_service.h"
+#include "base/stl_util.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/waitable_event.h"
@@ -18,6 +24,7 @@
 #include "base/test/test_timeouts.h"
 #include "base/win/registry.h"
 #include "chrome/chrome_cleaner/parsers/json_parser/test_json_parser.h"
+#include "chrome/chrome_cleaner/test/test_extensions.h"
 #include "chrome/chrome_cleaner/test/test_file_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -29,91 +36,18 @@
 
 const int kExtensionIdLength = 32;
 
-struct TestRegistryEntry {
-  HKEY hkey;
-  const base::string16 path;
-  const base::string16 name;
-  const base::string16 value;
+struct ExtensionIDHash {
+  size_t operator()(const ForceInstalledExtension& extension) const {
+    return std::hash<std::string>{}(extension.id.AsString());
+  }
 };
 
-const TestRegistryEntry extension_forcelist_entries[] = {
-    {HKEY_LOCAL_MACHINE, kChromePoliciesForcelistKeyPath, L"test1",
-     L"ababababcdcdcdcdefefefefghghghgh;https://test.test/crx"
-     L"update2/crx"},
-    {HKEY_CURRENT_USER, kChromePoliciesForcelistKeyPath, L"test2",
-     L"aaaabbbbccccddddeeeeffffgggghhhh;https://test.test/crx"
-     L"update2/crx"}};
-
-const wchar_t kFakeChromeFolder[] = L"google\\chrome\\application\\42.12.34.56";
-
-const wchar_t kTestExtensionId1[] = L"ababababcdcdcdcdefefefefghghghgh";
-const wchar_t kTestExtensionId2[] = L"aaaabbbbccccddddeeeeffffgggghhhh";
-const char kDefaultExtensionsJson[] =
-    R"(
-    {
-      "ababababcdcdcdcdefefefefghghghgh" : {
-        "external_update_url":"https://test.test/crx"
-      },
-      "aaaabbbbccccddddeeeeffffgggghhhh" : {
-        "external_update_url":"https://test.test/crx"
-      },
-      // Google Sheets
-      "aapocclcgogkmnckokdopfmhonfmgoek" : {
-        "external_update_url":"https://test.test/crx"
-      },
-    })";
-const char kInvalidDefaultExtensionsJson[] = "{ json: invalid }";
-
-// ExtensionSettings that has two force_installed extensions and two not.
-const wchar_t kExtensionSettingsJson[] =
-    LR"(
-    {
-      "ababababcdcdcdcdefefefefghghghgh": {
-        "installation_mode": "force_installed",
-        "update_url":"https://test.test/crx"
-      },
-      "aaaabbbbccccddddeeeeffffgggghhhh": {
-        "installation_mode": "force_installed",
-        "update_url":"https://test.test/crx"
-      },
-      "extensionwithinstallmodeblockeda": {
-        "installation_mode": "blocked",
-        "update_url":"https://test.test/crx"
-      },
-      "extensionwithnosettingsabcdefghi": {}
-    })";
-const TestRegistryEntry extension_settings_entry = {
-    HKEY_LOCAL_MACHINE, L"software\\policies\\google\\chrome",
-    L"ExtensionSettings", kExtensionSettingsJson};
-
-const wchar_t kChromeExePath[] = L"google\\chrome\\application";
-const wchar_t kMasterPreferencesFileName[] = L"master_preferences";
-const char kMasterPreferencesJson[] =
-    R"(
-    {
-      "homepage": "http://dev.chromium.org/",
-      "extensions": {
-        "settings": {
-          "ababababcdcdcdcdefefefefghghghgh": {
-            "location": 1,
-            "manifest": {
-              "name": "Test extension"
-            }
-          },
-          "aaaabbbbccccddddeeeeffffgggghhhh": {
-            "location": 1,
-            "manifest": {
-              "name": "Another one"
-            }
-          }
-        }
-      }
-    })";
-const char kMasterPreferencesJsonNoExtensions[] =
-    R"(
-    {
-      "homepage": "http://dev.chromium.org/"
-    })";
+struct ExtensionIDEqual {
+  bool operator()(const ForceInstalledExtension& lhs,
+                  const ForceInstalledExtension& rhs) const {
+    return lhs.id == rhs.id;
+  }
+};
 
 bool ExtensionPolicyRegistryEntryFound(
     TestRegistryEntry test_entry,
@@ -135,11 +69,11 @@
   registry_util::RegistryOverrideManager registry_override;
   registry_override.OverrideRegistry(HKEY_CURRENT_USER);
   registry_override.OverrideRegistry(HKEY_LOCAL_MACHINE);
-  for (const TestRegistryEntry& policy : extension_forcelist_entries) {
+  for (const TestRegistryEntry& policy : kExtensionForcelistEntries) {
     base::win::RegKey policy_key;
     ASSERT_EQ(ERROR_SUCCESS, policy_key.Create(policy.hkey, policy.path.c_str(),
                                                KEY_ALL_ACCESS));
-    DCHECK(policy_key.Valid());
+    ASSERT_TRUE(policy_key.Valid());
     ASSERT_EQ(ERROR_SUCCESS,
               policy_key.WriteValue(policy.name.c_str(), policy.value.c_str()));
   }
@@ -147,11 +81,52 @@
   std::vector<ExtensionPolicyRegistryEntry> policies;
   GetExtensionForcelistRegistryPolicies(&policies);
 
-  for (const TestRegistryEntry& expected_result : extension_forcelist_entries) {
+  for (const TestRegistryEntry& expected_result : kExtensionForcelistEntries) {
     EXPECT_TRUE(ExtensionPolicyRegistryEntryFound(expected_result, policies));
   }
 }
 
+TEST(ExtensionsUtilTest, RemoveForcelistPolicyExtensions) {
+  registry_util::RegistryOverrideManager registry_override;
+  registry_override.OverrideRegistry(HKEY_CURRENT_USER);
+  registry_override.OverrideRegistry(HKEY_LOCAL_MACHINE);
+  for (const TestRegistryEntry& policy : kExtensionForcelistEntries) {
+    base::win::RegKey policy_key;
+    ASSERT_EQ(ERROR_SUCCESS, policy_key.Create(policy.hkey, policy.path.c_str(),
+                                               KEY_ALL_ACCESS));
+    DCHECK(policy_key.Valid());
+    ASSERT_EQ(ERROR_SUCCESS,
+              policy_key.WriteValue(policy.name.c_str(), policy.value.c_str()));
+    base::string16 value;
+    policy_key.ReadValue(policy.name.c_str(), &value);
+    ASSERT_EQ(value, policy.value);
+  }
+
+  std::vector<ForceInstalledExtension> extensions;
+  std::vector<ExtensionPolicyRegistryEntry> policies;
+  GetExtensionForcelistRegistryPolicies(&policies);
+  for (ExtensionPolicyRegistryEntry& policy : policies) {
+    ForceInstalledExtension extension(
+        ExtensionID::Create(base::UTF16ToUTF8(policy.extension_id)).value(),
+        POLICY_EXTENSION_FORCELIST, "", "");
+    extension.policy_registry_entry =
+        std::make_shared<ExtensionPolicyRegistryEntry>(std::move(policy));
+    extensions.push_back(extension);
+  }
+
+  for (ForceInstalledExtension& extension : extensions) {
+    base::win::RegKey policy_key;
+    ASSERT_TRUE(RemoveForcelistPolicyExtension(extension));
+    ASSERT_EQ(ERROR_SUCCESS,
+              policy_key.Open(extension.policy_registry_entry->hkey,
+                              extension.policy_registry_entry->path.c_str(),
+                              KEY_READ));
+    base::string16 value;
+    policy_key.ReadValue(extension.policy_registry_entry->name.c_str(), &value);
+    ASSERT_EQ(value, L"");
+  }
+}
+
 TEST(ExtensionsUtilTest, GetNonWhitelistedDefaultExtensions) {
   // Set up a fake default extensions JSON file.
   base::ScopedPathOverride program_files_override(base::DIR_PROGRAM_FILES);
@@ -200,6 +175,69 @@
               ::testing::UnorderedElementsAreArray(found_extension_ids));
 }
 
+TEST(ExtensionsUtilTest, RemoveNonWhitelistedDefaultExtensions) {
+  // Set up a fake default extensions JSON file.
+  base::ScopedPathOverride program_files_override(base::DIR_PROGRAM_FILES);
+  base::FilePath program_files_dir;
+  ASSERT_TRUE(
+      base::PathService::Get(base::DIR_PROGRAM_FILES, &program_files_dir));
+
+  base::FilePath fake_apps_dir(
+      program_files_dir.Append(kFakeChromeFolder).Append(L"default_apps"));
+  ASSERT_TRUE(base::CreateDirectoryAndGetError(fake_apps_dir, nullptr));
+
+  base::FilePath default_extensions_file =
+      fake_apps_dir.Append(L"external_extensions.json");
+  CreateFileWithContent(default_extensions_file, kDefaultExtensionsJson,
+                        sizeof(kDefaultExtensionsJson) - 1);
+  ASSERT_TRUE(base::PathExists(default_extensions_file));
+
+  // Set up an invalid default extensions JSON file
+  base::ScopedPathOverride program_files_x86_override(
+      base::DIR_PROGRAM_FILESX86);
+  ASSERT_TRUE(
+      base::PathService::Get(base::DIR_PROGRAM_FILESX86, &program_files_dir));
+
+  fake_apps_dir =
+      program_files_dir.Append(kFakeChromeFolder).Append(L"default_apps");
+  ASSERT_TRUE(base::CreateDirectoryAndGetError(fake_apps_dir, nullptr));
+
+  default_extensions_file = fake_apps_dir.Append(L"external_extensions.json");
+  CreateFileWithContent(default_extensions_file, kInvalidDefaultExtensionsJson,
+                        sizeof(kInvalidDefaultExtensionsJson) - 1);
+  ASSERT_TRUE(base::PathExists(default_extensions_file));
+
+  TestJsonParser json_parser;
+  std::vector<ExtensionPolicyFile> policies;
+  base::WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL,
+                           WaitableEvent::InitialState::NOT_SIGNALED);
+  GetNonWhitelistedDefaultExtensions(&json_parser, &policies, &done);
+  ASSERT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));
+
+  std::vector<ForceInstalledExtension> extensions;
+  for (ExtensionPolicyFile& policy : policies) {
+    ForceInstalledExtension extension(
+        ExtensionID::Create(base::UTF16ToUTF8(policy.extension_id)).value(),
+        DEFAULT_APPS_EXTENSION, "", "");
+    extension.policy_file =
+        std::make_shared<ExtensionPolicyFile>(std::move(policy));
+    extensions.push_back(extension);
+  }
+  base::Value json_result = extensions[0].policy_file->json->data.Clone();
+  for (ForceInstalledExtension& extension : extensions) {
+    ASSERT_TRUE(RemoveDefaultExtension(extension, &json_result));
+  }
+  std::string result;
+  JSONStringValueSerializer serializer(&result);
+  ASSERT_TRUE(serializer.Serialize(json_result));
+  ASSERT_EQ(result, kValidDefaultExtensionsJson);
+  base::Value original = json_result.Clone();
+  for (ForceInstalledExtension& extension : extensions) {
+    ASSERT_FALSE(RemoveDefaultExtension(extension, &json_result));
+    ASSERT_EQ(original, json_result);
+  }
+}
+
 TEST(ExtensionsUtilTest, GetNonWhitelistedDefaultExtensionsNoFilesFound) {
   base::ScopedPathOverride program_files_override(base::DIR_PROGRAM_FILES);
   base::ScopedPathOverride program_files_x86_override(
@@ -223,13 +261,12 @@
 
   base::win::RegKey settings_key;
   ASSERT_EQ(ERROR_SUCCESS,
-            settings_key.Create(extension_settings_entry.hkey,
-                                extension_settings_entry.path.c_str(),
-                                KEY_ALL_ACCESS));
-  DCHECK(settings_key.Valid());
+            settings_key.Create(HKEY_LOCAL_MACHINE,
+                                kExtensionSettingsPolicyPath, KEY_ALL_ACCESS));
+  ASSERT_TRUE(settings_key.Valid());
   ASSERT_EQ(ERROR_SUCCESS,
-            settings_key.WriteValue(extension_settings_entry.name.c_str(),
-                                    extension_settings_entry.value.c_str()));
+            settings_key.WriteValue(kExtensionSettingsName,
+                                    kExtensionSettingsJsonOnlyForced));
 
   TestJsonParser json_parser;
   std::vector<ExtensionPolicyRegistryEntry> policies;
@@ -239,8 +276,8 @@
   ASSERT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));
 
   // Check that only the two force installed extensions were found
-  const base::string16 expected_extension_ids[] = {kTestExtensionId1,
-                                                   kTestExtensionId2};
+  const base::string16 expected_extension_ids[] = {kTestExtensionId4,
+                                                   kTestExtensionId5};
   const base::string16 found_extension_ids[] = {policies[0].extension_id,
                                                 policies[1].extension_id};
   EXPECT_THAT(expected_extension_ids,
@@ -249,9 +286,106 @@
   // Also check that the collected registry entries match the values in the
   // registry.
   for (const ExtensionPolicyRegistryEntry& policy : policies) {
-    EXPECT_EQ(policy.hkey, extension_settings_entry.hkey);
-    EXPECT_EQ(policy.path, extension_settings_entry.path);
-    EXPECT_EQ(policy.name, extension_settings_entry.name);
+    EXPECT_EQ(policy.hkey, HKEY_LOCAL_MACHINE);
+    EXPECT_EQ(policy.path, kExtensionSettingsPolicyPath);
+    EXPECT_EQ(policy.name, kExtensionSettingsName);
+  }
+}
+
+TEST(ExtensionsUtilTest, RemoveExtensionSettingsForceInstalledExtensions) {
+  registry_util::RegistryOverrideManager registry_override;
+  registry_override.OverrideRegistry(HKEY_CURRENT_USER);
+  registry_override.OverrideRegistry(HKEY_LOCAL_MACHINE);
+
+  base::win::RegKey settings_key;
+  ASSERT_EQ(ERROR_SUCCESS,
+            settings_key.Create(HKEY_LOCAL_MACHINE,
+                                kExtensionSettingsPolicyPath, KEY_ALL_ACCESS));
+  DCHECK(settings_key.Valid());
+  ASSERT_EQ(ERROR_SUCCESS,
+            settings_key.WriteValue(kExtensionSettingsName,
+                                    kExtensionSettingsJsonOnlyForced));
+
+  TestJsonParser json_parser;
+  std::vector<ExtensionPolicyRegistryEntry> policies;
+  base::WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL,
+                           WaitableEvent::InitialState::NOT_SIGNALED);
+  GetExtensionSettingsForceInstalledExtensions(&json_parser, &policies, &done);
+  ASSERT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));
+
+  std::unordered_set<ForceInstalledExtension, ExtensionIDHash, ExtensionIDEqual>
+      extensions;
+  base::Value json_result = policies[0].json->data.Clone();
+  for (ExtensionPolicyRegistryEntry& policy : policies) {
+    ForceInstalledExtension extension(
+        ExtensionID::Create(base::UTF16ToUTF8(policy.extension_id)).value(),
+        POLICY_EXTENSION_SETTINGS, "", "");
+    extension.policy_registry_entry =
+        std::make_shared<ExtensionPolicyRegistryEntry>(std::move(policy));
+    extensions.insert(extension);
+  }
+
+  for (const ForceInstalledExtension& extension : extensions) {
+    ASSERT_TRUE(
+        RemoveExtensionSettingsPoliciesExtension(extension, &json_result));
+  }
+  std::string result;
+  JSONStringValueSerializer serializer(&result);
+  ASSERT_TRUE(serializer.Serialize(json_result));
+  ASSERT_EQ(result, "{}");
+  base::Value original = json_result.Clone();
+  for (const ForceInstalledExtension& extension : extensions) {
+    ASSERT_FALSE(
+        RemoveExtensionSettingsPoliciesExtension(extension, &json_result));
+    ASSERT_EQ(original, json_result);
+  }
+}
+
+TEST(ExtensionsUtilTest, RemoveSomeExtensionSettingsForceInstalledExtensions) {
+  registry_util::RegistryOverrideManager registry_override;
+  registry_override.OverrideRegistry(HKEY_CURRENT_USER);
+  registry_override.OverrideRegistry(HKEY_LOCAL_MACHINE);
+
+  base::win::RegKey settings_key;
+  ASSERT_EQ(ERROR_SUCCESS,
+            settings_key.Create(HKEY_LOCAL_MACHINE,
+                                kExtensionSettingsPolicyPath, KEY_ALL_ACCESS));
+  DCHECK(settings_key.Valid());
+  ASSERT_EQ(ERROR_SUCCESS, settings_key.WriteValue(kExtensionSettingsName,
+                                                   kExtensionSettingsJson));
+
+  TestJsonParser json_parser;
+  std::vector<ExtensionPolicyRegistryEntry> policies;
+  base::WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL,
+                           WaitableEvent::InitialState::NOT_SIGNALED);
+  GetExtensionSettingsForceInstalledExtensions(&json_parser, &policies, &done);
+  ASSERT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));
+
+  std::unordered_set<ForceInstalledExtension, ExtensionIDHash, ExtensionIDEqual>
+      extensions;
+  base::Value json_result = policies[0].json->data.Clone();
+  for (ExtensionPolicyRegistryEntry& policy : policies) {
+    ForceInstalledExtension extension(
+        ExtensionID::Create(base::UTF16ToUTF8(policy.extension_id)).value(),
+        POLICY_EXTENSION_SETTINGS, "", "");
+    extension.policy_registry_entry =
+        std::make_shared<ExtensionPolicyRegistryEntry>(std::move(policy));
+    extensions.insert(extension);
+  }
+
+  for (const ForceInstalledExtension& extension : extensions) {
+    ASSERT_TRUE(
+        RemoveExtensionSettingsPoliciesExtension(extension, &json_result));
+  }
+  std::string result;
+  JSONStringValueSerializer serializer(&result);
+  ASSERT_TRUE(serializer.Serialize(json_result));
+  ASSERT_EQ(result, kValidExtensionSettingsJson);
+  base::Value original = json_result.Clone();
+  for (const ForceInstalledExtension& extension : extensions) {
+    ASSERT_FALSE(
+        RemoveExtensionSettingsPoliciesExtension(extension, &json_result));
+    ASSERT_EQ(original, json_result);
   }
 }
 
@@ -295,8 +429,8 @@
   GetMasterPreferencesExtensions(&json_parser, &policies, &done);
   ASSERT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));
 
-  const base::string16 expected_extension_ids[] = {kTestExtensionId1,
-                                                   kTestExtensionId2};
+  const base::string16 expected_extension_ids[] = {kTestExtensionId6,
+                                                   kTestExtensionId7};
   ASSERT_EQ(base::size(expected_extension_ids), policies.size());
   const base::string16 found_extension_ids[] = {policies[0].extension_id,
                                                 policies[1].extension_id};
@@ -304,6 +438,48 @@
               ::testing::UnorderedElementsAreArray(found_extension_ids));
 }
 
+TEST(ExtensionsUtilTest, RemoveMasterPreferencesExtensionsNoneFound) {
+  // Set up a fake master preferences JSON file.
+  base::ScopedPathOverride program_files_override(base::DIR_PROGRAM_FILES);
+  base::FilePath program_files_dir;
+  ASSERT_TRUE(
+      base::PathService::Get(base::DIR_PROGRAM_FILES, &program_files_dir));
+
+  base::FilePath chrome_dir(program_files_dir.Append(kChromeExePath));
+  ASSERT_TRUE(base::CreateDirectoryAndGetError(chrome_dir, nullptr));
+
+  base::FilePath master_preferences =
+      chrome_dir.Append(kMasterPreferencesFileName);
+  CreateFileWithContent(master_preferences, kMasterPreferencesJson,
+                        sizeof(kMasterPreferencesJson) - 1);
+  ASSERT_TRUE(base::PathExists(master_preferences));
+
+  TestJsonParser json_parser;
+  std::vector<ExtensionPolicyFile> policies;
+  base::WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL,
+                           WaitableEvent::InitialState::NOT_SIGNALED);
+  GetMasterPreferencesExtensions(&json_parser, &policies, &done);
+  ASSERT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));
+
+  std::vector<ForceInstalledExtension> extensions;
+  for (ExtensionPolicyFile& policy : policies) {
+    ForceInstalledExtension extension(
+        ExtensionID::Create(base::UTF16ToUTF8(policy.extension_id)).value(),
+        POLICY_MASTER_PREFERENCES, "", "");
+    extension.policy_file =
+        std::make_shared<ExtensionPolicyFile>(std::move(policy));
+    extensions.push_back(extension);
+  }
+  base::Value json_result = extensions[0].policy_file->json->data.Clone();
+  for (ForceInstalledExtension& extension : extensions) {
+    ASSERT_TRUE(RemoveMasterPreferencesExtension(extension, &json_result));
+  }
+  std::string result;
+  JSONStringValueSerializer serializer(&result);
+  ASSERT_TRUE(serializer.Serialize(json_result));
+  ASSERT_EQ(result, kValidMasterPreferencesJson);
+}
+
 TEST(ExtensionsUtilTest, GetMasterPreferencesExtensionsNoneFound) {
   // Set up a fake master preferences JSON file.
   base::ScopedPathOverride program_files_override(base::DIR_PROGRAM_FILES);
diff --git a/chrome/chrome_cleaner/chrome_utils/force_installed_extension.cc b/chrome/chrome_cleaner/chrome_utils/force_installed_extension.cc
new file mode 100644
index 0000000..08c6750
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/force_installed_extension.cc
@@ -0,0 +1,69 @@
+// 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 "chrome/chrome_cleaner/chrome_utils/force_installed_extension.h"
+
+#include <string>
+#include <utility>
+
+namespace chrome_cleaner {
+
+ForceInstalledExtension::ForceInstalledExtension(
+    const ExtensionID& id,
+    ExtensionInstallMethod install_method,
+    const std::string& update_url,
+    const std::string& manifest_permissions)
+    : id(std::move(id)),
+      install_method(std::move(install_method)),
+      update_url(std::move(update_url)),
+      manifest_permissions(std::move(manifest_permissions)) {}
+
+ForceInstalledExtension& ForceInstalledExtension::operator=(
+    const ForceInstalledExtension& other) = default;
+ForceInstalledExtension& ForceInstalledExtension::operator=(
+    ForceInstalledExtension&& other) = default;
+ForceInstalledExtension::ForceInstalledExtension(
+    ExtensionID id,
+    ExtensionInstallMethod install_method)
+    : id(std::move(id)), install_method(std::move(install_method)) {}
+
+ForceInstalledExtension::ForceInstalledExtension(
+    const ForceInstalledExtension& extension) = default;
+ForceInstalledExtension::ForceInstalledExtension(
+    ForceInstalledExtension&& extension) = default;
+
+ForceInstalledExtension::~ForceInstalledExtension() = default;
+
+bool ForceInstalledExtension::operator==(
+    const ForceInstalledExtension& other) const {
+  // Don't include policy pointers in comparison because that metadata
+  // is only used when writing out the results of transforming the values.
+  return id == other.id && install_method == other.install_method &&
+         update_url == other.update_url &&
+         manifest_permissions == other.manifest_permissions;
+}
+
+bool ForceInstalledExtension::operator<(
+    const ForceInstalledExtension& other) const {
+  if (id < other.id) {
+    return true;
+  } else if (id > other.id) {
+    return false;
+  } else if (install_method < other.install_method) {
+    return true;
+  } else if (install_method > other.install_method) {
+    return false;
+  } else if (update_url < other.update_url) {
+    return true;
+  } else if (update_url > other.update_url) {
+    return false;
+  } else if (manifest_permissions < other.manifest_permissions) {
+    return true;
+  } else if (manifest_permissions > other.manifest_permissions) {
+    return false;
+  }
+  return false;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/chrome_utils/force_installed_extension.h b/chrome/chrome_cleaner/chrome_utils/force_installed_extension.h
new file mode 100644
index 0000000..b21fd769
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/force_installed_extension.h
@@ -0,0 +1,55 @@
+// 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 CHROME_CHROME_CLEANER_CHROME_UTILS_FORCE_INSTALLED_EXTENSION_H_
+#define CHROME_CHROME_CLEANER_CHROME_UTILS_FORCE_INSTALLED_EXTENSION_H_
+
+#include <memory>
+#include <string>
+
+#include "chrome/chrome_cleaner/chrome_utils/extension_id.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
+
+namespace chrome_cleaner {
+
+// Forward declare to avoid dependency cycle.
+struct ExtensionPolicyRegistryEntry;
+struct ExtensionPolicyFile;
+
+// An extension that has been force installed.
+struct ForceInstalledExtension {
+  ForceInstalledExtension(const ExtensionID& id,
+                          ExtensionInstallMethod install_method,
+                          const std::string& update_url,
+                          const std::string& manifest_permissions);
+  ForceInstalledExtension(ExtensionID id,
+                          ExtensionInstallMethod install_method);
+
+  ForceInstalledExtension(const ForceInstalledExtension& extension);
+  ForceInstalledExtension(ForceInstalledExtension&& extension);
+  ForceInstalledExtension& operator=(const ForceInstalledExtension& other);
+  ForceInstalledExtension& operator=(ForceInstalledExtension&& other);
+  ~ForceInstalledExtension();
+  bool operator==(const ForceInstalledExtension& other) const;
+  bool operator<(const ForceInstalledExtension& other) const;
+
+  ExtensionID id;
+  ExtensionInstallMethod install_method;
+  std::string update_url;
+  std::string manifest_permissions;
+  std::shared_ptr<ExtensionPolicyRegistryEntry> policy_registry_entry;
+  std::shared_ptr<ExtensionPolicyFile> policy_file;
+};
+
+// Overload to compare by Extension IDs, useful when constructing sets
+struct ExtensionIDCompare {
+  bool operator()(const ForceInstalledExtension& lhs,
+                  const ForceInstalledExtension& rhs) const {
+    return lhs.id < rhs.id;
+  }
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_CHROME_UTILS_FORCE_INSTALLED_EXTENSION_H_
diff --git a/chrome/chrome_cleaner/parsers/parser_utils/BUILD.gn b/chrome/chrome_cleaner/parsers/parser_utils/BUILD.gn
new file mode 100644
index 0000000..b9025c8
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/parser_utils/BUILD.gn
@@ -0,0 +1,14 @@
+# 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.
+
+static_library("parse_tasks_remaining_counter") {
+  sources = [
+    "parse_tasks_remaining_counter.cc",
+    "parse_tasks_remaining_counter.h",
+  ]
+
+  deps = [
+    "//base:base",
+  ]
+}
diff --git a/chrome/chrome_cleaner/parsers/parser_utils/parse_tasks_remaining_counter.cc b/chrome/chrome_cleaner/parsers/parser_utils/parse_tasks_remaining_counter.cc
new file mode 100644
index 0000000..d218678
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/parser_utils/parse_tasks_remaining_counter.cc
@@ -0,0 +1,33 @@
+// 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 "chrome/chrome_cleaner/parsers/parser_utils/parse_tasks_remaining_counter.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+
+namespace chrome_cleaner {
+
+ParseTasksRemainingCounter::ParseTasksRemainingCounter(
+    size_t count,
+    base::WaitableEvent* done)
+    : count_(count), done_(done) {
+  DCHECK(count_ > 0) << "Must be constructed with a positive count.";
+}
+
+void ParseTasksRemainingCounter::Increment() {
+  DCHECK(count_ > 0)
+      << "Once decremented to zero, Increment should never be called.";
+  count_++;
+}
+
+void ParseTasksRemainingCounter::Decrement() {
+  DCHECK(count_);
+  count_--;
+  if (count_ == 0) {
+    done_->Signal();
+  }
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/parsers/parser_utils/parse_tasks_remaining_counter.h b/chrome/chrome_cleaner/parsers/parser_utils/parse_tasks_remaining_counter.h
new file mode 100644
index 0000000..3070580
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/parser_utils/parse_tasks_remaining_counter.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 CHROME_CHROME_CLEANER_PARSERS_PARSER_UTILS_PARSE_TASKS_REMAINING_COUNTER_H_
+#define CHROME_CHROME_CLEANER_PARSERS_PARSER_UTILS_PARSE_TASKS_REMAINING_COUNTER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+
+namespace chrome_cleaner {
+
+class ParseTasksRemainingCounter
+    : public base::RefCountedThreadSafe<ParseTasksRemainingCounter> {
+ public:
+  ParseTasksRemainingCounter(size_t count, base::WaitableEvent* done);
+  void Increment();
+  void Decrement();
+
+ private:
+  friend class base::RefCountedThreadSafe<ParseTasksRemainingCounter>;
+  ~ParseTasksRemainingCounter() = default;
+
+  size_t count_;
+  base::WaitableEvent* done_;
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_PARSERS_PARSER_UTILS_PARSE_TASKS_REMAINING_COUNTER_H_
diff --git a/chrome/chrome_cleaner/test/BUILD.gn b/chrome/chrome_cleaner/test/BUILD.gn
index 37fc2f3..a4be344 100644
--- a/chrome/chrome_cleaner/test/BUILD.gn
+++ b/chrome/chrome_cleaner/test/BUILD.gn
@@ -71,6 +71,18 @@
   ]
 }
 
+source_set("test_extensions") {
+  sources = [
+    "test_extensions.cc",
+    "test_extensions.h",
+  ]
+
+  deps = [
+    "//base:base",
+    "//chrome/chrome_cleaner/os:common_os",
+  ]
+}
+
 source_set("test_pup_data") {
   testonly = true
 
diff --git a/chrome/chrome_cleaner/test/test_extensions.cc b/chrome/chrome_cleaner/test/test_extensions.cc
new file mode 100644
index 0000000..83375fa
--- /dev/null
+++ b/chrome/chrome_cleaner/test/test_extensions.cc
@@ -0,0 +1,18 @@
+// 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 "chrome/chrome_cleaner/test/test_extensions.h"
+
+namespace chrome_cleaner {
+
+TestRegistryEntry::TestRegistryEntry(HKEY hkey,
+                                     const base::string16& path,
+                                     const base::string16& name,
+                                     const base::string16& value)
+    : hkey(hkey), path(path), name(name), value(value) {}
+TestRegistryEntry::TestRegistryEntry(const TestRegistryEntry& other) = default;
+TestRegistryEntry& TestRegistryEntry::operator=(
+    const TestRegistryEntry& other) = default;
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/test/test_extensions.h b/chrome/chrome_cleaner/test/test_extensions.h
new file mode 100644
index 0000000..03e54c8
--- /dev/null
+++ b/chrome/chrome_cleaner/test/test_extensions.h
@@ -0,0 +1,132 @@
+// 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 CHROME_CHROME_CLEANER_TEST_TEST_EXTENSIONS_H_
+#define CHROME_CHROME_CLEANER_TEST_TEST_EXTENSIONS_H_
+
+#include "base/win/registry.h"
+#include "chrome/chrome_cleaner/os/registry_util.h"
+
+namespace chrome_cleaner {
+
+struct TestRegistryEntry {
+  HKEY hkey;
+  base::string16 path;
+  base::string16 name;
+  base::string16 value;
+
+  TestRegistryEntry(HKEY hkey,
+                    const base::string16& path,
+                    const base::string16& name,
+                    const base::string16& value);
+  TestRegistryEntry(const TestRegistryEntry& other);
+  TestRegistryEntry& operator=(const TestRegistryEntry& other);
+};
+
+const wchar_t kChromeExePath[] = L"google\\chrome\\application";
+const wchar_t kFakeChromeFolder[] = L"google\\chrome\\application\\42.12.34.56";
+const wchar_t kExtensionSettingsPolicyPath[] =
+    L"software\\policies\\google\\chrome";
+const wchar_t kExtensionSettingsName[] = L"ExtensionSettings";
+const wchar_t kMasterPreferencesFileName[] = L"master_preferences";
+const wchar_t kTestExtensionId1[] = L"ababababcdcdcdcdefefefefghghghgh";
+const wchar_t kTestExtensionId2[] = L"aaaabbbbccccddddeeeeffffgggghhhh";
+const wchar_t kTestExtensionId3[] = L"hhhheeeeebbbbbccccddddaaaaabcdef";
+const wchar_t kTestExtensionId4[] = L"abcdefghgfedcbabcdefghgfedcbaced";
+const wchar_t kTestExtensionId5[] = L"dcbdcbdcbdcbdcbdcbdcbdcbdcbdcbcd";
+const wchar_t kTestExtensionId6[] = L"egegegegegegegegegegegegegegegeg";
+const wchar_t kTestExtensionId7[] = L"nopnopnopnopnopnopnopnopnopnopno";
+
+// Test force installed extension settings
+const TestRegistryEntry kExtensionForcelistEntries[] = {
+    {HKEY_LOCAL_MACHINE, kChromePoliciesForcelistKeyPath, L"test1",
+     base::string16(kTestExtensionId3) + L"https://test.test/crxupdate2/crx"}};
+const char kValidExtensionSettingsJson[] =
+    "{\"extensionwithinstallmodeblockeda\":{\"installation_mode\":\"blocked\","
+    "\"update_url\":"
+    "\"https://test.test/crx\"},\"extensionwithnosettingsabcdefghi\":{}}";
+const wchar_t kExtensionSettingsJson[] =
+    LR"(
+  {
+    "dcbdcbdcbdcbdcbdcbdcbdcbdcbdcbcd": {
+      "installation_mode": "force_installed",
+      "update_url":"https://test.test/crx"
+    },
+    "abcdefghgfedcbabcdefghgfedcbaced" : {
+      "installation_mode": "force_installed",
+      "update_url":"https://test.test/crx"
+    },
+    "extensionwithinstallmodeblockeda": {
+      "installation_mode": "blocked",
+      "update_url":"https://test.test/crx"
+    },
+    "extensionwithnosettingsabcdefghi": {}
+  })";
+const wchar_t kExtensionSettingsJsonOnlyForced[] =
+    LR"(
+  {
+    "dcbdcbdcbdcbdcbdcbdcbdcbdcbdcbcd": {
+      "installation_mode": "force_installed",
+      "update_url":"https://test.test/crx"
+    },
+    "abcdefghgfedcbabcdefghgfedcbaced": {
+      "installation_mode": "force_installed",
+      "update_url":"https://test.test/crx"
+    }
+  })";
+
+// Test force installed default extensions
+const char kInvalidDefaultExtensionsJson[] = "{ json: invalid }";
+const char kDefaultExtensionsJson[] =
+    R"(
+  {
+    "ababababcdcdcdcdefefefefghghghgh" : {
+      "external_update_url":"https://test.test/crx"
+    },
+    "aaaabbbbccccddddeeeeffffgggghhhh" : {
+      "external_update_url":"https://test.test/crx"
+    },
+    // Google Sheets
+    "aapocclcgogkmnckokdopfmhonfmgoek" : {
+      "external_update_url":"https://test.test/crx"
+    },
+  })";
+const char kValidDefaultExtensionsJson[] =
+    "{\"aapocclcgogkmnckokdopfmhonfmgoek\":{"
+    "\"external_update_url\":\"https://test.test/crx\"}}";
+
+// Test force installed master preferences
+const char kMasterPreferencesJson[] =
+    R"(
+    {
+      "homepage": "http://dev.chromium.org/",
+      "extensions": {
+        "settings": {
+          "egegegegegegegegegegegegegegegeg": {
+            "location": 1,
+            "manifest": {
+              "name": "Test extension"
+            }
+          },
+          "nopnopnopnopnopnopnopnopnopnopno": {
+            "location": 1,
+            "manifest": {
+              "name": "Another one"
+            }
+          }
+        }
+      }
+    })";
+const char kValidMasterPreferencesJson[] =
+    "{\"extensions\":{\"settings\":{}},\"homepage\":\"http://dev.chromium.org/"
+    "\"}";
+const char kMasterPreferencesJsonNoExtensions[] =
+    R"(
+{
+  "homepage": "http://dev.chromium.org/"
+})";
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_TEST_TEST_EXTENSIONS_H_