// Copyright 2024 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/message_tracker.h" #include #include "base/containers/contains.h" #include "base/functional/callback_helpers.h" #include "base/memory/singleton.h" #include "base/memory/weak_ptr.h" #include "base/metrics/histogram_functions.h" #include "base/strings/stringprintf.h" #include "base/task/single_thread_task_runner.h" #include "base/time/time.h" #include "base/unguessable_token.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "content/public/browser/browser_context.h" #include "extensions/browser/extensions_browser_client.h" #include "extensions/common/mojom/message_port.mojom-shared.h" namespace extensions { namespace { MessageTracker::TestObserver* g_test_observer = nullptr; class MessageTrackerFactory : public BrowserContextKeyedServiceFactory { public: MessageTrackerFactory(); MessageTrackerFactory(const MessageTrackerFactory&) = delete; MessageTrackerFactory& operator=(const MessageTrackerFactory&) = delete; ~MessageTrackerFactory() override = default; MessageTracker* GetForBrowserContext(content::BrowserContext* context); private: // BrowserContextKeyedServiceFactory: content::BrowserContext* GetBrowserContextToUse( content::BrowserContext* context) const override; std::unique_ptr BuildServiceInstanceForBrowserContext( content::BrowserContext* context) const override; }; MessageTrackerFactory::MessageTrackerFactory() : BrowserContextKeyedServiceFactory( "MessageTracker", BrowserContextDependencyManager::GetInstance()) {} MessageTracker* MessageTrackerFactory::GetForBrowserContext( content::BrowserContext* browser_context) { return static_cast( GetServiceForBrowserContext(browser_context, /*create=*/true)); } content::BrowserContext* MessageTrackerFactory::GetBrowserContextToUse( content::BrowserContext* context) const { // One instance will exist across incognito and regular contexts. return ExtensionsBrowserClient::Get()->GetContextRedirectedToOriginal( context); } std::unique_ptr MessageTrackerFactory::BuildServiceInstanceForBrowserContext( content::BrowserContext* context) const { return std::make_unique(context); } const char* GetChannelTypeMetricSuffix(const mojom::ChannelType channel_type) { switch (channel_type) { case mojom::ChannelType::kSendMessage: return "SendMessageChannel"; case mojom::ChannelType::kConnect: return "ConnectChannel"; case mojom::ChannelType::kNative: return "NativeChannel"; case mojom::ChannelType::kSendRequest: return "SendRequestChannel"; } } } // namespace MessageTracker::TrackedStage::TrackedStage(std::string metric_name, mojom::ChannelType channel_type) : metric_name_(std::move(metric_name)), channel_type_(channel_type) {} MessageTracker::MessageTracker(content::BrowserContext* context) : context_(context) {} MessageTracker::~MessageTracker() = default; // static MessageTracker* MessageTracker::Get(content::BrowserContext* browser_context) { return static_cast(GetFactory()) ->GetForBrowserContext(browser_context); } // static BrowserContextKeyedServiceFactory* MessageTracker::GetFactory() { static base::NoDestructor g_factory; return g_factory.get(); } void MessageTracker::StartTrackingMessagingStage( const base::UnguessableToken& tracking_id, std::string base_metric_name, mojom::ChannelType channel_type) { CHECK(!base::Contains(tracked_stages_, tracking_id)); tracked_stages_.emplace( tracking_id, TrackedStage(std::move(base_metric_name), channel_type)); // Eventually emits metrics on whether the message sat in this stage past the // timeout. base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, base::BindOnce(&MessageTracker::OnMessageTimeoutElapsed, weak_factory_.GetWeakPtr(), tracking_id), /*delay=*/stage_timeout_); } void MessageTracker::StopTrackingMessagingStage( const base::UnguessableToken& message_id, OpenChannelMessagePipelineResult result) { TrackedStage* tracked_stage = GetTrackedStage(message_id); // A message might've been delayed too long and already cleared or two paths // might try to stop tracking for `message_id` and one of them finished first. if (!tracked_stage) { return; } // Emit overall metric for all channel types and then for the specific type. base::UmaHistogramEnumeration(tracked_stage->metric_name(), result); const std::string metric_name = base::StringPrintf( "%s.%s", tracked_stage->metric_name(), GetChannelTypeMetricSuffix(tracked_stage->channel_type())); base::UmaHistogramEnumeration(metric_name, result); tracked_stages_.erase(message_id); } MessageTracker::TestObserver::TestObserver() = default; MessageTracker::TestObserver::~TestObserver() = default; // static void MessageTracker::SetObserverForTest( MessageTracker::TestObserver* observer) { g_test_observer = observer; } MessageTracker::TrackedStage* MessageTracker::GetTrackedStage( const base::UnguessableToken& message_id) { auto it = tracked_stages_.find(message_id); return it == tracked_stages_.end() ? nullptr : &it->second; } void MessageTracker::OnMessageTimeoutElapsed( const base::UnguessableToken& message_id) { // Ensure the test observer is notified before we exit the method, but // after we do any work related to handling the registration. base::ScopedClosureRunner notify_test_observer(base::BindOnce( [](const base::UnguessableToken& message_id) { if (g_test_observer) { g_test_observer->OnStageTimeoutRan(message_id); } }, message_id)); TrackedStage* tracked_stage = GetTrackedStage(message_id); // The message is no longer being tracked (e.g. completed process // successfully). if (!tracked_stage) { return; } // Message is delayed too long so emit fail metrics and cleanup from tracking. // Emit overall metric for all channel types and then for the specific type. base::UmaHistogramEnumeration(tracked_stage->metric_name(), OpenChannelMessagePipelineResult::kHung); const std::string metric_name = base::StringPrintf( "%s.%s", tracked_stage->metric_name(), GetChannelTypeMetricSuffix(tracked_stage->channel_type())); base::UmaHistogramEnumeration(metric_name, OpenChannelMessagePipelineResult::kHung); tracked_stages_.erase(message_id); } } // namespace extensions