// Copyright 2021 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "extensions/browser/script_injection_tracker.h" #include "base/check_is_test.h" #include "base/containers/contains.h" #include "base/memory/ptr_util.h" #include "base/memory/raw_ref.h" #include "base/ranges/algorithm.h" #include "base/strings/string_number_conversions.h" #include "base/trace_event/typed_macros.h" #include "components/guest_view/browser/guest_view_base.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/global_routing_id.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/browser_frame_context_data.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/guest_view/web_view/web_view_content_script_manager.h" #include "extensions/browser/url_loader_factory_manager.h" #include "extensions/browser/user_script_manager.h" #include "extensions/common/constants.h" #include "extensions/common/content_script_injection_url_getter.h" #include "extensions/common/extension.h" #include "extensions/common/manifest_handlers/content_scripts_handler.h" #include "extensions/common/permissions/permissions_data.h" #include "extensions/common/trace_util.h" #include "extensions/common/user_script.h" #include "services/metrics/public/cpp/metrics_utils.h" #include "services/metrics/public/cpp/ukm_builders.h" using perfetto::protos::pbzero::ChromeTrackEvent; namespace extensions { namespace { // Helper for lazily attaching ExtensionIdSet to a RenderProcessHost. Used to // track the set of extensions which have injected a JS script into a // RenderProcessHost. // // We track script injection per-RenderProcessHost: // 1. This matches the real security boundary that Site Isolation uses (the // boundary of OS processes) and follows the precedent of // content::ChildProcessSecurityPolicy. // 2. This robustly handles initial empty documents (see the *InitialEmptyDoc* // tests in //script_injection_tracker_browsertest.cc) and isn't impacted // by ReadyToCommit races associated with DocumentUserData. // For more information see: // https://docs.google.com/document/d/1MFprp2ss2r9RNamJ7Jxva1bvRZvec3rzGceDGoJ6vW0/edit# class RenderProcessHostUserData : public base::SupportsUserData::Data { public: static const RenderProcessHostUserData* Get( const content::RenderProcessHost& process) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); return static_cast( process.GetUserData(kUserDataKey)); } static RenderProcessHostUserData& GetOrCreate( content::RenderProcessHost& process) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); auto* self = static_cast( process.GetUserData(kUserDataKey)); if (!self) { // Create a new RenderProcessHostUserData if needed. The ownership is // passed to the `process` (i.e. the new RenderProcessHostUserData will be // destroyed at the same time as the `process` - this is why we don't need // to purge or destroy the set from within ScriptInjectionTracker). auto owned_self = base::WrapUnique(new RenderProcessHostUserData(process)); self = owned_self.get(); process.SetUserData(kUserDataKey, std::move(owned_self)); } DCHECK(self); return *self; } // base::SupportsUserData::Data override: ~RenderProcessHostUserData() override { TRACE_EVENT_END("extensions", perfetto::Track::FromPointer(this), ChromeTrackEvent::kRenderProcessHost, *process_); } bool HasScript(ScriptInjectionTracker::ScriptType script_type, const ExtensionId& extension_id) const { return base::Contains(GetScripts(script_type), extension_id); } void AddScript(ScriptInjectionTracker::ScriptType script_type, const ExtensionId& extension_id) { TRACE_EVENT_INSTANT( "extensions", "ScriptInjectionTracker::RenderProcessHostUserData::AddScript", ChromeTrackEvent::kRenderProcessHost, *process_, ChromeTrackEvent::kChromeExtensionId, ExtensionIdForTracing(extension_id)); GetScripts(script_type).insert(extension_id); } const ExtensionIdSet& content_scripts() const { return content_scripts_; } const ExtensionIdSet& user_scripts() const { return user_scripts_; } private: explicit RenderProcessHostUserData(content::RenderProcessHost& process) : process_(process) { TRACE_EVENT_BEGIN("extensions", "ScriptInjectionTracker::RenderProcessHostUserData", perfetto::Track::FromPointer(this), ChromeTrackEvent::kRenderProcessHost, *process_); } const ExtensionIdSet& GetScripts( ScriptInjectionTracker::ScriptType script_type) const { switch (script_type) { case ScriptInjectionTracker::ScriptType::kContentScript: return content_scripts_; case ScriptInjectionTracker::ScriptType::kUserScript: return user_scripts_; } } ExtensionIdSet& GetScripts(ScriptInjectionTracker::ScriptType script_type) { return const_cast( const_cast(this)->GetScripts( script_type)); } static const char* kUserDataKey; // The sets of extension ids that have *ever* injected a content script or // user script into this particular renderer process. This is the core data // maintained by the ScriptInjectionTracker. ExtensionIdSet content_scripts_; ExtensionIdSet user_scripts_; // Only used for tracing. const raw_ref process_; }; const char* RenderProcessHostUserData::kUserDataKey = "ScriptInjectionTracker's data"; std::vector GetVectorFromScriptList( const UserScriptList& scripts) { std::vector result; result.reserve(scripts.size()); for (const auto& script : scripts) { result.push_back(script.get()); } return result; } // Returns all the loaded dynamic scripts with `source` of `extension_id` on // `frame`. std::vector GetLoadedDynamicScripts( const ExtensionId& extension_id, UserScript::Source source, content::RenderProcessHost& process) { // `manager` can be null for some unit tests which do not initialize the // ExtensionSystem. UserScriptManager* manager = ExtensionSystem::Get(process.GetBrowserContext())->user_script_manager(); if (!manager) { CHECK_IS_TEST(); return std::vector(); } const UserScriptList& loaded_dynamic_scripts = manager->GetUserScriptLoaderForExtension(extension_id) ->GetLoadedDynamicScripts(); std::vector scripts; for (auto& loaded_script : loaded_dynamic_scripts) { if (loaded_script->GetSource() == source) { scripts.push_back(loaded_script.get()); } } return scripts; } // This function approximates ScriptContext::GetEffectiveDocumentURLForInjection // from the renderer side. GURL GetEffectiveDocumentURL( content::RenderFrameHost* frame, const GURL& document_url, MatchOriginAsFallbackBehavior match_origin_as_fallback) { // This is a simplification to avoid calling // `BrowserFrameContextData::CanAccess` which is unable to replicate all of // WebSecurityOrigin::CanAccess checks (e.g. universal access or file // exceptions tracked on the renderer side). This is okay, because our only // caller (DoesContentScriptMatch()) expects false positives. constexpr bool kAllowInaccessibleParents = true; return ContentScriptInjectionUrlGetter::Get( BrowserFrameContextData(frame), document_url, match_origin_as_fallback, kAllowInaccessibleParents); } // Returns whether the extension's scripts can run on `frame`. bool CanExtensionScriptsAffectFrame(content::RenderFrameHost& frame, const Extension& extension) { // Most extension's scripts won't run on webviews. The only ones that may are // those from extensions that can execute script everywhere. auto* guest = guest_view::GuestViewBase::FromRenderFrameHost(&frame); return !guest || PermissionsData::CanExecuteScriptEverywhere( extension.id(), extension.location()); } // Returns whether `extension` will inject any of `scripts` JavaScript content // into the `frame` / `url`. Note that this function ignores CSS content // scripts. This function approximates a subset of checks from // UserScriptSet::GetInjectionForScript (which runs in the renderer process). // Unlike the renderer version, the code below doesn't consider ability to // create an injection host, nor the results of // ScriptInjector::CanExecuteOnFrame, nor the path of `url_patterns`. // Additionally the `effective_url` calculations are also only an approximation. // This is okay, because the top-level doc comment for ScriptInjectionTracker // documents that false positives are expected and why they are okay. bool DoesScriptMatch(const Extension& extension, const UserScript& script, content::RenderFrameHost& frame, const GURL& url) { // ScriptInjectionTracker only needs to track Javascript content scripts (e.g. // doesn't track CSS-only injections). if (script.js_scripts().empty()) { return false; } GURL effective_url = GetEffectiveDocumentURL(&frame, url, script.match_origin_as_fallback()); // Dynamic scripts can only inject when the extension has host permissions for // the url. auto script_source = script.GetSource(); if ((script_source == UserScript::Source::kDynamicContentScript || script_source == UserScript::Source::kDynamicUserScript) && !extension.permissions_data()->HasHostPermission(effective_url)) { return false; } return script.url_patterns().MatchesSecurityOrigin(effective_url); } void HandleProgrammaticScriptInjection( base::PassKey pass_key, ScriptInjectionTracker::ScriptType script_type, content::RenderFrameHost* frame, const Extension& extension) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // Store `extension.id()` in `process_data`. ScriptInjectionTracker never // removes entries from this set - once a renderer process gains an ability to // talk on behalf of a content script, it retains this ability forever. Note // that the `process_data` will be destroyed together with the // RenderProcessHost (see also a comment inside // RenderProcessHostUserData::GetOrCreate). auto& process_data = RenderProcessHostUserData::GetOrCreate(*frame->GetProcess()); process_data.AddScript(script_type, extension.id()); URLLoaderFactoryManager::WillProgrammaticallyInjectContentScript( pass_key, frame, extension); } // Returns whether ``extension` will inject any of `scripts` JavaScript content // into the `frame` / `url`. bool DoScriptsMatch(const Extension& extension, const std::vector& scripts, content::RenderFrameHost& frame, const GURL& url) { return base::ranges::any_of( scripts.begin(), scripts.end(), [&extension, &frame, &url](const UserScript* script) { return DoesScriptMatch(extension, *script, frame, url); }); } // Returns whether an `extension` can inject JavaScript web view scripts into // the `frame` / `url`. bool DoWebViewScripstMatch(const Extension& extension, content::RenderFrameHost& frame) { content::RenderProcessHost& process = *frame.GetProcess(); TRACE_EVENT("extensions", "ScriptInjectionTracker/DoWebViewScripstMatch", ChromeTrackEvent::kRenderProcessHost, process, ChromeTrackEvent::kChromeExtensionId, ExtensionIdForTracing(extension.id())); auto* guest = guest_view::GuestViewBase::FromRenderFrameHost(&frame); if (!guest) { // Not a guest; no webview scripts. return false; } // Return true if `extension` is an owner of `guest` and it registered // content scripts using the `webview.addContentScripts` API. GURL owner_site_url = guest->GetOwnerSiteURL(); if (owner_site_url.SchemeIs(kExtensionScheme) && owner_site_url.host_piece() == extension.id()) { WebViewContentScriptManager* script_manager = WebViewContentScriptManager::Get(frame.GetBrowserContext()); int embedder_process_id = guest->owner_rfh()->GetProcess()->GetID(); std::set script_ids = script_manager->GetContentScriptIDSet( embedder_process_id, guest->view_instance_id()); // Note - more granular checks (e.g. against URL patterns) are desirable // for performance (to avoid creating unnecessary URLLoaderFactory via // URLLoaderFactoryManager), but not necessarily for security (because // there are anyway no OOPIFs inside the webView process - // https://crbug.com/614463). At the same time, more granular checks are // difficult to achieve, because the UserScript objects are not retained // (i.e. only UserScriptIDs are available) by WebViewContentScriptManager. if (!script_ids.empty()) { return true; } } return false; } // Returns whether an `extension` can inject JavaScript static content scripts // into the `frame` / `url`. The `url` might be either the last committed URL // of `frame` or the target of a ReadyToCommit navigation in `frame`. bool DoStaticContentScriptsMatch(const Extension& extension, content::RenderFrameHost& frame, const GURL& url) { content::RenderProcessHost& process = *frame.GetProcess(); TRACE_EVENT("extensions", "ScriptInjectionTracker/DoStaticContentScriptMatch", ChromeTrackEvent::kRenderProcessHost, process, ChromeTrackEvent::kChromeExtensionId, ExtensionIdForTracing(extension.id())); if (!CanExtensionScriptsAffectFrame(frame, extension)) { return false; } std::vector static_content_scripts = GetVectorFromScriptList( ContentScriptsInfo::GetContentScripts(&extension)); return DoScriptsMatch(extension, static_content_scripts, frame, url); } // Returns whether an `extension` can inject JavaScript dynamic content scripts // into the `frame` / `url`. The `url` might be either the last committed // URL of `frame` or the target of a ReadyToCommit navigation in `frame`. bool DoDynamicContentScriptsMatch(const Extension& extension, content::RenderFrameHost& frame, const GURL& url) { content::RenderProcessHost& process = *frame.GetProcess(); TRACE_EVENT("extensions", "ScriptInjectionTracker/DoDynamicContentScriptsMatch", ChromeTrackEvent::kRenderProcessHost, process, ChromeTrackEvent::kChromeExtensionId, ExtensionIdForTracing(extension.id())); if (!CanExtensionScriptsAffectFrame(frame, extension)) { return false; } std::vector dynamic_user_scripts = GetLoadedDynamicScripts( extension.id(), UserScript::Source::kDynamicContentScript, process); return DoScriptsMatch(extension, dynamic_user_scripts, frame, url); } // Returns whether an `extension` can inject JavaScript dynamic user scripts // into the `frame` / `url`. The `url` might be either the last committed URL // of `frame` or the target of a ReadyToCommit navigation in `frame`. bool DoUserScriptsMatch(const Extension& extension, content::RenderFrameHost& frame, const GURL& url) { content::RenderProcessHost& process = *frame.GetProcess(); TRACE_EVENT("extensions", "ScriptInjectionTracker/DoUserScriptsMatch", ChromeTrackEvent::kRenderProcessHost, process, ChromeTrackEvent::kChromeExtensionId, ExtensionIdForTracing(extension.id())); if (!CanExtensionScriptsAffectFrame(frame, extension)) { return false; } std::vector dynamic_user_scripts = GetLoadedDynamicScripts( extension.id(), UserScript::Source::kDynamicUserScript, process); return DoScriptsMatch(extension, dynamic_user_scripts, frame, url); } // Returns all the extensions injecting content scripts into the `frame` / // `url`. std::vector GetExtensionsInjectingContentScripts( const ExtensionSet& extensions, content::RenderFrameHost& frame, const GURL& url) { std::vector extensions_injecting_scripts; for (const auto& it : extensions) { const Extension& extension = *it; if (DoWebViewScripstMatch(extension, frame) || DoStaticContentScriptsMatch(extension, frame, url) || DoDynamicContentScriptsMatch(extension, frame, url)) { extensions_injecting_scripts.push_back(&extension); } } return extensions_injecting_scripts; } // Adds all scripts from `extension` that matches the `process` renderers to the // process data. void AddMatchingScriptsToProcess(const Extension& extension, content::RenderProcessHost& process) { bool any_frame_matches_content_scripts = false; bool any_frame_matches_user_scripts = false; process.ForEachRenderFrameHost([&any_frame_matches_content_scripts, &any_frame_matches_user_scripts, &extension](content::RenderFrameHost* frame) { const GURL& url = frame->GetLastCommittedURL(); if (!any_frame_matches_content_scripts) { any_frame_matches_content_scripts = DoWebViewScripstMatch(extension, *frame) || DoStaticContentScriptsMatch(extension, *frame, url) || DoDynamicContentScriptsMatch(extension, *frame, url); } if (!any_frame_matches_user_scripts) { any_frame_matches_user_scripts = DoUserScriptsMatch(extension, *frame, url); } }); if (any_frame_matches_content_scripts || any_frame_matches_user_scripts) { auto& process_data = RenderProcessHostUserData::GetOrCreate(process); if (any_frame_matches_content_scripts) { process_data.AddScript(ScriptInjectionTracker::ScriptType::kContentScript, extension.id()); } if (any_frame_matches_user_scripts) { process_data.AddScript(ScriptInjectionTracker::ScriptType::kUserScript, extension.id()); } } } // Returns all the extensions injecting user scripts into the `frame` / `url`. std::vector GetExtensionsInjectingUserScripts( const ExtensionSet& extensions, content::RenderFrameHost& frame, const GURL& url) { std::vector extensions_injecting_scripts; for (const auto& it : extensions) { const Extension& extension = *it; if (DoUserScriptsMatch(extension, frame, url)) { extensions_injecting_scripts.push_back(&extension); } } return extensions_injecting_scripts; } void RecordUkm(content::NavigationHandle* navigation, int extensions_injecting_content_script_count) { using PermissionID = extensions::mojom::APIPermissionID; const ExtensionSet& enabled_extensions = ExtensionRegistry::Get( navigation->GetRenderFrameHost()->GetProcess()->GetBrowserContext()) ->enabled_extensions(); int enabled_extension_count = 0; int enabled_extension_count_has_host_permissions = 0; int web_request_permission_count = 0; int web_request_auth_provider_permission_count = 0; int web_request_blocking_permission_count = 0; int declarative_net_request_permission_count = 0; int declarative_net_request_feedback_permission_count = 0; int declarative_net_request_with_host_access_permission_count = 0; int declarative_web_request_permission_count = 0; for (const scoped_refptr& extension : enabled_extensions) { if (!extension->is_extension()) { continue; } // Ignore component extensions. if (Manifest::IsComponentLocation(extension->location())) { continue; } enabled_extension_count++; const PermissionsData* permissions = extension->permissions_data(); if (!permissions) { continue; } if (!permissions->HasHostPermission(navigation->GetURL())) { continue; } enabled_extension_count_has_host_permissions++; if (permissions->HasAPIPermission(PermissionID::kWebRequest)) { web_request_permission_count++; } if (permissions->HasAPIPermission(PermissionID::kWebRequestAuthProvider)) { web_request_auth_provider_permission_count++; } if (permissions->HasAPIPermission(PermissionID::kWebRequestBlocking)) { web_request_blocking_permission_count++; } if (permissions->HasAPIPermission(PermissionID::kDeclarativeNetRequest)) { declarative_net_request_permission_count++; } if (permissions->HasAPIPermission( PermissionID::kDeclarativeNetRequestFeedback)) { declarative_net_request_feedback_permission_count++; } if (permissions->HasAPIPermission( PermissionID::kDeclarativeNetRequestWithHostAccess)) { declarative_net_request_with_host_access_permission_count++; } if (permissions->HasAPIPermission(PermissionID::kDeclarativeWebRequest)) { declarative_web_request_permission_count++; } } const double kBucketSpacing = 2; ukm::builders::Extensions_OnNavigation(navigation->GetNextPageUkmSourceId()) .SetEnabledExtensionCount( ukm::GetExponentialBucketMin(enabled_extension_count, kBucketSpacing)) .SetEnabledExtensionCount_InjectContentScript( ukm::GetExponentialBucketMin( extensions_injecting_content_script_count, kBucketSpacing)) .SetEnabledExtensionCount_HaveHostPermissions( ukm::GetExponentialBucketMin( enabled_extension_count_has_host_permissions, kBucketSpacing)) .SetWebRequestPermissionCount(ukm::GetExponentialBucketMin( web_request_permission_count, kBucketSpacing)) .SetWebRequestAuthProviderPermissionCount(ukm::GetExponentialBucketMin( web_request_auth_provider_permission_count, kBucketSpacing)) .SetWebRequestBlockingPermissionCount(ukm::GetExponentialBucketMin( web_request_blocking_permission_count, kBucketSpacing)) .SetDeclarativeNetRequestPermissionCount(ukm::GetExponentialBucketMin( declarative_net_request_permission_count, kBucketSpacing)) .SetDeclarativeNetRequestFeedbackPermissionCount( ukm::GetExponentialBucketMin( declarative_net_request_feedback_permission_count, kBucketSpacing)) .SetDeclarativeNetRequestWithHostAccessPermissionCount( ukm::GetExponentialBucketMin( declarative_net_request_with_host_access_permission_count, kBucketSpacing)) .SetDeclarativeWebRequestPermissionCount(ukm::GetExponentialBucketMin( declarative_web_request_permission_count, kBucketSpacing)) .Record(ukm::UkmRecorder::Get()); } const Extension* FindExtensionByHostId(content::BrowserContext* browser_context, const mojom::HostID& host_id) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); switch (host_id.type) { // TODO(cmp): Investigate whether Controlled Frame support is needed in // ScriptInjectionTracker. case mojom::HostID::HostType::kControlledFrameEmbedder: case mojom::HostID::HostType::kWebUi: // ScriptInjectionTracker only tracks extensions. return nullptr; case mojom::HostID::HostType::kExtensions: break; } const ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context); DCHECK(registry); // WillExecuteCode and DidUpdateScriptsInRenderer // shouldn't happen during shutdown. const Extension* extension = registry->enabled_extensions().GetByID(host_id.id); return extension; } // Stores extensions injecting scripts with `script_type` in `process` data. void StoreExtensionsInjectingScripts( const std::vector& extensions, ScriptInjectionTracker::ScriptType script_type, content::RenderProcessHost& process) { // ContentScriptTracker never removes entries from this set - once a // renderer process gains an ability to talk on behalf of a content script, // it retains this ability forever. Note that the `process_data` will be // destroyed together with the RenderProcessHost (see also a comment inside // RenderProcessHostUserData::GetOrCreate). auto& process_data = RenderProcessHostUserData::GetOrCreate(process); for (const Extension* extension : extensions) { process_data.AddScript(script_type, extension->id()); } } bool DidProcessRunScriptFromExtension( ScriptInjectionTracker::ScriptType script_type, const content::RenderProcessHost& process, const ExtensionId& extension_id) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK(!extension_id.empty()); // Check if we've been notified about the content script injection via // ReadyToCommitNavigation or WillExecuteCode methods. const auto* process_data = RenderProcessHostUserData::Get(process); if (!process_data) { return false; } return process_data->HasScript(script_type, extension_id); } } // namespace // static ExtensionIdSet ScriptInjectionTracker::GetExtensionsThatRanContentScriptsInProcess( const content::RenderProcessHost& process) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); const auto* process_data = RenderProcessHostUserData::Get(process); if (!process_data) { return {}; } return process_data->content_scripts(); } // static bool ScriptInjectionTracker::DidProcessRunContentScriptFromExtension( const content::RenderProcessHost& process, const ExtensionId& extension_id) { return DidProcessRunScriptFromExtension(ScriptType::kContentScript, process, extension_id); } // static bool ScriptInjectionTracker::DidProcessRunUserScriptFromExtension( const content::RenderProcessHost& process, const ExtensionId& extension_id) { return DidProcessRunScriptFromExtension(ScriptType::kUserScript, process, extension_id); } // static void ScriptInjectionTracker::ReadyToCommitNavigation( base::PassKey pass_key, content::NavigationHandle* navigation) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); content::RenderFrameHost& frame = *navigation->GetRenderFrameHost(); content::RenderProcessHost& process = *frame.GetProcess(); TRACE_EVENT("extensions", "ScriptInjectionTracker::ReadyToCommitNavigation", ChromeTrackEvent::kRenderProcessHost, process); const GURL& url = navigation->GetURL(); const ExtensionRegistry* registry = ExtensionRegistry::Get(process.GetBrowserContext()); DCHECK(registry); // This method shouldn't be called during shutdown. const ExtensionSet& extensions = registry->enabled_extensions(); // Need to call StoreExtensionsInjectingScripts at ReadyToCommitNavigation // time to deal with a (hypothetical, not confirmed by tests) race condition // where Browser process sends Commit IPC and then immediately disables the // extension. In this scenario, the renderer may run some content scripts, // even though at DidCommit time the Browser will see that the extension has // been disabled. std::vector extensions_injecting_content_scripts = GetExtensionsInjectingContentScripts(extensions, frame, url); std::vector extensions_injecting_user_scripts = GetExtensionsInjectingUserScripts(extensions, frame, url); StoreExtensionsInjectingScripts( extensions_injecting_content_scripts, ScriptInjectionTracker::ScriptType::kContentScript, process); StoreExtensionsInjectingScripts( extensions_injecting_user_scripts, ScriptInjectionTracker::ScriptType::kUserScript, process); // Notify URLLoaderFactoryManager for both user and content scripts. This // needs to happen at ReadyToCommitNavigation time (i.e. before constructing a // URLLoaderFactory that will be sent to the Renderer in a Commit IPC). // TODO(crbug.com/1495177): This should only use webview scripts, since it's // not needed for all extensions. extensions_injecting_content_scripts.reserve( extensions_injecting_content_scripts.size() + extensions_injecting_user_scripts.size()); extensions_injecting_content_scripts.insert( extensions_injecting_content_scripts.end(), extensions_injecting_user_scripts.begin(), extensions_injecting_user_scripts.end()); URLLoaderFactoryManager::WillInjectContentScriptsWhenNavigationCommits( base::PassKey(), navigation, extensions_injecting_content_scripts); } // static void ScriptInjectionTracker::DidFinishNavigation( base::PassKey pass_key, content::NavigationHandle* navigation) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // Only consider cross-document navigations that actually commit. (Documents // associated with same-document navigations should have already been // processed by an earlier DidFinishNavigation. Navigations that don't // commit/load won't inject content scripts. Content script injections are // primarily driven by URL matching and therefore failed navigations may still // end up injecting content scripts into the error page. Pre-rendered pages // already ran content scripts at the initial navigation and don't need to // run them again on activation.) if (!navigation->HasCommitted() || navigation->IsSameDocument() || navigation->IsPrerenderedPageActivation()) { return; } content::RenderFrameHost& frame = *navigation->GetRenderFrameHost(); content::RenderProcessHost& process = *frame.GetProcess(); TRACE_EVENT("extensions", "ScriptInjectionTracker::DidFinishNavigation", ChromeTrackEvent::kRenderProcessHost, process); const GURL& url = navigation->GetURL(); const ExtensionRegistry* registry = ExtensionRegistry::Get(process.GetBrowserContext()); DCHECK(registry); // This method shouldn't be called during shutdown. const ExtensionSet& extensions = registry->enabled_extensions(); // Calling StoreExtensionsInjectingScripts in response to DidCommit IPC is // required for correct handling of the race condition from // https://crbug.com/1312125. std::vector extensions_injecting_content_scripts = GetExtensionsInjectingContentScripts(extensions, frame, url); std::vector extensions_injecting_user_scripts = GetExtensionsInjectingUserScripts(extensions, frame, url); StoreExtensionsInjectingScripts( extensions_injecting_content_scripts, ScriptInjectionTracker::ScriptType::kContentScript, process); StoreExtensionsInjectingScripts( extensions_injecting_user_scripts, ScriptInjectionTracker::ScriptType::kUserScript, process); int num_extensions_injecting_scripts = extensions_injecting_content_scripts.size() + extensions_injecting_user_scripts.size(); RecordUkm(navigation, num_extensions_injecting_scripts); } // static void ScriptInjectionTracker::WillExecuteCode( base::PassKey pass_key, ScriptType script_type, content::RenderFrameHost* frame, const mojom::HostID& host_id) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); content::RenderProcessHost& process = *frame->GetProcess(); TRACE_EVENT("extensions", "ScriptInjectionTracker::WillExecuteCode/1", ChromeTrackEvent::kRenderProcessHost, process, ChromeTrackEvent::kChromeExtensionId, ExtensionIdForTracing(host_id.id)); const Extension* extension = FindExtensionByHostId(process.GetBrowserContext(), host_id); if (!extension) { return; } HandleProgrammaticScriptInjection(PassKey(), script_type, frame, *extension); } // static void ScriptInjectionTracker::WillExecuteCode( base::PassKey pass_key, content::RenderFrameHost* frame, const Extension& extension) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); TRACE_EVENT("extensions", "ScriptInjectionTracker::WillExecuteCode/2", ChromeTrackEvent::kRenderProcessHost, *frame->GetProcess(), ChromeTrackEvent::kChromeExtensionId, ExtensionIdForTracing(extension.id())); // Declarative content scripts are only ever of a kContentScript type and // never handle user scripts. HandleProgrammaticScriptInjection(PassKey(), ScriptType::kContentScript, frame, extension); } // static void ScriptInjectionTracker::DidUpdateScriptsInRenderer( base::PassKey pass_key, const mojom::HostID& host_id, content::RenderProcessHost& process) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); TRACE_EVENT( "extensions", "ScriptInjectionTracker::DidUpdateScriptsInRenderer", ChromeTrackEvent::kRenderProcessHost, process, ChromeTrackEvent::kChromeExtensionId, ExtensionIdForTracing(host_id.id)); scoped_refptr extension = FindExtensionByHostId(process.GetBrowserContext(), host_id); if (!extension) { return; } AddMatchingScriptsToProcess(*extension, process); } // static void ScriptInjectionTracker::DidUpdatePermissionsInRenderer( base::PassKey pass_key, const Extension& extension, content::RenderProcessHost& process) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); AddMatchingScriptsToProcess(extension, process); } // static bool ScriptInjectionTracker::DoStaticContentScriptsMatchForTesting( const Extension& extension, content::RenderFrameHost* frame, const GURL& url) { return DoStaticContentScriptsMatch(extension, *frame, url); } namespace debug { namespace { base::debug::CrashKeyString* GetRegistryStatusCrashKey() { static auto* crash_key = base::debug::AllocateCrashKeyString( "extension_registry_status", base::debug::CrashKeySize::Size256); return crash_key; } std::string GetRegistryStatusValue(const ExtensionId& extension_id, content::BrowserContext& browser_context) { std::string result = "status="; ExtensionRegistry* registry = ExtensionRegistry::Get(&browser_context); if (registry->enabled_extensions().Contains(extension_id)) { result += "enabled,"; } if (registry->disabled_extensions().Contains(extension_id)) { result += "disabled,"; } if (registry->terminated_extensions().Contains(extension_id)) { result += "terminated,"; } if (registry->blocklisted_extensions().Contains(extension_id)) { result += "blocklisted,"; } if (registry->blocked_extensions().Contains(extension_id)) { result += "blocked,"; } if (registry->ready_extensions().Contains(extension_id)) { result += "ready,"; } return result; } base::debug::CrashKeyString* GetIsIncognitoCrashKey() { static auto* crash_key = base::debug::AllocateCrashKeyString( "is_incognito", base::debug::CrashKeySize::Size32); return crash_key; } base::debug::CrashKeyString* GetLastCommittedOriginCrashKey() { static auto* crash_key = base::debug::AllocateCrashKeyString( "script_frame_last_committed_origin", base::debug::CrashKeySize::Size256); return crash_key; } base::debug::CrashKeyString* GetLastCommittedUrlCrashKey() { static auto* crash_key = base::debug::AllocateCrashKeyString( "script_frame_last_committed_url", base::debug::CrashKeySize::Size256); return crash_key; } base::debug::CrashKeyString* GetLifecycleStateCrashKey() { static auto* crash_key = base::debug::AllocateCrashKeyString( "lifecycle_state", base::debug::CrashKeySize::Size32); return crash_key; } base::debug::CrashKeyString* GetIsGuestCrashKey() { static auto* crash_key = base::debug::AllocateCrashKeyString( "is_guest", base::debug::CrashKeySize::Size32); return crash_key; } base::debug::CrashKeyString* GetDoWebViewScriptsMatchCrashKey() { static auto* crash_key = base::debug::AllocateCrashKeyString( "do_web_view_scripts_match", base::debug::CrashKeySize::Size32); return crash_key; } base::debug::CrashKeyString* GetDoStaticContentScriptsMatchCrashKey() { static auto* crash_key = base::debug::AllocateCrashKeyString( "do_static_content_scripts_match", base::debug::CrashKeySize::Size32); return crash_key; } base::debug::CrashKeyString* GetDoDynamicContentScriptsMatchCrashKey() { static auto* crash_key = base::debug::AllocateCrashKeyString( "do_dynamic_content_scripts_match", base::debug::CrashKeySize::Size32); return crash_key; } base::debug::CrashKeyString* GetDoUserScriptsMatchCrashKey() { static auto* crash_key = base::debug::AllocateCrashKeyString( "do_user_scripts_match", base::debug::CrashKeySize::Size32); return crash_key; } const char* BoolToCrashKeyValue(bool value) { return value ? "yes" : "no"; } } // namespace ScopedScriptInjectionTrackerFailureCrashKeys:: ScopedScriptInjectionTrackerFailureCrashKeys( content::BrowserContext& browser_context, const ExtensionId& extension_id) : registry_status_crash_key_( GetRegistryStatusCrashKey(), GetRegistryStatusValue(extension_id, browser_context)), is_incognito_crash_key_( GetIsIncognitoCrashKey(), BoolToCrashKeyValue(browser_context.IsOffTheRecord())) {} ScopedScriptInjectionTrackerFailureCrashKeys:: ScopedScriptInjectionTrackerFailureCrashKeys( content::RenderFrameHost& frame, const ExtensionId& extension_id) : ScopedScriptInjectionTrackerFailureCrashKeys(*frame.GetBrowserContext(), extension_id) { const GURL& frame_url = frame.GetLastCommittedURL(); last_committed_origin_crash_key_.emplace( GetLastCommittedOriginCrashKey(), frame.GetLastCommittedOrigin().GetDebugString()); last_committed_url_crash_key_.emplace(GetLastCommittedUrlCrashKey(), frame_url.possibly_invalid_spec()); lifecycle_state_crash_key_.emplace( GetLifecycleStateCrashKey(), base::NumberToString(static_cast(frame.GetLifecycleState()))); auto* guest = guest_view::GuestViewBase::FromRenderFrameHost(&frame); is_guest_crash_key_.emplace(GetIsGuestCrashKey(), BoolToCrashKeyValue(!!guest)); const ExtensionRegistry* registry = ExtensionRegistry::Get(frame.GetBrowserContext()); CHECK(registry); const Extension* extension = registry->enabled_extensions().GetByID(extension_id); if (extension) { do_web_view_scripts_match_crash_key_.emplace( GetDoWebViewScriptsMatchCrashKey(), BoolToCrashKeyValue(DoWebViewScripstMatch(*extension, frame))); do_static_content_scripts_match_crash_key_.emplace( GetDoStaticContentScriptsMatchCrashKey(), BoolToCrashKeyValue( DoStaticContentScriptsMatch(*extension, frame, frame_url))); do_dynamic_content_scripts_match_crash_key_.emplace( GetDoDynamicContentScriptsMatchCrashKey(), BoolToCrashKeyValue( DoDynamicContentScriptsMatch(*extension, frame, frame_url))); do_user_scripts_match_crash_key_.emplace( GetDoUserScriptsMatchCrashKey(), BoolToCrashKeyValue(DoUserScriptsMatch(*extension, frame, frame_url))); } } ScopedScriptInjectionTrackerFailureCrashKeys:: ~ScopedScriptInjectionTrackerFailureCrashKeys() = default; } // namespace debug } // namespace extensions