| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/api/chrome_extensions_api_client.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/extensions/api/automation_internal/chrome_automation_internal_api_delegate.h" |
| #include "chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.h" |
| #include "chrome/browser/extensions/api/declarative_content/default_content_predicate_evaluators.h" |
| #include "chrome/browser/extensions/api/management/chrome_management_api_delegate.h" |
| #include "chrome/browser/extensions/api/messaging/chrome_messaging_delegate.h" |
| #include "chrome/browser/extensions/api/messaging/chrome_native_message_port_dispatcher.h" |
| #include "chrome/browser/extensions/api/metrics_private/chrome_metrics_private_delegate.h" |
| #include "chrome/browser/extensions/api/storage/managed_value_store_cache.h" |
| #include "chrome/browser/extensions/api/storage/sync_value_store_cache.h" |
| #include "chrome/browser/extensions/extension_action_dispatcher.h" |
| #include "chrome/browser/extensions/extension_action_runner.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/favicon/favicon_utils.h" |
| #include "chrome/browser/ui/webui/devtools/devtools_ui.h" |
| #include "chrome/common/buildflags.h" |
| #include "chrome/common/url_constants.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "components/signin/core/browser/signin_header_helper.h" |
| #include "components/value_store/value_store_factory.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "extensions/browser/api/messaging/messaging_delegate.h" |
| #include "extensions/browser/api/messaging/native_message_host.h" |
| #include "extensions/browser/api/messaging/native_message_port.h" |
| #include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h" |
| #include "extensions/browser/api/web_request/web_request_info.h" |
| #include "extensions/browser/extension_action.h" |
| #include "extensions/browser/extension_action_manager.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/supervised_user_extensions_delegate.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "pdf/buildflags.h" |
| #include "printing/buildflags/buildflags.h" |
| #include "services/network/public/mojom/fetch_api.mojom-shared.h" |
| #include "ui/base/page_transition_types.h" |
| #include "ui/base/window_open_disposition.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(ENABLE_GUEST_VIEW) |
| #include "chrome/browser/guest_view/app_view/chrome_app_view_guest_delegate.h" |
| #include "chrome/browser/guest_view/chrome_guest_view_manager_delegate.h" |
| #include "chrome/browser/guest_view/extension_options/chrome_extension_options_guest_delegate.h" |
| #include "chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_guest_delegate.h" |
| #include "chrome/browser/guest_view/web_view/chrome_web_view_guest_delegate.h" |
| #include "chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.h" |
| #include "extensions/browser/guest_view/web_view/web_view_guest.h" |
| #include "extensions/browser/guest_view/web_view/web_view_permission_helper.h" |
| #endif |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| #include "chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.h" |
| #include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h" |
| #include "chrome/browser/search/instant_service.h" |
| #include "chrome/browser/search/instant_service_factory.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "chrome/browser/extensions/api/file_handlers/non_native_file_system_delegate_chromeos.h" |
| #include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate_ash.h" |
| #include "chrome/browser/extensions/api/file_system/consent_provider_impl.h" |
| #include "chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.h" |
| #include "chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.h" |
| #include "chrome/browser/extensions/clipboard_extension_helper_chromeos.h" |
| #include "chromeos/ash/components/settings/cros_settings.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_PRINTING) |
| #include "chrome/browser/printing/printing_init.h" |
| #endif |
| |
| namespace extensions { |
| |
| ChromeExtensionsAPIClient::ChromeExtensionsAPIClient() = default; |
| |
| ChromeExtensionsAPIClient::~ChromeExtensionsAPIClient() = default; |
| |
| void ChromeExtensionsAPIClient::AddAdditionalValueStoreCaches( |
| content::BrowserContext* context, |
| const scoped_refptr<value_store::ValueStoreFactory>& factory, |
| SettingsChangedCallback observer, |
| std::map<settings_namespace::Namespace, |
| raw_ptr<ValueStoreCache, CtnExperimental>>* caches) { |
| // Add support for chrome.storage.sync. |
| (*caches)[settings_namespace::SYNC] = |
| new SyncValueStoreCache(factory, observer, context->GetPath()); |
| |
| // Add support for chrome.storage.managed. |
| (*caches)[settings_namespace::MANAGED] = new ManagedValueStoreCache( |
| *Profile::FromBrowserContext(context), factory, observer); |
| } |
| |
| void ChromeExtensionsAPIClient::AttachWebContentsHelpers( |
| content::WebContents* web_contents) const { |
| favicon::CreateContentFaviconDriverForWebContents(web_contents); |
| #if BUILDFLAG(ENABLE_PRINTING) |
| printing::InitializePrintingForWebContents(web_contents); |
| #endif |
| } |
| |
| bool ChromeExtensionsAPIClient::ShouldHideResponseHeader( |
| const GURL& url, |
| const std::string& header_name) const { |
| // Gaia may send a OAUth2 authorization code in the Dice response header, |
| // which could allow an extension to generate a refresh token for the account. |
| return url.host_piece() == GaiaUrls::GetInstance()->gaia_url().host_piece() && |
| base::CompareCaseInsensitiveASCII(header_name, |
| signin::kDiceResponseHeader) == 0; |
| } |
| |
| bool ChromeExtensionsAPIClient::ShouldHideBrowserNetworkRequest( |
| content::BrowserContext* context, |
| const WebRequestInfo& request) const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Note: browser initiated non-navigation requests are hidden from extensions. |
| // But we do still need to protect some sensitive sub-frame navigation |
| // requests. |
| // Exclude main frame navigation requests. |
| bool is_browser_request = |
| request.render_process_id == -1 && |
| request.web_request_type != WebRequestResourceType::MAIN_FRAME; |
| |
| // Hide requests made by the Devtools frontend. |
| bool is_sensitive_request = |
| is_browser_request && DevToolsUI::IsFrontendResourceURL(request.url); |
| |
| // Hide requests made by the browser on behalf of the NTP. |
| is_sensitive_request |= |
| is_browser_request && |
| request.initiator == |
| url::Origin::Create(GURL(chrome::kChromeUINewTabURL)); |
| |
| // Hide requests made by the browser on behalf of the 1P WebUI NTP. |
| is_sensitive_request |= |
| is_browser_request && |
| request.initiator == |
| url::Origin::Create(GURL(chrome::kChromeUINewTabPageURL)); |
| |
| // Android does not support instant. |
| #if !BUILDFLAG(IS_ANDROID) |
| // Hide requests made by the NTP Instant renderer. |
| auto* instant_service = |
| context |
| ? InstantServiceFactory::GetForProfile(static_cast<Profile*>(context)) |
| : nullptr; |
| if (instant_service) { |
| is_sensitive_request |= |
| instant_service->IsInstantProcess(request.render_process_id); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| return is_sensitive_request; |
| } |
| |
| void ChromeExtensionsAPIClient::NotifyWebRequestWithheld( |
| int render_process_id, |
| int render_frame_id, |
| const ExtensionId& extension_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Track down the ExtensionActionRunner and the extension. Since this is |
| // asynchronous, we could hit a null anywhere along the path. |
| content::RenderFrameHost* render_frame_host = |
| content::RenderFrameHost::FromID(render_process_id, render_frame_id); |
| if (!render_frame_host) { |
| return; |
| } |
| // We don't count subframes and prerendering blocked actions as yet, since |
| // there's no way to surface this to the user. Ignore these (which is also |
| // what we do for content scripts). |
| if (!render_frame_host->IsInPrimaryMainFrame()) { |
| return; |
| } |
| content::WebContents* web_contents = |
| content::WebContents::FromRenderFrameHost(render_frame_host); |
| if (!web_contents) { |
| return; |
| } |
| extensions::ExtensionActionRunner* runner = |
| extensions::ExtensionActionRunner::GetForWebContents(web_contents); |
| if (!runner) { |
| return; |
| } |
| |
| const extensions::Extension* extension = |
| extensions::ExtensionRegistry::Get(web_contents->GetBrowserContext()) |
| ->enabled_extensions() |
| .GetByID(extension_id); |
| if (!extension) { |
| return; |
| } |
| |
| // If the extension doesn't request access to the tab, return. The user |
| // invoking the extension on a site grants access to the tab's origin if |
| // and only if the extension requested it; without requesting the tab, |
| // clicking on the extension won't grant access to the resource. |
| // https://crbug.com/891586. |
| // TODO(crbug.com/40076508): We can remove this if extensions require host |
| // permissions to the initiator, since then we'll never get into this type |
| // of circumstance (the request would be blocked, rather than withheld). |
| if (!extension->permissions_data() |
| ->withheld_permissions() |
| .explicit_hosts() |
| .MatchesURL(render_frame_host->GetLastCommittedURL())) { |
| return; |
| } |
| |
| runner->OnWebRequestBlocked(extension); |
| } |
| |
| void ChromeExtensionsAPIClient::UpdateActionCount( |
| content::BrowserContext* context, |
| const ExtensionId& extension_id, |
| int tab_id, |
| int action_count, |
| bool clear_badge_text) { |
| const Extension* extension = |
| ExtensionRegistry::Get(context)->enabled_extensions().GetByID( |
| extension_id); |
| DCHECK(extension); |
| |
| ExtensionAction* action = |
| ExtensionActionManager::Get(context)->GetExtensionAction(*extension); |
| DCHECK(action); |
| |
| action->SetDNRActionCount(tab_id, action_count); |
| |
| // The badge text should be cleared if |action| contains explicitly set badge |
| // text for the |tab_id| when the preference is then toggled on. In this case, |
| // the matched action count should take precedence over the badge text. |
| if (clear_badge_text) { |
| action->ClearBadgeText(tab_id); |
| } |
| |
| content::WebContents* tab_contents = nullptr; |
| if (ExtensionTabUtil::GetTabById(tab_id, context, /*include_incognito=*/true, |
| &tab_contents) && |
| tab_contents) { |
| ExtensionActionDispatcher::Get(context)->NotifyChange(action, tab_contents, |
| context); |
| } |
| } |
| |
| void ChromeExtensionsAPIClient::ClearActionCount( |
| content::BrowserContext* context, |
| const Extension& extension) { |
| ExtensionAction* action = |
| ExtensionActionManager::Get(context)->GetExtensionAction(extension); |
| DCHECK(action); |
| |
| action->ClearDNRActionCountForAllTabs(); |
| |
| std::vector<content::WebContents*> contents_to_notify = |
| ExtensionTabUtil::GetAllActiveWebContentsForContext( |
| context, /*include_incognito=*/true); |
| |
| for (auto* active_contents : contents_to_notify) { |
| ExtensionActionDispatcher::Get(context)->NotifyChange( |
| action, active_contents, context); |
| } |
| } |
| |
| #if BUILDFLAG(ENABLE_GUEST_VIEW) |
| std::unique_ptr<AppViewGuestDelegate> |
| ChromeExtensionsAPIClient::CreateAppViewGuestDelegate() const { |
| return std::make_unique<ChromeAppViewGuestDelegate>(); |
| } |
| |
| std::unique_ptr<ExtensionOptionsGuestDelegate> |
| ChromeExtensionsAPIClient::CreateExtensionOptionsGuestDelegate( |
| ExtensionOptionsGuest* guest) const { |
| return std::make_unique<ChromeExtensionOptionsGuestDelegate>(guest); |
| } |
| |
| std::unique_ptr<guest_view::GuestViewManagerDelegate> |
| ChromeExtensionsAPIClient::CreateGuestViewManagerDelegate() const { |
| return std::make_unique<ChromeGuestViewManagerDelegate>(); |
| } |
| |
| std::unique_ptr<MimeHandlerViewGuestDelegate> |
| ChromeExtensionsAPIClient::CreateMimeHandlerViewGuestDelegate( |
| MimeHandlerViewGuest* guest) const { |
| return std::make_unique<ChromeMimeHandlerViewGuestDelegate>(); |
| } |
| |
| std::unique_ptr<WebViewGuestDelegate> |
| ChromeExtensionsAPIClient::CreateWebViewGuestDelegate( |
| WebViewGuest* web_view_guest) const { |
| return std::make_unique<ChromeWebViewGuestDelegate>(web_view_guest); |
| } |
| |
| std::unique_ptr<WebViewPermissionHelperDelegate> |
| ChromeExtensionsAPIClient::CreateWebViewPermissionHelperDelegate( |
| WebViewPermissionHelper* web_view_permission_helper) const { |
| return std::make_unique<ChromeWebViewPermissionHelperDelegate>( |
| web_view_permission_helper); |
| } |
| #endif // BUILDFLAG(ENABLE_GUEST_VIEW) |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| std::unique_ptr<ConsentProvider> |
| ChromeExtensionsAPIClient::CreateConsentProvider( |
| content::BrowserContext* browser_context) const { |
| auto consent_provider_delegate = |
| std::make_unique<file_system_api::ConsentProviderDelegate>( |
| Profile::FromBrowserContext(browser_context)); |
| return std::make_unique<file_system_api::ConsentProviderImpl>( |
| std::move(consent_provider_delegate)); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| scoped_refptr<ContentRulesRegistry> |
| ChromeExtensionsAPIClient::CreateContentRulesRegistry( |
| content::BrowserContext* browser_context, |
| RulesCacheDelegate* cache_delegate) const { |
| return base::MakeRefCounted<ChromeContentRulesRegistry>( |
| browser_context, cache_delegate, |
| base::BindOnce(&CreateDefaultContentPredicateEvaluators, |
| base::Unretained(browser_context))); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| bool ChromeExtensionsAPIClient::ShouldAllowDetachingUsb(int vid, |
| int pid) const { |
| const base::Value::List* policy_list; |
| if (ash::CrosSettings::Get()->GetList(ash::kUsbDetachableAllowlist, |
| &policy_list)) { |
| for (const auto& entry : *policy_list) { |
| const base::Value::Dict* entry_dict = entry.GetIfDict(); |
| if (entry_dict && |
| entry_dict->FindInt(ash::kUsbDetachableAllowlistKeyVid) == vid && |
| entry_dict->FindInt(ash::kUsbDetachableAllowlistKeyPid) == pid) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| std::unique_ptr<VirtualKeyboardDelegate> |
| ChromeExtensionsAPIClient::CreateVirtualKeyboardDelegate( |
| content::BrowserContext* browser_context) const { |
| #if BUILDFLAG(IS_CHROMEOS) |
| return std::make_unique<ChromeVirtualKeyboardDelegate>(browser_context); |
| #else |
| return nullptr; |
| #endif |
| } |
| |
| ManagementAPIDelegate* ChromeExtensionsAPIClient::CreateManagementAPIDelegate() |
| const { |
| return new ChromeManagementAPIDelegate; |
| } |
| |
| MetricsPrivateDelegate* ChromeExtensionsAPIClient::GetMetricsPrivateDelegate() { |
| if (!metrics_private_delegate_) { |
| metrics_private_delegate_ = |
| std::make_unique<ChromeMetricsPrivateDelegate>(); |
| } |
| return metrics_private_delegate_.get(); |
| } |
| |
| MessagingDelegate* ChromeExtensionsAPIClient::GetMessagingDelegate() { |
| if (!messaging_delegate_) { |
| messaging_delegate_ = std::make_unique<ChromeMessagingDelegate>(); |
| } |
| return messaging_delegate_.get(); |
| } |
| |
| // The APIs that require these methods are not supported on Android. |
| #if !BUILDFLAG(IS_ANDROID) |
| FileSystemDelegate* ChromeExtensionsAPIClient::GetFileSystemDelegate() { |
| if (!file_system_delegate_) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| file_system_delegate_ = std::make_unique<ChromeFileSystemDelegateAsh>(); |
| #else |
| file_system_delegate_ = std::make_unique<ChromeFileSystemDelegate>(); |
| #endif |
| } |
| return file_system_delegate_.get(); |
| } |
| |
| FeedbackPrivateDelegate* |
| ChromeExtensionsAPIClient::GetFeedbackPrivateDelegate() { |
| if (!feedback_private_delegate_) { |
| feedback_private_delegate_ = |
| std::make_unique<ChromeFeedbackPrivateDelegate>(); |
| } |
| return feedback_private_delegate_.get(); |
| } |
| |
| AutomationInternalApiDelegate* |
| ChromeExtensionsAPIClient::GetAutomationInternalApiDelegate() { |
| if (!extensions_automation_api_delegate_) { |
| extensions_automation_api_delegate_ = |
| std::make_unique<ChromeAutomationInternalApiDelegate>(); |
| } |
| return extensions_automation_api_delegate_.get(); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| MediaPerceptionAPIDelegate* |
| ChromeExtensionsAPIClient::GetMediaPerceptionAPIDelegate() { |
| if (!media_perception_api_delegate_) { |
| media_perception_api_delegate_ = |
| std::make_unique<MediaPerceptionAPIDelegateChromeOS>(); |
| } |
| return media_perception_api_delegate_.get(); |
| } |
| |
| NonNativeFileSystemDelegate* |
| ChromeExtensionsAPIClient::GetNonNativeFileSystemDelegate() { |
| if (!non_native_file_system_delegate_) { |
| non_native_file_system_delegate_ = |
| std::make_unique<NonNativeFileSystemDelegateChromeOS>(); |
| } |
| return non_native_file_system_delegate_.get(); |
| } |
| |
| void ChromeExtensionsAPIClient::SaveImageDataToClipboard( |
| std::vector<uint8_t> image_data, |
| api::clipboard::ImageType type, |
| AdditionalDataItemList additional_items, |
| base::OnceClosure success_callback, |
| base::OnceCallback<void(const std::string&)> error_callback) { |
| if (!clipboard_extension_helper_) { |
| clipboard_extension_helper_ = std::make_unique<ClipboardExtensionHelper>(); |
| } |
| clipboard_extension_helper_->DecodeAndSaveImageData( |
| std::move(image_data), type, std::move(additional_items), |
| std::move(success_callback), std::move(error_callback)); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| std::unique_ptr<NativeMessagePortDispatcher> |
| ChromeExtensionsAPIClient::CreateNativeMessagePortDispatcher( |
| std::unique_ptr<NativeMessageHost> host, |
| base::WeakPtr<NativeMessagePort> port, |
| scoped_refptr<base::SingleThreadTaskRunner> message_service_task_runner) { |
| return std::make_unique<ChromeNativeMessagePortDispatcher>( |
| std::move(host), std::move(port), std::move(message_service_task_runner)); |
| } |
| |
| } // namespace extensions |