| // 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 "extensions/browser/renderer_startup_helper.h" |
| |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.h" |
| #include "components/keyed_service/content/browser_context_dependency_manager.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "extensions/browser/extension_function_dispatcher.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_util.h" |
| #include "extensions/browser/extensions_browser_client.h" |
| #include "extensions/browser/guest_view/web_view/web_view_guest.h" |
| #include "extensions/common/extension_messages.h" |
| #include "extensions/common/extension_set.h" |
| #include "extensions/common/extensions_client.h" |
| #include "extensions/common/features/feature_channel.h" |
| #include "extensions/common/features/feature_session_type.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "ui/base/webui/web_ui_util.h" |
| |
| using content::BrowserContext; |
| |
| namespace extensions { |
| |
| namespace { |
| |
| // Returns whether the |extension| should be loaded in the given |
| // |browser_context|. |
| bool IsExtensionVisibleToContext(const Extension& extension, |
| content::BrowserContext* browser_context) { |
| // Renderers don't need to know about themes. |
| if (extension.is_theme()) |
| return false; |
| |
| // Only extensions enabled in incognito mode should be loaded in an incognito |
| // renderer. However extensions which can't be enabled in the incognito mode |
| // (e.g. platform apps) should also be loaded in an incognito renderer to |
| // ensure connections from incognito tabs to such extensions work. |
| return !browser_context->IsOffTheRecord() || |
| !util::CanBeIncognitoEnabled(&extension) || |
| util::IsIncognitoEnabled(extension.id(), browser_context); |
| } |
| |
| } // namespace |
| |
| RendererStartupHelper::RendererStartupHelper(BrowserContext* browser_context) |
| : browser_context_(browser_context) { |
| DCHECK(browser_context); |
| registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| } |
| |
| RendererStartupHelper::~RendererStartupHelper() {} |
| |
| void RendererStartupHelper::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| switch (type) { |
| case content::NOTIFICATION_RENDERER_PROCESS_CREATED: |
| InitializeProcess( |
| content::Source<content::RenderProcessHost>(source).ptr()); |
| break; |
| case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: |
| // Fall through. |
| case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: |
| // This is needed to take care of the case when a RenderProcessHost is |
| // reused for a different renderer process. |
| UntrackProcess(content::Source<content::RenderProcessHost>(source).ptr()); |
| break; |
| default: |
| NOTREACHED() << "Unexpected notification: " << type; |
| } |
| } |
| |
| void RendererStartupHelper::InitializeProcess( |
| content::RenderProcessHost* process) { |
| ExtensionsBrowserClient* client = ExtensionsBrowserClient::Get(); |
| if (!client->IsSameContext(browser_context_, process->GetBrowserContext())) |
| return; |
| |
| bool activity_logging_enabled = |
| client->IsActivityLoggingEnabled(process->GetBrowserContext()); |
| // We only send the ActivityLoggingEnabled message if it is enabled; otherwise |
| // the default (not enabled) is correct. |
| if (activity_logging_enabled) { |
| process->Send( |
| new ExtensionMsg_SetActivityLoggingEnabled(activity_logging_enabled)); |
| } |
| |
| // Extensions need to know the channel and the session type for API |
| // restrictions. The values are sent to all renderers, as the non-extension |
| // renderers may have content scripts. |
| bool is_lock_screen_context = |
| client->IsLockScreenContext(process->GetBrowserContext()); |
| process->Send(new ExtensionMsg_SetSessionInfo(GetCurrentChannel(), |
| GetCurrentFeatureSessionType(), |
| is_lock_screen_context)); |
| |
| // Platform apps need to know the system font. |
| // TODO(dbeam): this is not the system font in all cases. |
| process->Send(new ExtensionMsg_SetSystemFont(webui::GetFontFamily(), |
| webui::GetFontSize())); |
| |
| // Scripting whitelist. This is modified by tests and must be communicated |
| // to renderers. |
| process->Send(new ExtensionMsg_SetScriptingWhitelist( |
| extensions::ExtensionsClient::Get()->GetScriptingWhitelist())); |
| |
| // If the new render process is a WebView guest process, propagate the WebView |
| // partition ID to it. |
| std::string webview_partition_id = WebViewGuest::GetPartitionID(process); |
| if (!webview_partition_id.empty()) { |
| process->Send(new ExtensionMsg_SetWebViewPartitionID( |
| WebViewGuest::GetPartitionID(process))); |
| } |
| |
| // Load default policy_blocked_hosts and policy_allowed_hosts settings, part |
| // of the ExtensionSettings policy. |
| ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params params; |
| params.default_policy_blocked_hosts = |
| PermissionsData::default_policy_blocked_hosts(); |
| params.default_policy_allowed_hosts = |
| PermissionsData::default_policy_allowed_hosts(); |
| process->Send(new ExtensionMsg_UpdateDefaultPolicyHostRestrictions(params)); |
| |
| // Loaded extensions. |
| std::vector<ExtensionMsg_Loaded_Params> loaded_extensions; |
| BrowserContext* renderer_context = process->GetBrowserContext(); |
| const ExtensionSet& extensions = |
| ExtensionRegistry::Get(browser_context_)->enabled_extensions(); |
| for (const auto& ext : extensions) { |
| // OnLoadedExtension should have already been called for the extension. |
| DCHECK(base::ContainsKey(extension_process_map_, ext->id())); |
| DCHECK(!base::ContainsKey(extension_process_map_[ext->id()], process)); |
| |
| if (!IsExtensionVisibleToContext(*ext, renderer_context)) |
| continue; |
| |
| // TODO(kalman): Only include tab specific permissions for extension |
| // processes, no other process needs it, so it's mildly wasteful. |
| // I am not sure this is possible to know this here, at such a low |
| // level of the stack. Perhaps site isolation can help. |
| bool include_tab_permissions = true; |
| loaded_extensions.push_back( |
| ExtensionMsg_Loaded_Params(ext.get(), include_tab_permissions)); |
| extension_process_map_[ext->id()].insert(process); |
| } |
| |
| // Activate pending extensions. |
| process->Send(new ExtensionMsg_Loaded(loaded_extensions)); |
| auto iter = pending_active_extensions_.find(process); |
| if (iter != pending_active_extensions_.end()) { |
| for (const ExtensionId& id : iter->second) { |
| // The extension should be loaded in the process. |
| DCHECK(extensions.Contains(id)); |
| DCHECK(base::ContainsKey(extension_process_map_, id)); |
| DCHECK(base::ContainsKey(extension_process_map_[id], process)); |
| process->Send(new ExtensionMsg_ActivateExtension(id)); |
| } |
| } |
| |
| initialized_processes_.insert(process); |
| pending_active_extensions_.erase(process); |
| } |
| |
| void RendererStartupHelper::UntrackProcess( |
| content::RenderProcessHost* process) { |
| if (!ExtensionsBrowserClient::Get()->IsSameContext( |
| browser_context_, process->GetBrowserContext())) { |
| return; |
| } |
| |
| initialized_processes_.erase(process); |
| pending_active_extensions_.erase(process); |
| for (auto& extension_process_pair : extension_process_map_) |
| extension_process_pair.second.erase(process); |
| } |
| |
| void RendererStartupHelper::ActivateExtensionInProcess( |
| const Extension& extension, |
| content::RenderProcessHost* process) { |
| // The extension should have been loaded already. Dump without crashing to |
| // debug crbug.com/528026. |
| if (!base::ContainsKey(extension_process_map_, extension.id())) { |
| #if DCHECK_IS_ON() |
| NOTREACHED() << "Extension " << extension.id() |
| << "activated before loading"; |
| #else |
| base::debug::DumpWithoutCrashing(); |
| return; |
| #endif |
| } |
| |
| if (!IsExtensionVisibleToContext(extension, process->GetBrowserContext())) |
| return; |
| |
| if (base::ContainsKey(initialized_processes_, process)) { |
| DCHECK(base::ContainsKey(extension_process_map_[extension.id()], process)); |
| process->Send(new ExtensionMsg_ActivateExtension(extension.id())); |
| } else { |
| pending_active_extensions_[process].insert(extension.id()); |
| } |
| } |
| |
| void RendererStartupHelper::OnExtensionLoaded(const Extension& extension) { |
| // Extension was already loaded. |
| // TODO(crbug.com/708230): Ensure that clients don't call this for an |
| // already loaded extension and change this to a DCHECK. |
| if (base::ContainsKey(extension_process_map_, extension.id())) |
| return; |
| |
| // Mark the extension as loaded. |
| std::set<content::RenderProcessHost*>& loaded_process_set = |
| extension_process_map_[extension.id()]; |
| |
| // IsExtensionVisibleToContext() would filter out themes, but we choose to |
| // return early for performance reasons. |
| if (extension.is_theme()) |
| return; |
| |
| // We don't need to include tab permisisons here, since the extension |
| // was just loaded. |
| // Uninitialized renderers will be informed of the extension load during the |
| // first batch of messages. |
| std::vector<ExtensionMsg_Loaded_Params> params( |
| 1, |
| ExtensionMsg_Loaded_Params(&extension, false /* no tab permissions */)); |
| for (content::RenderProcessHost* process : initialized_processes_) { |
| if (!IsExtensionVisibleToContext(extension, process->GetBrowserContext())) |
| continue; |
| process->Send(new ExtensionMsg_Loaded(params)); |
| loaded_process_set.insert(process); |
| } |
| } |
| |
| void RendererStartupHelper::OnExtensionUnloaded(const Extension& extension) { |
| // Extension is not loaded. |
| // TODO(crbug.com/708230): Ensure that clients call this for a loaded |
| // extension only and change this to a DCHECK. |
| if (!base::ContainsKey(extension_process_map_, extension.id())) |
| return; |
| |
| const std::set<content::RenderProcessHost*>& loaded_process_set = |
| extension_process_map_[extension.id()]; |
| for (content::RenderProcessHost* process : loaded_process_set) { |
| DCHECK(base::ContainsKey(initialized_processes_, process)); |
| process->Send(new ExtensionMsg_Unloaded(extension.id())); |
| } |
| |
| for (auto& process_extensions_pair : pending_active_extensions_) |
| process_extensions_pair.second.erase(extension.id()); |
| |
| // Mark the extension as unloaded. |
| extension_process_map_.erase(extension.id()); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| // static |
| RendererStartupHelper* RendererStartupHelperFactory::GetForBrowserContext( |
| BrowserContext* context) { |
| return static_cast<RendererStartupHelper*>( |
| GetInstance()->GetServiceForBrowserContext(context, true)); |
| } |
| |
| // static |
| RendererStartupHelperFactory* RendererStartupHelperFactory::GetInstance() { |
| return base::Singleton<RendererStartupHelperFactory>::get(); |
| } |
| |
| RendererStartupHelperFactory::RendererStartupHelperFactory() |
| : BrowserContextKeyedServiceFactory( |
| "RendererStartupHelper", |
| BrowserContextDependencyManager::GetInstance()) { |
| // No dependencies on other services. |
| } |
| |
| RendererStartupHelperFactory::~RendererStartupHelperFactory() {} |
| |
| KeyedService* RendererStartupHelperFactory::BuildServiceInstanceFor( |
| content::BrowserContext* context) const { |
| return new RendererStartupHelper(context); |
| } |
| |
| BrowserContext* RendererStartupHelperFactory::GetBrowserContextToUse( |
| BrowserContext* context) const { |
| // Redirected in incognito. |
| return ExtensionsBrowserClient::Get()->GetOriginalContext(context); |
| } |
| |
| bool RendererStartupHelperFactory::ServiceIsCreatedWithBrowserContext() const { |
| return true; |
| } |
| |
| } // namespace extensions |