// Copyright 2015 The Chromium Embedded Framework Authors. // Portions 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 "libcef/browser/extensions/extension_system.h" #include #include "libcef/browser/extension_impl.h" #include "libcef/browser/extensions/pdf_extension_util.h" #include "libcef/browser/extensions/value_store/cef_value_store_factory.h" #include "libcef/browser/thread_util.h" #include "libcef/common/extensions/extensions_util.h" #include "base/command_line.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/strings/string_tokenizer.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_paths.h" #include "components/crx_file/id_util.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/plugin_service.h" #include "content/public/browser/render_process_host.h" #include "extensions/browser/api/app_runtime/app_runtime_api.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/info_map.h" #include "extensions/browser/notification_types.h" #include "extensions/browser/null_app_sorting.h" #include "extensions/browser/quota_service.h" #include "extensions/browser/renderer_startup_helper.h" #include "extensions/browser/runtime_data.h" #include "extensions/browser/service_worker_manager.h" #include "extensions/browser/state_store.h" #include "extensions/common/constants.h" #include "extensions/common/extension_messages.h" #include "extensions/common/file_util.h" #include "extensions/common/manifest_constants.h" #include "extensions/common/manifest_handlers/mime_types_handler.h" #include "extensions/common/switches.h" #include "net/base/mime_util.h" using content::BrowserContext; using content::BrowserThread; namespace extensions { namespace { // Implementation based on ComponentLoader::ParseManifest. std::unique_ptr ParseManifest( const std::string& manifest_contents) { JSONStringValueDeserializer deserializer(manifest_contents); std::unique_ptr manifest(deserializer.Deserialize(NULL, NULL)); if (!manifest.get() || !manifest->is_dict()) { LOG(ERROR) << "Failed to parse extension manifest."; return NULL; } // Transfer ownership to the caller. return base::WrapUnique( static_cast(manifest.release())); } void ExecuteLoadFailure(CefRefPtr handler, cef_errorcode_t result) { if (!handler) return; if (!CEF_CURRENTLY_ON_UIT()) { CEF_POST_TASK(CEF_UIT, base::BindOnce(ExecuteLoadFailure, handler, result)); return; } handler->OnExtensionLoadFailed(result); } void LoadExtensionOnUIThread(base::WeakPtr context, std::unique_ptr manifest, const base::FilePath& root_directory, bool internal, CefRefPtr loader_context, CefRefPtr handler) { if (!CEF_CURRENTLY_ON_UIT()) { CEF_POST_TASK(CEF_UIT, base::BindOnce(LoadExtensionOnUIThread, context, base::Passed(std::move(manifest)), root_directory, internal, loader_context, handler)); return; } if (context) { context->LoadExtension(std::move(manifest), root_directory, internal, loader_context, handler); } } void LoadExtensionWithManifest(base::WeakPtr context, const std::string& manifest_contents, const base::FilePath& root_directory, bool internal, CefRefPtr loader_context, CefRefPtr handler) { CEF_REQUIRE_BLOCKING(); std::unique_ptr manifest = ParseManifest(manifest_contents); if (!manifest) { LOG(WARNING) << "Failed to parse extension manifest"; ExecuteLoadFailure(handler, ERR_INVALID_ARGUMENT); return; } LoadExtensionOnUIThread(context, std::move(manifest), root_directory, internal, loader_context, handler); } void LoadExtensionFromDisk(base::WeakPtr context, const base::FilePath& root_directory, bool internal, CefRefPtr loader_context, CefRefPtr handler) { CEF_REQUIRE_BLOCKING(); base::FilePath manifest_path = root_directory.AppendASCII("manifest.json"); std::string manifest_contents; if (!base::ReadFileToString(manifest_path, &manifest_contents)) { LOG(WARNING) << "Failed to read extension manifest from " << manifest_path.MaybeAsASCII(); ExecuteLoadFailure(handler, ERR_FILE_NOT_FOUND); return; } LoadExtensionWithManifest(context, manifest_contents, root_directory, internal, loader_context, handler); } } // namespace CefExtensionSystem::CefExtensionSystem(BrowserContext* browser_context) : browser_context_(browser_context), initialized_(false), registry_(ExtensionRegistry::Get(browser_context)), renderer_helper_( extensions::RendererStartupHelperFactory::GetForBrowserContext( browser_context)), weak_ptr_factory_(this) { InitPrefs(); } CefExtensionSystem::~CefExtensionSystem() {} void CefExtensionSystem::Init() { DCHECK(!initialized_); // There's complexity here related to the ordering of message delivery. For // an extension to load correctly both the ExtensionMsg_Loaded and // ExtensionMsg_ActivateExtension messages must be sent. These messages are // currently sent by RendererStartupHelper, ExtensionWebContentsObserver, and // this class. ExtensionMsg_Loaded is handled by Dispatcher::OnLoaded and adds // the extension to |extensions_|. ExtensionMsg_ActivateExtension is handled // by Dispatcher::OnActivateExtension and adds the extension to // |active_extension_ids_|. If these messages are not sent correctly then // ScriptContextSet::Register called from Dispatcher::DidCreateScriptContext // will classify the extension incorrectly and API bindings will not be added. // Inform the rest of the extensions system to start. ready_.Signal(); content::NotificationService::current()->Notify( NOTIFICATION_EXTENSIONS_READY_DEPRECATED, content::Source(browser_context_), content::NotificationService::NoDetails()); // Add the internal PDF extension. PDF loading works as follows: // 1. PDF PPAPI plugin is registered to handle kPDFPluginOutOfProcessMimeType // in libcef/common/content_client.cc ComputeBuiltInPlugins. // 2. PDF extension is registered and associated with the "application/pdf" // mime type by the below call to AddExtension. // 3. A page requests a resource with the "application/pdf" mime type. For // example, by loading a PDF file. // 4. CefResourceDispatcherHostDelegate::ShouldInterceptResourceAsStream // intercepts the PDF resource load in the browser process, generates a // unique View ID that is associated with the resource request for later // retrieval via MimeHandlerStreamManager and the // chrome.mimeHandlerPrivate JS API (extensions/common/api/ // mime_handler_private.idl), and returns the unique View ID via the // |payload| argument. // 5. The unique View ID arrives in the renderer process via // ResourceLoader::didReceiveData and triggers creation of a new Document. // DOMImplementation::createDocument indirectly calls // RendererBlinkPlatformImpl::getPluginList to retrieve the list of // supported plugins from the browser process. If a plugin supports the // "application/pdf" mime type then a PluginDocument is created and // CefContentRendererClient::OverrideCreatePlugin is called. This then // indirectly calls CefContentRendererClient::CreateBrowserPluginDelegate // to create a MimeHandlerViewContainer. // 6. A MimeHandlerViewGuest and CefMimeHandlerViewGuestDelegate is created in // the browser process. // 7. MimeHandlerViewGuest navigates to the PDF extension URL. // 8. Access to PDF extension resources is checked by // CefExtensionsBrowserClient::AllowCrossRendererResourceLoad. // 9. PDF extension resources are provided from bundle via // CefExtensionsBrowserClient::MaybeCreateResourceBundleRequestJob and // CefComponentExtensionResourceManager. // 10.The PDF extension (chrome/browser/resources/pdf/browser_api.js) calls // chrome.mimeHandlerPrivate.getStreamInfo to retrieve the PDF resource // stream. This API is implemented using Mojo as described in // libcef/common/extensions/api/README.txt. // 11.The PDF extension requests a plugin to handle // kPDFPluginOutOfProcessMimeType which loads the PDF PPAPI plugin. // 12.Routing of print-related commands are handled by ChromePDFPrintClient // and CefPrintRenderFrameHelperDelegate in the renderer process. // 13.The PDF extension is granted access to chrome://resources via // CefExtensionWebContentsObserver::RenderViewCreated in the browser // process. if (PdfExtensionEnabled()) { LoadExtension(ParseManifest(pdf_extension_util::GetManifest()), base::FilePath(FILE_PATH_LITERAL("pdf")), true /* internal */, nullptr, nullptr); } initialized_ = true; } void CefExtensionSystem::LoadExtension( const base::FilePath& root_directory, bool internal, CefRefPtr loader_context, CefRefPtr handler) { CEF_REQUIRE_UIT(); CEF_POST_USER_VISIBLE_TASK( base::BindOnce(LoadExtensionFromDisk, weak_ptr_factory_.GetWeakPtr(), root_directory, internal, loader_context, handler)); } void CefExtensionSystem::LoadExtension( const std::string& manifest_contents, const base::FilePath& root_directory, bool internal, CefRefPtr loader_context, CefRefPtr handler) { CEF_REQUIRE_UIT(); CEF_POST_USER_VISIBLE_TASK(base::BindOnce( LoadExtensionWithManifest, weak_ptr_factory_.GetWeakPtr(), manifest_contents, root_directory, internal, loader_context, handler)); } // Implementation based on ComponentLoader::Add. void CefExtensionSystem::LoadExtension( std::unique_ptr manifest, const base::FilePath& root_directory, bool internal, CefRefPtr loader_context, CefRefPtr handler) { CEF_REQUIRE_UIT(); // Internal extensions don't have a loader context. External extensions should. #if DCHECK_IS_ON() if (internal) { DCHECK(!loader_context); } else { DCHECK(loader_context); } #endif ComponentExtensionInfo info(manifest.get(), root_directory, internal); const Extension* extension = LoadExtension(info, loader_context, handler); if (!extension) ExecuteLoadFailure(handler, ERR_FAILED); } // Implementation based on ExtensionService::RemoveComponentExtension. bool CefExtensionSystem::UnloadExtension(const std::string& extension_id) { CEF_REQUIRE_UIT(); ExtensionMap::iterator it = extension_map_.find(extension_id); if (it == extension_map_.end()) { // No CEF representation so we've already unloaded it. return false; } CefRefPtr cef_extension = static_cast(it->second.get()); // Erase first so that callbacks can't retrieve the unloaded extension. extension_map_.erase(it); cef_extension->OnExtensionUnloaded(); scoped_refptr extension( registry_->GetInstalledExtension(extension_id)); UnloadExtension(extension_id, UnloadedExtensionReason::UNINSTALL); if (extension.get()) { registry_->TriggerOnUninstalled( extension.get(), extensions::UNINSTALL_REASON_COMPONENT_REMOVED); } return true; } bool CefExtensionSystem::HasExtension(const std::string& extension_id) const { return !!GetExtension(extension_id); } CefRefPtr CefExtensionSystem::GetExtension( const std::string& extension_id) const { CEF_REQUIRE_UIT(); ExtensionMap::const_iterator it = extension_map_.find(extension_id); if (it != extension_map_.end()) return it->second; return nullptr; } CefExtensionSystem::ExtensionMap CefExtensionSystem::GetExtensions() const { CEF_REQUIRE_UIT(); return extension_map_; } void CefExtensionSystem::OnRequestContextDeleted(CefRequestContext* context) { CEF_REQUIRE_UIT(); DCHECK(context); // Make a copy of the map because UnloadExtension will modify it. // Don't add any references to |context|. ExtensionMap map = extension_map_; ExtensionMap::const_iterator it = map.begin(); for (; it != map.end(); ++it) { CefRefPtr cef_extension = static_cast(it->second.get()); if (cef_extension->loader_context() == context) UnloadExtension(it->first); } } void CefExtensionSystem::Shutdown() { CEF_REQUIRE_UIT(); // Only internal extensions should exist at this point. #if DCHECK_IS_ON() ExtensionMap::iterator it = extension_map_.begin(); for (; it != extension_map_.end(); ++it) { CefRefPtr cef_extension = static_cast(it->second.get()); DCHECK(!cef_extension->loader_context()); } #endif extension_map_.clear(); } void CefExtensionSystem::InitForRegularProfile(bool extensions_enabled) { DCHECK(!initialized_); service_worker_manager_.reset(new ServiceWorkerManager(browser_context_)); runtime_data_.reset(new RuntimeData(registry_)); quota_service_.reset(new QuotaService); app_sorting_.reset(new NullAppSorting); } void CefExtensionSystem::InitForIncognitoProfile() { NOTREACHED(); } ExtensionService* CefExtensionSystem::extension_service() { return nullptr; } RuntimeData* CefExtensionSystem::runtime_data() { return runtime_data_.get(); } ManagementPolicy* CefExtensionSystem::management_policy() { return nullptr; } ServiceWorkerManager* CefExtensionSystem::service_worker_manager() { return service_worker_manager_.get(); } SharedUserScriptMaster* CefExtensionSystem::shared_user_script_master() { return nullptr; } StateStore* CefExtensionSystem::state_store() { return state_store_.get(); } StateStore* CefExtensionSystem::rules_store() { return rules_store_.get(); } scoped_refptr CefExtensionSystem::store_factory() { return store_factory_; } InfoMap* CefExtensionSystem::info_map() { if (!info_map_.get()) info_map_ = new InfoMap; return info_map_.get(); } QuotaService* CefExtensionSystem::quota_service() { return quota_service_.get(); } AppSorting* CefExtensionSystem::app_sorting() { return app_sorting_.get(); } // Implementation based on // ExtensionSystemImpl::RegisterExtensionWithRequestContexts. void CefExtensionSystem::RegisterExtensionWithRequestContexts( const Extension* extension, const base::Closure& callback) { // TODO(extensions): The |incognito_enabled| value should be set based on // manifest settings. BrowserThread::PostTaskAndReply( BrowserThread::IO, FROM_HERE, base::Bind(&InfoMap::AddExtension, info_map(), base::RetainedRef(extension), base::Time::Now(), true, // incognito_enabled false), // notifications_disabled callback); } // Implementation based on // ExtensionSystemImpl::UnregisterExtensionWithRequestContexts. void CefExtensionSystem::UnregisterExtensionWithRequestContexts( const std::string& extension_id, const UnloadedExtensionReason reason) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&InfoMap::RemoveExtension, info_map(), extension_id, reason)); } const OneShotEvent& CefExtensionSystem::ready() const { return ready_; } ContentVerifier* CefExtensionSystem::content_verifier() { return nullptr; } std::unique_ptr CefExtensionSystem::GetDependentExtensions( const Extension* extension) { return std::make_unique(); } void CefExtensionSystem::InstallUpdate( const std::string& extension_id, const std::string& public_key, const base::FilePath& temp_dir, bool install_immediately, InstallUpdateCallback install_update_callback) { NOTREACHED(); base::DeleteFile(temp_dir, true /* recursive */); } bool CefExtensionSystem::FinishDelayedInstallationIfReady( const std::string& extension_id, bool install_immediately) { NOTREACHED(); return false; } CefExtensionSystem::ComponentExtensionInfo::ComponentExtensionInfo( const base::DictionaryValue* manifest, const base::FilePath& directory, bool internal) : manifest(manifest), root_directory(directory), internal(internal) { if (!root_directory.IsAbsolute()) { // This path structure is required by // url_request_util::MaybeCreateURLRequestResourceBundleJob. CHECK(base::PathService::Get(chrome::DIR_RESOURCES, &root_directory)); root_directory = root_directory.Append(directory); } } void CefExtensionSystem::InitPrefs() { store_factory_ = new CefValueStoreFactory(browser_context_->GetPath()); Profile* profile = Profile::FromBrowserContext(browser_context_); // Two state stores. The latter, which contains declarative rules, must be // loaded immediately so that the rules are ready before we issue network // requests. state_store_.reset(new StateStore( profile, store_factory_, ValueStoreFrontend::BackendType::STATE, true)); rules_store_.reset(new StateStore( profile, store_factory_, ValueStoreFrontend::BackendType::RULES, false)); } // Implementation based on ComponentLoader::CreateExtension. scoped_refptr CefExtensionSystem::CreateExtension( const ComponentExtensionInfo& info, std::string* utf8_error) { // TODO(abarth): We should REQUIRE_MODERN_MANIFEST_VERSION once we've updated // our component extensions to the new manifest version. int flags = 0; if (info.internal) { // Internal extensions must have kPublicKey in the manifest. flags |= Extension::REQUIRE_KEY; } return Extension::Create( info.root_directory, // Tests should continue to use the Manifest::COMMAND_LINE value here // Some Chrome APIs will cause undesired effects if this is incorrect // e.g.: alarms API has 1 minute minimum applied to Packed Extensions info.internal ? Manifest::COMPONENT : Manifest::COMMAND_LINE, *info.manifest, flags, utf8_error); } // Implementation based on ComponentLoader::Load and // ExtensionService::AddExtension. const Extension* CefExtensionSystem::LoadExtension( const ComponentExtensionInfo& info, CefRefPtr loader_context, CefRefPtr handler) { std::string error; scoped_refptr extension(CreateExtension(info, &error)); if (!extension.get()) { LOG(ERROR) << error; return nullptr; } if (registry_->GetInstalledExtension(extension->id())) { LOG(ERROR) << "Extension with id " << extension->id() << "is already installed"; return nullptr; } CefRefPtr cef_extension = new CefExtensionImpl(extension.get(), loader_context.get(), handler); // Insert first so that callbacks can retrieve the loaded extension. extension_map_.insert(std::make_pair(extension->id(), cef_extension)); cef_extension->OnExtensionLoaded(); // This may trigger additional callbacks. registry_->AddEnabled(extension.get()); NotifyExtensionLoaded(extension.get()); return extension.get(); } // Implementation based on ExtensionService::UnloadExtension. void CefExtensionSystem::UnloadExtension(const std::string& extension_id, UnloadedExtensionReason reason) { // Make sure the extension gets deleted after we return from this function. int include_mask = ExtensionRegistry::EVERYTHING & ~ExtensionRegistry::TERMINATED; scoped_refptr extension( registry_->GetExtensionById(extension_id, include_mask)); // This method can be called via PostTask, so the extension may have been // unloaded by the time this runs. if (!extension.get()) { // In case the extension may have crashed/uninstalled. Allow the profile to // clean up its RequestContexts. UnregisterExtensionWithRequestContexts(extension_id, reason); return; } if (registry_->disabled_extensions().Contains(extension->id())) { registry_->RemoveDisabled(extension->id()); // Make sure the profile cleans up its RequestContexts when an already // disabled extension is unloaded (since they are also tracking the disabled // extensions). UnregisterExtensionWithRequestContexts(extension_id, reason); // Don't send the unloaded notification. It was sent when the extension // was disabled. } else { // Remove the extension from the enabled list. registry_->RemoveEnabled(extension->id()); NotifyExtensionUnloaded(extension.get(), reason); } content::NotificationService::current()->Notify( extensions::NOTIFICATION_EXTENSION_REMOVED, content::Source(browser_context_), content::Details(extension.get())); } // Implementation based on ExtensionService::NotifyExtensionLoaded. void CefExtensionSystem::NotifyExtensionLoaded(const Extension* extension) { // The URLRequestContexts need to be first to know that the extension // was loaded, otherwise a race can arise where a renderer that is created // for the extension may try to load an extension URL with an extension id // that the request context doesn't yet know about. The profile is responsible // for ensuring its URLRequestContexts appropriately discover the loaded // extension. RegisterExtensionWithRequestContexts( extension, base::Bind(&CefExtensionSystem::OnExtensionRegisteredWithRequestContexts, weak_ptr_factory_.GetWeakPtr(), base::WrapRefCounted(extension))); // Tell renderers about the loaded extension. renderer_helper_->OnExtensionLoaded(*extension); // Tell subsystems that use the ExtensionRegistryObserver::OnExtensionLoaded // about the new extension. // // NOTE: It is important that this happen after notifying the renderers about // the new extensions so that if we navigate to an extension URL in // ExtensionRegistryObserver::OnExtensionLoaded the renderer is guaranteed to // know about it. registry_->TriggerOnLoaded(extension); // Register plugins included with the extension. // Implementation based on PluginManager::OnExtensionLoaded. const MimeTypesHandler* handler = MimeTypesHandler::GetHandler(extension); if (handler && !handler->handler_url().empty()) { content::WebPluginInfo info; info.type = content::WebPluginInfo::PLUGIN_TYPE_BROWSER_PLUGIN; info.name = base::UTF8ToUTF16(extension->name()); info.path = base::FilePath::FromUTF8Unsafe(extension->url().spec()); for (std::set::const_iterator mime_type = handler->mime_type_set().begin(); mime_type != handler->mime_type_set().end(); ++mime_type) { content::WebPluginMimeType mime_type_info; mime_type_info.mime_type = *mime_type; base::FilePath::StringType file_extension; if (net::GetPreferredExtensionForMimeType(*mime_type, &file_extension)) { mime_type_info.file_extensions.push_back( base::FilePath(file_extension).AsUTF8Unsafe()); } info.mime_types.push_back(mime_type_info); } content::PluginService* plugin_service = content::PluginService::GetInstance(); plugin_service->RefreshPlugins(); plugin_service->RegisterInternalPlugin(info, true); } } void CefExtensionSystem::OnExtensionRegisteredWithRequestContexts( scoped_refptr extension) { registry_->AddReady(extension); if (registry_->enabled_extensions().Contains(extension->id())) registry_->TriggerOnReady(extension.get()); } // Implementation based on ExtensionService::NotifyExtensionUnloaded. void CefExtensionSystem::NotifyExtensionUnloaded( const Extension* extension, UnloadedExtensionReason reason) { // Unregister plugins included with the extension. // Implementation based on PluginManager::OnExtensionUnloaded. const MimeTypesHandler* handler = MimeTypesHandler::GetHandler(extension); if (handler && !handler->handler_url().empty()) { base::FilePath path = base::FilePath::FromUTF8Unsafe(extension->url().spec()); content::PluginService* plugin_service = content::PluginService::GetInstance(); plugin_service->UnregisterInternalPlugin(path); plugin_service->RefreshPlugins(); } registry_->TriggerOnUnloaded(extension, reason); // Tell renderers about the unloaded extension. renderer_helper_->OnExtensionUnloaded(*extension); UnregisterExtensionWithRequestContexts(extension->id(), reason); } } // namespace extensions