diff options
Diffstat (limited to 'chrome/browser/extensions/extensions_service.cc')
-rw-r--r-- | chrome/browser/extensions/extensions_service.cc | 265 |
1 files changed, 134 insertions, 131 deletions
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 3271920..5611594 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -68,8 +68,8 @@ const char kExternalInstallFile[] = "EXTERNAL_INSTALL"; ExtensionsService::ExtensionsService(Profile* profile, UserScriptMaster* user_script_master) : message_loop_(MessageLoop::current()), - backend_(new ExtensionsServiceBackend), install_directory_(profile->GetPath().AppendASCII(kInstallDirectoryName)), + backend_(new ExtensionsServiceBackend(install_directory_)), profile_(profile), user_script_master_(user_script_master) { } @@ -83,14 +83,12 @@ ExtensionsService::~ExtensionsService() { bool ExtensionsService::Init() { #if defined(OS_WIN) - // TODO(port): ExtensionsServiceBackend::CheckForExternalUpdates depends on // the Windows registry. // TODO(erikkay): Should we monitor the registry during run as well? g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), &ExtensionsServiceBackend::CheckForExternalUpdates, - install_directory_, scoped_refptr<ExtensionsServiceFrontendInterface>(this))); #endif @@ -99,8 +97,7 @@ bool ExtensionsService::Init() { // from the frontend interface. g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), - &ExtensionsServiceBackend::LoadExtensionsFromDirectory, - install_directory_, + &ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory, scoped_refptr<ExtensionsServiceFrontendInterface>(this))); return true; @@ -125,8 +122,6 @@ void ExtensionsService::InstallExtension(const FilePath& extension_path) { NewRunnableMethod(backend_.get(), &ExtensionsServiceBackend::InstallExtension, extension_path, - install_directory_, - true, // alert_on_error scoped_refptr<ExtensionsServiceFrontendInterface>(this))); } @@ -141,7 +136,7 @@ void ExtensionsService::LoadExtension(const FilePath& extension_path) { scoped_refptr<ExtensionsServiceFrontendInterface>(this))); } -void ExtensionsService::OnExtensionsLoadedFromDirectory( +void ExtensionsService::OnExtensionsLoaded( ExtensionList* new_extensions) { extensions_.insert(extensions_.end(), new_extensions->begin(), new_extensions->end()); @@ -168,7 +163,8 @@ void ExtensionsService::OnExtensionsLoadedFromDirectory( } } - // Tell UserScriptMaster to kick off the first scan. + // Since user scripts may have changed, tell UserScriptMaster to kick off + // a scan. user_script_master_->StartScan(); NotificationService::current()->Notify( @@ -191,21 +187,19 @@ void ExtensionsService::OnExtensionInstalled(FilePath path, bool update) { // ExtensionsServicesBackend -void ExtensionsServiceBackend::LoadExtensionsFromDirectory( - const FilePath& path_in, +void ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory( scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { - FilePath path = path_in; frontend_ = frontend; alert_on_error_ = false; - if (!file_util::AbsolutePath(&path)) + if (!file_util::AbsolutePath(&install_directory_)) NOTREACHED(); scoped_ptr<ExtensionList> extensions(new ExtensionList); // Create the <Profile>/Extensions directory if it doesn't exist. - if (!file_util::DirectoryExists(path)) { - file_util::CreateDirectory(path); + if (!file_util::DirectoryExists(install_directory_)) { + file_util::CreateDirectory(install_directory_); LOG(INFO) << "Created Extensions directory. No extensions to install."; ReportExtensionsLoaded(extensions.release()); return; @@ -215,12 +209,25 @@ void ExtensionsServiceBackend::LoadExtensionsFromDirectory( // Find all child directories in the install directory and load their // manifests. Post errors and results to the frontend. - file_util::FileEnumerator enumerator(path, + file_util::FileEnumerator enumerator(install_directory_, false, // not recursive file_util::FileEnumerator::DIRECTORIES); - for (extension_path_ = enumerator.Next(); !extension_path_.value().empty(); - extension_path_ = enumerator.Next()) { - Extension* extension = LoadExtensionCurrentVersion(); + FilePath extension_path; + for (extension_path = enumerator.Next(); !extension_path.value().empty(); + extension_path = enumerator.Next()) { + std::string extension_id = WideToASCII( + extension_path.BaseName().ToWStringHack()); + if (CheckExternalUninstall(extension_path, extension_id)) { + // TODO(erikkay): Possibly defer this operation to avoid slowing initial + // load of extensions. + UninstallExtension(extension_path); + + // No error needs to be reported. The extension effectively doesn't + // exist. + continue; + } + + Extension* extension = LoadExtensionCurrentVersion(extension_path); if (extension) extensions->push_back(extension); } @@ -237,14 +244,14 @@ void ExtensionsServiceBackend::LoadSingleExtension( // Explicit UI loads are always noisy. alert_on_error_ = true; - extension_path_ = path_in; - if (!file_util::AbsolutePath(&extension_path_)) + FilePath extension_path = path_in; + if (!file_util::AbsolutePath(&extension_path)) NOTREACHED(); LOG(INFO) << "Loading single extension from " << - WideToASCII(extension_path_.BaseName().ToWStringHack()); + WideToASCII(extension_path.BaseName().ToWStringHack()); - Extension* extension = LoadExtension(); + Extension* extension = LoadExtension(extension_path); if (extension) { ExtensionList* extensions = new ExtensionList; extensions->push_back(extension); @@ -252,27 +259,29 @@ void ExtensionsServiceBackend::LoadSingleExtension( } } -Extension* ExtensionsServiceBackend::LoadExtensionCurrentVersion() { +Extension* ExtensionsServiceBackend::LoadExtensionCurrentVersion( + const FilePath& extension_path) { std::string version_str; - if (!ReadCurrentVersion(extension_path_, &version_str)) { - ReportExtensionLoadError(StringPrintf("Could not read '%s' file.", - ExtensionsService::kCurrentVersionFileName)); + if (!ReadCurrentVersion(extension_path, &version_str)) { + ReportExtensionLoadError(extension_path, + StringPrintf("Could not read '%s' file.", + ExtensionsService::kCurrentVersionFileName)); return NULL; } LOG(INFO) << " " << - WideToASCII(extension_path_.BaseName().ToWStringHack()) << + WideToASCII(extension_path.BaseName().ToWStringHack()) << " version: " << version_str; - extension_path_ = extension_path_.AppendASCII(version_str); - return LoadExtension(); + return LoadExtension(extension_path.AppendASCII(version_str)); } -Extension* ExtensionsServiceBackend::LoadExtension() { +Extension* ExtensionsServiceBackend::LoadExtension( + const FilePath& extension_path) { FilePath manifest_path = - extension_path_.AppendASCII(Extension::kManifestFilename); + extension_path.AppendASCII(Extension::kManifestFilename); if (!file_util::PathExists(manifest_path)) { - ReportExtensionLoadError(Extension::kInvalidManifestError); + ReportExtensionLoadError(extension_path, Extension::kInvalidManifestError); return NULL; } @@ -280,29 +289,19 @@ Extension* ExtensionsServiceBackend::LoadExtension() { std::string error; scoped_ptr<Value> root(serializer.Deserialize(&error)); if (!root.get()) { - ReportExtensionLoadError(error); + ReportExtensionLoadError(extension_path, error); return NULL; } if (!root->IsType(Value::TYPE_DICTIONARY)) { - ReportExtensionLoadError(Extension::kInvalidManifestError); + ReportExtensionLoadError(extension_path, Extension::kInvalidManifestError); return NULL; } - scoped_ptr<Extension> extension(new Extension(extension_path_)); + scoped_ptr<Extension> extension(new Extension(extension_path)); if (!extension->InitFromValue(*static_cast<DictionaryValue*>(root.get()), &error)) { - ReportExtensionLoadError(error); - return NULL; - } - - if (CheckExternalUninstall(extension_path_, extension->id())) { - - // TODO(erikkay): Possibly defer this operation to avoid slowing initial - // load of extensions. - UninstallExtension(extension_path_); - - // No error needs to be reported. The extension effectively doesn't exist. + ReportExtensionLoadError(extension_path, error); return NULL; } @@ -311,7 +310,7 @@ Extension* ExtensionsServiceBackend::LoadExtension() { extension->content_scripts().begin(); iter != extension->content_scripts().end(); ++iter) { if (!file_util::PathExists(iter->path())) { - ReportExtensionLoadError( + ReportExtensionLoadError(extension_path, StringPrintf("Could not load content script '%s'.", WideToUTF8(iter->path().ToWStringHack()).c_str())); return NULL; @@ -322,9 +321,9 @@ Extension* ExtensionsServiceBackend::LoadExtension() { } void ExtensionsServiceBackend::ReportExtensionLoadError( - const std::string &error) { + const FilePath& extension_path, const std::string &error) { // TODO(port): note that this isn't guaranteed to work properly on Linux. - std::string path_str = WideToASCII(extension_path_.ToWStringHack()); + std::string path_str = WideToASCII(extension_path.ToWStringHack()); std::string message = StringPrintf("Could not load extension from '%s'. %s", path_str.c_str(), error.c_str()); ExtensionErrorReporter::GetInstance()->ReportError(message, alert_on_error_); @@ -334,17 +333,18 @@ void ExtensionsServiceBackend::ReportExtensionsLoaded( ExtensionList* extensions) { frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod( frontend_, - &ExtensionsServiceFrontendInterface::OnExtensionsLoadedFromDirectory, + &ExtensionsServiceFrontendInterface::OnExtensionsLoaded, extensions)); } // The extension file format is a header, followed by the manifest, followed // by the zip file. The header is a magic number, a version, the size of the // header, and the size of the manifest. These ints are 4 byte little endian. -DictionaryValue* ExtensionsServiceBackend::ReadManifest() { - ScopedStdioHandle file(file_util::OpenFile(extension_path_, "rb")); +DictionaryValue* ExtensionsServiceBackend::ReadManifest( + const FilePath& extension_path) { + ScopedStdioHandle file(file_util::OpenFile(extension_path, "rb")); if (!file.get()) { - ReportExtensionInstallError("no such extension file"); + ReportExtensionInstallError(extension_path, "no such extension file"); return NULL; } @@ -358,15 +358,15 @@ DictionaryValue* ExtensionsServiceBackend::ReadManifest() { // on a little endian machine with 4 byte alignment. len = fread(&header, 1, sizeof(ExtensionHeader), file.get()); if (len < sizeof(ExtensionHeader)) { - ReportExtensionInstallError("invalid extension header"); + ReportExtensionInstallError(extension_path, "invalid extension header"); return NULL; } if (strncmp(kExtensionFileMagic, header.magic, sizeof(header.magic))) { - ReportExtensionInstallError("bad magic number"); + ReportExtensionInstallError(extension_path, "bad magic number"); return NULL; } if (header.version != Extension::kExpectedFormatVersion) { - ReportExtensionInstallError("bad version number"); + ReportExtensionInstallError(extension_path, "bad version number"); return NULL; } if (header.header_size > sizeof(ExtensionHeader)) @@ -389,11 +389,12 @@ DictionaryValue* ExtensionsServiceBackend::ReadManifest() { std::string error; scoped_ptr<Value> val(json.Deserialize(&error)); if (!val.get()) { - ReportExtensionInstallError(error); + ReportExtensionInstallError(extension_path, error); return NULL; } if (!val->IsType(Value::TYPE_DICTIONARY)) { - ReportExtensionInstallError("manifest isn't a JSON dictionary"); + ReportExtensionInstallError(extension_path, + "manifest isn't a JSON dictionary"); return NULL; } DictionaryValue* manifest = static_cast<DictionaryValue*>(val.get()); @@ -403,14 +404,14 @@ DictionaryValue* ExtensionsServiceBackend::ReadManifest() { // work. std::string id; if (!manifest->GetString(Extension::kIdKey, &id)) { - ReportExtensionInstallError("missing id key"); + ReportExtensionInstallError(extension_path, "missing id key"); return NULL; } FilePath dest_dir = install_directory_.AppendASCII(id.c_str()); if (file_util::PathExists(dest_dir)) { std::string version; if (!manifest->GetString(Extension::kVersionKey, &version)) { - ReportExtensionInstallError("missing version key"); + ReportExtensionInstallError(extension_path, "missing version key"); return NULL; } std::string current_version; @@ -422,11 +423,11 @@ DictionaryValue* ExtensionsServiceBackend::ReadManifest() { std::string zip_hash; if (!manifest->GetString(Extension::kZipHashKey, &zip_hash)) { - ReportExtensionInstallError("missing zip_hash key"); + ReportExtensionInstallError(extension_path, "missing zip_hash key"); return NULL; } if (zip_hash.size() != kZipHashHexBytes) { - ReportExtensionInstallError("invalid zip_hash key"); + ReportExtensionInstallError(extension_path, "invalid zip_hash key"); return NULL; } @@ -443,16 +444,17 @@ DictionaryValue* ExtensionsServiceBackend::ReadManifest() { std::vector<uint8> zip_hash_bytes; if (!HexStringToBytes(zip_hash, &zip_hash_bytes)) { - ReportExtensionInstallError("invalid zip_hash key"); + ReportExtensionInstallError(extension_path, "invalid zip_hash key"); return NULL; } if (zip_hash_bytes.size() != kZipHashBytes) { - ReportExtensionInstallError("invalid zip_hash key"); + ReportExtensionInstallError(extension_path, "invalid zip_hash key"); return NULL; } for (size_t i = 0; i < kZipHashBytes; ++i) { if (zip_hash_bytes[i] != hash[i]) { - ReportExtensionInstallError("zip_hash key didn't match zip hash"); + ReportExtensionInstallError(extension_path, + "zip_hash key didn't match zip hash"); return NULL; } } @@ -487,14 +489,13 @@ bool ExtensionsServiceBackend::CheckCurrentVersion( scoped_ptr<Version> new_version( Version::GetVersionFromString(new_version_str)); if (current_version->CompareTo(*new_version) >= 0) { - // Verify that the directory actually exists. If it doesn't we'll return // true so that the install code will repair the broken installation. // TODO(erikkay): A further step would be to verify that the extension // has actually loaded successfully. FilePath version_dir = dest_dir.AppendASCII(current_version_str); if (file_util::PathExists(version_dir)) { - ReportExtensionInstallError( + ReportExtensionInstallError(dest_dir, "Existing version is already up to date."); return false; } @@ -506,11 +507,10 @@ bool ExtensionsServiceBackend::InstallDirSafely(const FilePath& source_dir, const FilePath& dest_dir) { if (file_util::PathExists(dest_dir)) { - // By the time we get here, it should be safe to assume that this directory // is not currently in use (it's not the current active version). if (!file_util::Delete(dest_dir, true)) { - ReportExtensionInstallError( + ReportExtensionInstallError(source_dir, "Can't delete existing version directory."); return false; } @@ -518,13 +518,15 @@ bool ExtensionsServiceBackend::InstallDirSafely(const FilePath& source_dir, FilePath parent = dest_dir.DirName(); if (!file_util::DirectoryExists(parent)) { if (!file_util::CreateDirectory(parent)) { - ReportExtensionInstallError("Couldn't create extension directory."); + ReportExtensionInstallError(source_dir, + "Couldn't create extension directory."); return false; } } } if (!file_util::Move(source_dir, dest_dir)) { - ReportExtensionInstallError("Couldn't move temporary directory."); + ReportExtensionInstallError(source_dir, + "Couldn't move temporary directory."); return false; } @@ -541,13 +543,15 @@ bool ExtensionsServiceBackend::SetCurrentVersion(const FilePath& dest_dir, current_version.InsertBeforeExtension(FILE_PATH_LITERAL("_old")); if (file_util::PathExists(current_version_old)) { if (!file_util::Delete(current_version_old, false)) { - ReportExtensionInstallError("Couldn't remove CurrentVersion_old file."); + ReportExtensionInstallError(dest_dir, + "Couldn't remove CurrentVersion_old file."); return false; } } if (file_util::PathExists(current_version)) { if (!file_util::Move(current_version, current_version_old)) { - ReportExtensionInstallError("Couldn't move CurrentVersion file."); + ReportExtensionInstallError(dest_dir, + "Couldn't move CurrentVersion file."); return false; } } @@ -566,7 +570,8 @@ bool ExtensionsServiceBackend::SetCurrentVersion(const FilePath& dest_dir, // TODO(erikkay): This is an ugly state to be in. Try harder? } } - ReportExtensionInstallError("Couldn't create CurrentVersion file."); + ReportExtensionInstallError(dest_dir, + "Couldn't create CurrentVersion file."); return false; } return true; @@ -574,44 +579,46 @@ bool ExtensionsServiceBackend::SetCurrentVersion(const FilePath& dest_dir, void ExtensionsServiceBackend::InstallExtension( const FilePath& extension_path, - const FilePath& install_dir, - bool alert_on_error, scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { LOG(INFO) << "Installing extension " << extension_path.value(); frontend_ = frontend; - alert_on_error_ = alert_on_error; - external_install_ = false; - extension_path_ = extension_path; - install_directory_ = install_dir; + alert_on_error_ = false; - InstallOrUpdateExtension(std::string()); + bool was_update = false; + FilePath destination_path; + if (InstallOrUpdateExtension(extension_path, + std::string(), // no expected id + &destination_path, &was_update)) + ReportExtensionInstalled(destination_path.DirName(), was_update); } -void ExtensionsServiceBackend::InstallOrUpdateExtension( - const std::string& expected_id) { - bool update = false; +bool ExtensionsServiceBackend::InstallOrUpdateExtension( + const FilePath& source_file, const std::string& expected_id, + FilePath* version_dir, bool* was_update) { + if (was_update) + *was_update = false; // Read and verify the extension. - scoped_ptr<DictionaryValue> manifest(ReadManifest()); + scoped_ptr<DictionaryValue> manifest(ReadManifest(source_file)); if (!manifest.get()) { - // ReadManifest has already reported the extension error. - return; + return false; } DictionaryValue* dict = manifest.get(); Extension extension; std::string error; if (!extension.InitFromValue(*dict, &error)) { - ReportExtensionInstallError("Invalid extension manifest."); - return; + ReportExtensionInstallError(source_file, + "Invalid extension manifest."); + return false; } // If an expected id was provided, make sure it matches. if (expected_id.length() && expected_id != extension.id()) { - ReportExtensionInstallError( + ReportExtensionInstallError(source_file, "ID in new extension manifest does not match expected ID."); - return; + return false; } // <profile>/Extensions/<id> @@ -620,8 +627,9 @@ void ExtensionsServiceBackend::InstallOrUpdateExtension( std::string current_version; if (ReadCurrentVersion(dest_dir, ¤t_version)) { if (!CheckCurrentVersion(version, current_version, dest_dir)) - return; - update = true; + return false; + if (was_update) + *was_update = true; } // <profile>/Extensions/INSTALL_TEMP @@ -630,56 +638,46 @@ void ExtensionsServiceBackend::InstallOrUpdateExtension( // Ensure we're starting with a clean slate. if (file_util::PathExists(temp_dir)) { if (!file_util::Delete(temp_dir, true)) { - ReportExtensionInstallError( + ReportExtensionInstallError(source_file, "Couldn't delete existing temporary directory."); - return; + return false; } } ScopedTempDir scoped_temp; scoped_temp.Set(temp_dir); if (!scoped_temp.IsValid()) { - ReportExtensionInstallError("Couldn't create temporary directory."); - return; + ReportExtensionInstallError(source_file, + "Couldn't create temporary directory."); + return false; } // <profile>/Extensions/INSTALL_TEMP/<version> FilePath temp_version = temp_dir.AppendASCII(version); file_util::CreateDirectory(temp_version); - if (!Unzip(extension_path_, temp_version, NULL)) { - ReportExtensionInstallError("Couldn't unzip extension."); - return; + if (!Unzip(source_file, temp_version, NULL)) { + ReportExtensionInstallError(source_file, "Couldn't unzip extension."); + return false; } // <profile>/Extensions/<dir_name>/<version> - FilePath version_dir = dest_dir.AppendASCII(version); - if (!InstallDirSafely(temp_version, version_dir)) - return; + *version_dir = dest_dir.AppendASCII(version); + if (!InstallDirSafely(temp_version, *version_dir)) + return false; if (!SetCurrentVersion(dest_dir, version)) { - if (!file_util::Delete(version_dir, true)) + if (!file_util::Delete(*version_dir, true)) LOG(WARNING) << "Can't remove " << dest_dir.value(); - return; - } - - if (external_install_) { - - // To mark that this extension was installed from an external source, - // create a zero-length file. At load time, this is used to indicate - // that the extension should be uninstalled. - // TODO(erikkay): move this into per-extension config storage when - // it appears. - FilePath marker = version_dir.AppendASCII(kExternalInstallFile); - file_util::WriteFile(marker, NULL, 0); + return false; } - ReportExtensionInstalled(dest_dir, update); + return true; } void ExtensionsServiceBackend::ReportExtensionInstallError( - const std::string &error) { + const FilePath& extension_path, const std::string &error) { // TODO(erikkay): note that this isn't guaranteed to work properly on Linux. - std::string path_str = WideToASCII(extension_path_.ToWStringHack()); + std::string path_str = WideToASCII(extension_path.ToWStringHack()); std::string message = StringPrintf("Could not install extension from '%s'. %s", path_str.c_str(), error.c_str()); @@ -687,7 +685,7 @@ void ExtensionsServiceBackend::ReportExtensionInstallError( } void ExtensionsServiceBackend::ReportExtensionInstalled( - FilePath path, bool update) { + const FilePath& path, bool update) { frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod( frontend_, &ExtensionsServiceFrontendInterface::OnExtensionInstalled, @@ -695,9 +693,8 @@ void ExtensionsServiceBackend::ReportExtensionInstalled( update)); // After it's installed, load it right away with the same settings. - extension_path_ = path; LOG(INFO) << "Loading extension " << path.value(); - Extension* extension = LoadExtensionCurrentVersion(); + Extension* extension = LoadExtensionCurrentVersion(path); if (extension) { // Only one extension, but ReportExtensionsLoaded can handle multiple, // so we need to construct a list. @@ -716,7 +713,6 @@ void ExtensionsServiceBackend::ReportExtensionInstalled( // check that location for a .crx file, which it will then install locally if // a new version is available. void ExtensionsServiceBackend::CheckForExternalUpdates( - const FilePath& install_dir, scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { // Note that this installation is intentionally silent (since it didn't @@ -726,8 +722,6 @@ void ExtensionsServiceBackend::CheckForExternalUpdates( // they could install an extension manually themselves anyway. alert_on_error_ = false; frontend_ = frontend; - external_install_ = true; - install_directory_ = install_dir; #if defined(OS_WIN) HKEY reg_root = HKEY_LOCAL_MACHINE; @@ -741,19 +735,28 @@ void ExtensionsServiceBackend::CheckForExternalUpdates( std::wstring extension_path; if (key.ReadValue(kRegistryExtensionPath, &extension_path)) { std::string id = WideToASCII(iterator.Name()); - extension_path_ = FilePath(extension_path); std::wstring extension_version; if (key.ReadValue(kRegistryExtensionVersion, &extension_version)) { - if (ShouldInstall(id, WideToASCII(extension_version))) - InstallOrUpdateExtension(id); + if (ShouldInstall(id, WideToASCII(extension_version))) { + FilePath version_dir; + if (InstallOrUpdateExtension(FilePath(extension_path), id, + &version_dir, NULL)) { + // To mark that this extension was installed from an external + // source, create a zero-length file. At load time, this is used + // to indicate that the extension should be uninstalled. + // TODO(erikkay): move this into per-extension config storage when + // it appears. + FilePath marker = version_dir.AppendASCII( + kExternalInstallFile); + file_util::WriteFile(marker, NULL, 0); + } + } } else { - // TODO(erikkay): find a way to get this into about:extensions LOG(WARNING) << "Missing value " << kRegistryExtensionVersion << " for key " << key_path; } } else { - // TODO(erikkay): find a way to get this into about:extensions LOG(WARNING) << "Missing value " << kRegistryExtensionPath << " for key " << key_path; |