|  | // Copyright 2015 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "content/browser/tracing/background_tracing_manager_impl.h" | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/callback.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/json/json_reader.h" | 
|  | #include "base/json/json_writer.h" | 
|  | #include "base/location.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/no_destructor.h" | 
|  | #include "base/task/single_thread_task_runner.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "base/time/time.h" | 
|  | #include "base/values.h" | 
|  | #include "build/build_config.h" | 
|  | #include "components/tracing/common/trace_startup_config.h" | 
|  | #include "components/variations/variations_associated_data.h" | 
|  | #include "content/browser/tracing/background_startup_tracing_observer.h" | 
|  | #include "content/browser/tracing/background_tracing_active_scenario.h" | 
|  | #include "content/browser/tracing/background_tracing_agent_client_impl.h" | 
|  | #include "content/browser/tracing/background_tracing_rule.h" | 
|  | #include "content/browser/tracing/tracing_controller_impl.h" | 
|  | #include "content/common/child_process.mojom.h" | 
|  | #include "content/public/browser/browser_child_process_host.h" | 
|  | #include "content/public/browser/browser_task_traits.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/child_process_data.h" | 
|  | #include "content/public/browser/content_browser_client.h" | 
|  | #include "content/public/browser/render_process_host.h" | 
|  | #include "content/public/browser/tracing_delegate.h" | 
|  | #include "content/public/common/child_process_host.h" | 
|  | #include "content/public/common/content_client.h" | 
|  | #include "content/public/common/content_switches.h" | 
|  | #include "services/tracing/public/cpp/perfetto/trace_event_data_source.h" | 
|  | #include "services/tracing/public/cpp/trace_event_agent.h" | 
|  | #include "services/tracing/public/cpp/tracing_features.h" | 
|  | #include "third_party/abseil-cpp/absl/types/optional.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | #include "content/browser/tracing/background_reached_code_tracing_observer_android.h" | 
|  | #endif | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kBackgroundTracingConfig[] = "config"; | 
|  | const char kBackgroundTracingUploadUrl[] = "upload_url"; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // static | 
|  | const char BackgroundTracingManager::kContentTriggerConfig[] = | 
|  | "content-trigger-config"; | 
|  |  | 
|  | // static | 
|  | BackgroundTracingManager& BackgroundTracingManager::GetInstance() { | 
|  | return BackgroundTracingManagerImpl::GetInstance(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void BackgroundTracingManagerImpl::RecordMetric(Metrics metric) { | 
|  | UMA_HISTOGRAM_ENUMERATION("Tracing.Background.ScenarioState", metric, | 
|  | Metrics::NUMBER_OF_BACKGROUND_TRACING_METRICS); | 
|  | } | 
|  |  | 
|  | // static | 
|  | BackgroundTracingManagerImpl& BackgroundTracingManagerImpl::GetInstance() { | 
|  | static base::NoDestructor<BackgroundTracingManagerImpl> manager; | 
|  | return *manager; | 
|  | } | 
|  |  | 
|  | // static | 
|  | void BackgroundTracingManagerImpl::ActivateForProcess( | 
|  | int child_process_id, | 
|  | mojom::ChildProcess* child_process) { | 
|  | // NOTE: Called from any thread. | 
|  |  | 
|  | mojo::PendingRemote<tracing::mojom::BackgroundTracingAgentProvider> | 
|  | pending_provider; | 
|  | child_process->GetBackgroundTracingAgentProvider( | 
|  | pending_provider.InitWithNewPipeAndPassReceiver()); | 
|  |  | 
|  | GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, base::BindOnce(&BackgroundTracingManagerImpl::AddPendingAgent, | 
|  | child_process_id, std::move(pending_provider))); | 
|  | } | 
|  |  | 
|  | BackgroundTracingManagerImpl::BackgroundTracingManagerImpl() | 
|  | : delegate_(GetContentClient()->browser()->GetTracingDelegate()), | 
|  | trigger_handle_ids_(0) { | 
|  | AddEnabledStateObserver(&BackgroundStartupTracingObserver::GetInstance()); | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | AddEnabledStateObserver(&BackgroundReachedCodeTracingObserver::GetInstance()); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | BackgroundTracingManagerImpl::~BackgroundTracingManagerImpl() = default; | 
|  |  | 
|  | void BackgroundTracingManagerImpl::AddMetadataGeneratorFunction() { | 
|  | tracing::TraceEventAgent::GetInstance()->AddMetadataGeneratorFunction( | 
|  | base::BindRepeating(&BackgroundTracingManagerImpl::GenerateMetadataDict, | 
|  | base::Unretained(this))); | 
|  | tracing::TraceEventMetadataSource::GetInstance()->AddGeneratorFunction( | 
|  | base::BindRepeating(&BackgroundTracingManagerImpl::GenerateMetadataProto, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | bool BackgroundTracingManagerImpl::SetActiveScenario( | 
|  | std::unique_ptr<BackgroundTracingConfig> config, | 
|  | DataFiltering data_filtering) { | 
|  | // Pass a null ReceiveCallback to use the default upload behaviour. | 
|  | return SetActiveScenarioWithReceiveCallback( | 
|  | std::move(config), ReceiveCallback(), data_filtering); | 
|  | } | 
|  |  | 
|  | bool BackgroundTracingManagerImpl::SetActiveScenarioWithReceiveCallback( | 
|  | std::unique_ptr<BackgroundTracingConfig> config, | 
|  | ReceiveCallback receive_callback, | 
|  | DataFiltering data_filtering) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (config) { | 
|  | RecordMetric(Metrics::SCENARIO_ACTIVATION_REQUESTED); | 
|  | } | 
|  |  | 
|  | if (active_scenario_ && (active_scenario_->state() != | 
|  | BackgroundTracingActiveScenario::State::kIdle)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // If we don't have a high resolution timer available, traces will be | 
|  | // too inaccurate to be useful. | 
|  | if (!base::TimeTicks::IsHighResolution()) { | 
|  | if (config) { | 
|  | RecordMetric(Metrics::SCENARIO_ACTION_FAILED_LOWRES_CLOCK); | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<BackgroundTracingConfigImpl> config_impl( | 
|  | static_cast<BackgroundTracingConfigImpl*>(config.release())); | 
|  | config_impl = BackgroundStartupTracingObserver::GetInstance() | 
|  | .IncludeStartupConfigIfNeeded(std::move(config_impl)); | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | config_impl = BackgroundReachedCodeTracingObserver::GetInstance() | 
|  | .IncludeReachedCodeConfigIfNeeded(std::move(config_impl)); | 
|  |  | 
|  | if (BackgroundReachedCodeTracingObserver::GetInstance() | 
|  | .enabled_in_current_session()) { | 
|  | data_filtering = DataFiltering::ANONYMIZE_DATA; | 
|  | RecordMetric(Metrics::REACHED_CODE_SCENARIO_TRIGGERED); | 
|  | } else | 
|  | #endif | 
|  | if (BackgroundStartupTracingObserver::GetInstance() | 
|  | .enabled_in_current_session()) { | 
|  | // Anonymize data for startup tracing by default. We currently do not | 
|  | // support storing the config in preferences for next session. | 
|  | data_filtering = DataFiltering::ANONYMIZE_DATA; | 
|  | RecordMetric(Metrics::STARTUP_SCENARIO_TRIGGERED); | 
|  | } else { | 
|  | // If startup config was not set and we're not a SYSTEM scenario (system | 
|  | // might already have started a trace in the background) but tracing was | 
|  | // enabled, then do not set any scenario. | 
|  | if (base::trace_event::TraceLog::GetInstance()->IsEnabled() && | 
|  | config_impl && | 
|  | config_impl->tracing_mode() != BackgroundTracingConfigImpl::SYSTEM) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!config_impl) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool requires_anonymized_data = (data_filtering == ANONYMIZE_DATA); | 
|  | config_impl->set_requires_anonymized_data(requires_anonymized_data); | 
|  |  | 
|  | // TODO(oysteine): Retry when time_until_allowed has elapsed. | 
|  | if (config_impl && delegate_ && | 
|  | !delegate_->IsAllowedToBeginBackgroundScenario( | 
|  | *config_impl.get(), requires_anonymized_data)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | active_scenario_ = std::make_unique<BackgroundTracingActiveScenario>( | 
|  | std::move(config_impl), std::move(receive_callback), | 
|  | base::BindOnce(&BackgroundTracingManagerImpl::OnScenarioAborted, | 
|  | base::Unretained(this))); | 
|  |  | 
|  | // Notify observers before starting tracing. | 
|  | for (auto* observer : background_tracing_observers_) { | 
|  | observer->OnScenarioActivated(active_scenario_->GetConfig()); | 
|  | } | 
|  |  | 
|  | active_scenario_->StartTracingIfConfigNeedsIt(); | 
|  | RecordMetric(Metrics::SCENARIO_ACTIVATED_SUCCESSFULLY); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool BackgroundTracingManagerImpl::HasActiveScenario() { | 
|  | return !!active_scenario_; | 
|  | } | 
|  |  | 
|  | bool BackgroundTracingManagerImpl::HasTraceToUpload() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | // Send the logs only when the trace size is within limits. If the connection | 
|  | // type changes and we have a bigger than expected trace, then the next time | 
|  | // service asks us when wifi is available, the trace will be sent. If we did | 
|  | // collect a trace that is bigger than expected, then we will end up never | 
|  | // uploading, and drop the trace. This should never happen because the trace | 
|  | // buffer limits are set appropriately. | 
|  | if (trace_to_upload_.empty()) { | 
|  | return false; | 
|  | } | 
|  | if (active_scenario_ && | 
|  | trace_to_upload_.size() <= | 
|  | active_scenario_->GetTraceUploadLimitKb() * 1024) { | 
|  | return true; | 
|  | } | 
|  | RecordMetric(Metrics::LARGE_UPLOAD_WAITING_TO_RETRY); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::string BackgroundTracingManagerImpl::GetLatestTraceToUpload() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | std::string ret; | 
|  | ret.swap(trace_to_upload_); | 
|  |  | 
|  | if (active_scenario_) { | 
|  | active_scenario_->OnFinalizeComplete(true); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::AddEnabledStateObserver( | 
|  | EnabledStateObserver* observer) { | 
|  | // Ensure that this code is called on the UI thread, except for | 
|  | // tests where a UI thread might not have been initialized at this point. | 
|  | DCHECK( | 
|  | content::BrowserThread::CurrentlyOn(content::BrowserThread::UI) || | 
|  | !content::BrowserThread::IsThreadInitialized(content::BrowserThread::UI)); | 
|  | background_tracing_observers_.insert(observer); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::RemoveEnabledStateObserver( | 
|  | EnabledStateObserver* observer) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | background_tracing_observers_.erase(observer); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::AddAgent( | 
|  | tracing::mojom::BackgroundTracingAgent* agent) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | agents_.insert(agent); | 
|  |  | 
|  | for (auto* observer : agent_observers_) { | 
|  | observer->OnAgentAdded(agent); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::RemoveAgent( | 
|  | tracing::mojom::BackgroundTracingAgent* agent) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | for (auto* observer : agent_observers_) { | 
|  | observer->OnAgentRemoved(agent); | 
|  | } | 
|  |  | 
|  | agents_.erase(agent); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::AddAgentObserver(AgentObserver* observer) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | agent_observers_.insert(observer); | 
|  |  | 
|  | MaybeConstructPendingAgents(); | 
|  |  | 
|  | for (auto* agent : agents_) { | 
|  | observer->OnAgentAdded(agent); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::RemoveAgentObserver( | 
|  | AgentObserver* observer) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | agent_observers_.erase(observer); | 
|  |  | 
|  | for (auto* agent : agents_) { | 
|  | observer->OnAgentRemoved(agent); | 
|  | } | 
|  | } | 
|  |  | 
|  | BackgroundTracingActiveScenario* | 
|  | BackgroundTracingManagerImpl::GetActiveScenarioForTesting() { | 
|  | DCHECK(active_scenario_); | 
|  | return active_scenario_.get(); | 
|  | } | 
|  |  | 
|  | bool BackgroundTracingManagerImpl::IsTracingForTesting() { | 
|  | return active_scenario_ && (active_scenario_->state() == | 
|  | BackgroundTracingActiveScenario::State::kTracing); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::SetTraceToUploadForTesting( | 
|  | std::unique_ptr<std::string> trace_data) { | 
|  | SetTraceToUpload(std::move(trace_data)); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::SetConfigTextFilterForTesting( | 
|  | ConfigTextFilterForTesting predicate) { | 
|  | config_text_filter_for_testing_ = std::move(predicate); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::SetTraceToUpload( | 
|  | std::unique_ptr<std::string> trace_data) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (trace_data) { | 
|  | trace_to_upload_.swap(*trace_data); | 
|  | } else { | 
|  | trace_to_upload_.clear(); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::string BackgroundTracingManagerImpl::GetBackgroundTracingUploadUrl( | 
|  | const std::string& trial_name) { | 
|  | return variations::GetVariationParamValue(trial_name, | 
|  | kBackgroundTracingUploadUrl); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<content::BackgroundTracingConfig> | 
|  | BackgroundTracingManagerImpl::GetBackgroundTracingConfig( | 
|  | const std::string& trial_name) { | 
|  | std::string config_text = | 
|  | variations::GetVariationParamValue(trial_name, kBackgroundTracingConfig); | 
|  | if (config_text.empty()) | 
|  | return nullptr; | 
|  |  | 
|  | if (config_text_filter_for_testing_) | 
|  | config_text = config_text_filter_for_testing_.Run(config_text); | 
|  |  | 
|  | auto value = base::JSONReader::Read(config_text); | 
|  | if (!value) | 
|  | return nullptr; | 
|  |  | 
|  | if (!value->is_dict()) | 
|  | return nullptr; | 
|  |  | 
|  | return BackgroundTracingConfig::FromDict(std::move(value->GetDict())); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::OnHistogramTrigger( | 
|  | const std::string& histogram_name) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (active_scenario_) { | 
|  | active_scenario_->OnHistogramTrigger(histogram_name); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::TriggerNamedEvent( | 
|  | BackgroundTracingManagerImpl::TriggerHandle handle, | 
|  | StartedFinalizingCallback callback) { | 
|  | if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | 
|  | GetUIThreadTaskRunner({})->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&BackgroundTracingManagerImpl::TriggerNamedEvent, | 
|  | base::Unretained(this), handle, std::move(callback))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!active_scenario_ || !IsTriggerHandleValid(handle)) { | 
|  | if (!callback.is_null()) { | 
|  | std::move(callback).Run(false); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | active_scenario_->TriggerNamedEvent(handle, std::move(callback)); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::OnRuleTriggered( | 
|  | const BackgroundTracingRule* triggered_rule, | 
|  | StartedFinalizingCallback callback) { | 
|  | // The active scenario can be null here if scenario was aborted during | 
|  | // validation and the rule was triggered just before validation. If validation | 
|  | // kicked in after this point, we still check before uploading. | 
|  | if (active_scenario_) { | 
|  | active_scenario_->OnRuleTriggered(triggered_rule, std::move(callback)); | 
|  | } | 
|  | } | 
|  |  | 
|  | BackgroundTracingManagerImpl::TriggerHandle | 
|  | BackgroundTracingManagerImpl::RegisterTriggerType( | 
|  | base::StringPiece trigger_name) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | trigger_handle_ids_ += 1; | 
|  | trigger_handles_.insert( | 
|  | std::pair<TriggerHandle, std::string>(trigger_handle_ids_, trigger_name)); | 
|  |  | 
|  | return static_cast<TriggerHandle>(trigger_handle_ids_); | 
|  | } | 
|  |  | 
|  | bool BackgroundTracingManagerImpl::IsTriggerHandleValid( | 
|  | BackgroundTracingManager::TriggerHandle handle) const { | 
|  | return trigger_handles_.find(handle) != trigger_handles_.end(); | 
|  | } | 
|  |  | 
|  | const std::string& BackgroundTracingManagerImpl::GetTriggerNameFromHandle( | 
|  | BackgroundTracingManager::TriggerHandle handle) { | 
|  | CHECK(IsTriggerHandleValid(handle)); | 
|  | return trigger_handles_.find(handle)->second; | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::InvalidateTriggerHandlesForTesting() { | 
|  | trigger_handles_.clear(); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::OnStartTracingDone( | 
|  | BackgroundTracingConfigImpl::CategoryPreset preset) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | for (auto* observer : background_tracing_observers_) { | 
|  | observer->OnTracingEnabled(preset); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::WhenIdle( | 
|  | base::RepeatingClosure idle_callback) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | idle_callback_ = std::move(idle_callback); | 
|  |  | 
|  | if (!active_scenario_) { | 
|  | idle_callback_.Run(); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool BackgroundTracingManagerImpl::IsAllowedFinalization( | 
|  | bool is_crash_scenario) const { | 
|  | return !delegate_ || | 
|  | (active_scenario_ && | 
|  | delegate_->IsAllowedToEndBackgroundScenario( | 
|  | *active_scenario_->GetConfig(), | 
|  | active_scenario_->GetConfig()->requires_anonymized_data(), | 
|  | is_crash_scenario)); | 
|  | } | 
|  |  | 
|  | absl::optional<base::Value::Dict> | 
|  | BackgroundTracingManagerImpl::GenerateMetadataDict() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (!active_scenario_) | 
|  | return absl::nullopt; | 
|  | return active_scenario_->GenerateMetadataDict(); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::GenerateMetadataProto( | 
|  | perfetto::protos::pbzero::ChromeMetadataPacket* metadata, | 
|  | bool privacy_filtering_enabled) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (active_scenario_) { | 
|  | active_scenario_->GenerateMetadataProto(metadata); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::AbortScenarioForTesting() { | 
|  | AbortScenario(); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::AbortScenario() { | 
|  | if (active_scenario_) { | 
|  | active_scenario_->AbortScenario(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::OnScenarioAborted() { | 
|  | DCHECK(active_scenario_); | 
|  |  | 
|  | // Don't synchronously delete to avoid use-after-free issues in | 
|  | // BackgroundTracingActiveScenario. | 
|  | base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, | 
|  | std::move(active_scenario_)); | 
|  |  | 
|  | for (auto* observer : background_tracing_observers_) { | 
|  | observer->OnScenarioAborted(); | 
|  | } | 
|  |  | 
|  | if (!idle_callback_.is_null()) { | 
|  | idle_callback_.Run(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // static | 
|  | void BackgroundTracingManagerImpl::AddPendingAgent( | 
|  | int child_process_id, | 
|  | mojo::PendingRemote<tracing::mojom::BackgroundTracingAgentProvider> | 
|  | pending_provider) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | // Delay agent initialization until we have an interested AgentObserver. | 
|  | // We set disconnect handler for cleanup when the tracing target is closed. | 
|  | mojo::Remote<tracing::mojom::BackgroundTracingAgentProvider> provider( | 
|  | std::move(pending_provider)); | 
|  |  | 
|  | provider.set_disconnect_handler(base::BindOnce( | 
|  | &BackgroundTracingManagerImpl::ClearPendingAgent, child_process_id)); | 
|  |  | 
|  | GetInstance().pending_agents_[child_process_id] = std::move(provider); | 
|  | GetInstance().MaybeConstructPendingAgents(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void BackgroundTracingManagerImpl::ClearPendingAgent(int child_process_id) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | GetInstance().pending_agents_.erase(child_process_id); | 
|  | } | 
|  |  | 
|  | void BackgroundTracingManagerImpl::MaybeConstructPendingAgents() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | if (agent_observers_.empty()) | 
|  | return; | 
|  |  | 
|  | for (auto& pending_agent : pending_agents_) { | 
|  | pending_agent.second.set_disconnect_handler(base::OnceClosure()); | 
|  | BackgroundTracingAgentClientImpl::Create(pending_agent.first, | 
|  | std::move(pending_agent.second)); | 
|  | } | 
|  | pending_agents_.clear(); | 
|  | } | 
|  |  | 
|  | }  // namespace content |