| // Copyright 2019 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/renderer/browser_exposed_renderer_interfaces.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "content/common/features.h" |
| #include "content/common/frame.mojom.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/resource_usage_reporter.mojom.h" |
| #include "content/public/common/resource_usage_reporter_type_converters.h" |
| #include "content/public/renderer/content_renderer_client.h" |
| #include "content/renderer/render_thread_impl.h" |
| #include "content/renderer/service_worker/embedded_worker_instance_client_impl.h" |
| #include "content/renderer/worker/shared_worker_factory_impl.h" |
| #include "content/services/auction_worklet/auction_worklet_service_impl.h" |
| #include "mojo/public/cpp/bindings/binder_map.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "v8/include/v8-isolate.h" |
| #include "v8/include/v8-statistics.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| constexpr int kWaitForWorkersStatsTimeoutMS = 20; |
| |
| class ResourceUsageReporterImpl : public content::mojom::ResourceUsageReporter { |
| public: |
| explicit ResourceUsageReporterImpl(base::WeakPtr<RenderThread> thread) |
| : thread_(std::move(thread)) {} |
| ResourceUsageReporterImpl(const ResourceUsageReporterImpl&) = delete; |
| ~ResourceUsageReporterImpl() override = default; |
| |
| ResourceUsageReporterImpl& operator=(const ResourceUsageReporterImpl&) = |
| delete; |
| |
| private: |
| static void CollectOnWorkerThread( |
| const scoped_refptr<base::TaskRunner>& master, |
| base::WeakPtr<ResourceUsageReporterImpl> impl) { |
| size_t total_bytes = 0; |
| size_t used_bytes = 0; |
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); |
| if (isolate) { |
| v8::HeapStatistics heap_stats; |
| isolate->GetHeapStatistics(&heap_stats); |
| total_bytes = heap_stats.total_heap_size(); |
| used_bytes = heap_stats.used_heap_size(); |
| } |
| master->PostTask(FROM_HERE, |
| base::BindOnce(&ResourceUsageReporterImpl::ReceiveStats, |
| impl, total_bytes, used_bytes)); |
| } |
| |
| void ReceiveStats(size_t total_bytes, size_t used_bytes) { |
| usage_data_->v8_bytes_allocated += total_bytes; |
| usage_data_->v8_bytes_used += used_bytes; |
| workers_to_go_--; |
| if (!workers_to_go_) |
| SendResults(); |
| } |
| |
| void SendResults() { |
| if (!callback_.is_null()) |
| std::move(callback_).Run(std::move(usage_data_)); |
| callback_.Reset(); |
| weak_factory_.InvalidateWeakPtrs(); |
| workers_to_go_ = 0; |
| } |
| |
| void GetUsageData(GetUsageDataCallback callback) override { |
| DCHECK(callback_.is_null()); |
| weak_factory_.InvalidateWeakPtrs(); |
| usage_data_ = mojom::ResourceUsageData::New(); |
| usage_data_->reports_v8_stats = true; |
| callback_ = std::move(callback); |
| |
| // Since it is not safe to call any Blink or V8 functions until Blink has |
| // been initialized (which also initializes V8), early out and send 0 back |
| // for all resources. |
| if (!thread_) { |
| SendResults(); |
| return; |
| } |
| |
| blink::WebCacheResourceTypeStats stats; |
| blink::WebCache::GetResourceTypeStats(&stats); |
| usage_data_->web_cache_stats = mojom::ResourceTypeStats::From(stats); |
| |
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); |
| if (isolate) { |
| v8::HeapStatistics heap_stats; |
| isolate->GetHeapStatistics(&heap_stats); |
| usage_data_->v8_bytes_allocated = heap_stats.total_heap_size(); |
| usage_data_->v8_bytes_used = heap_stats.used_heap_size(); |
| } |
| base::RepeatingClosure collect = |
| base::BindRepeating(&ResourceUsageReporterImpl::CollectOnWorkerThread, |
| base::SingleThreadTaskRunner::GetCurrentDefault(), |
| weak_factory_.GetWeakPtr()); |
| workers_to_go_ = |
| RenderThread::Get()->PostTaskToAllWebWorkers(std::move(collect)); |
| if (workers_to_go_) { |
| // The guard task to send out partial stats |
| // in case some workers are not responsive. |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&ResourceUsageReporterImpl::SendResults, |
| weak_factory_.GetWeakPtr()), |
| base::Milliseconds(kWaitForWorkersStatsTimeoutMS)); |
| } else { |
| // No worker threads so just send out the main thread data right away. |
| SendResults(); |
| } |
| } |
| |
| const base::WeakPtr<RenderThread> thread_; |
| mojom::ResourceUsageDataPtr usage_data_; |
| GetUsageDataCallback callback_; |
| int workers_to_go_ = 0; |
| |
| base::WeakPtrFactory<ResourceUsageReporterImpl> weak_factory_{this}; |
| }; |
| |
| void CreateResourceUsageReporter( |
| base::WeakPtr<RenderThreadImpl> render_thread, |
| mojo::PendingReceiver<mojom::ResourceUsageReporter> receiver) { |
| mojo::MakeSelfOwnedReceiver( |
| std::make_unique<ResourceUsageReporterImpl>(std::move(render_thread)), |
| std::move(receiver)); |
| } |
| |
| void CreateEmbeddedWorkerWithRenderMainThread( |
| scoped_refptr<base::SingleThreadTaskRunner> initiator_task_runner, |
| base::WeakPtr<RenderThreadImpl> render_thread, |
| mojo::PendingReceiver<blink::mojom::EmbeddedWorkerInstanceClient> |
| receiver) { |
| TRACE_EVENT0("ServiceWorker", "CreateEmbeddedWorkerWithRenderMainThread"); |
| initiator_task_runner->PostTask( |
| FROM_HERE, base::BindOnce(&EmbeddedWorkerInstanceClientImpl::Create, |
| initiator_task_runner, |
| render_thread->cors_exempt_header_list(), |
| std::move(receiver))); |
| } |
| |
| void CreateEmbeddedWorker( |
| scoped_refptr<base::SingleThreadTaskRunner> initiator_task_runner, |
| mojo::PendingReceiver<blink::mojom::EmbeddedWorkerInstanceClient> |
| receiver) { |
| TRACE_EVENT0("ServiceWorker", "CreateEmbeddedWorker"); |
| // An empty fake list is passed to |
| // `EmbeddedWorkerInstanceClientImpl::Create()`. That will be overridden by |
| // the actual cors exempt header list in |
| // `EmbeddedWorkerInstanceClientImpl::StartWorker()`. |
| // |
| // TODO(crbug.com/40753993): Remove this fake empty list once we confirmed |
| // this approach is fine. |
| const std::vector<std::string> fake_cors_exempt_header_list; |
| initiator_task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce(&EmbeddedWorkerInstanceClientImpl::Create, |
| initiator_task_runner, fake_cors_exempt_header_list, |
| std::move(receiver))); |
| } |
| } // namespace |
| |
| void ExposeRendererInterfacesToBrowser( |
| base::WeakPtr<RenderThreadImpl> render_thread, |
| mojo::BinderMap* binders) { |
| DCHECK(render_thread); |
| |
| binders->Add<blink::mojom::SharedWorkerFactory>( |
| base::BindRepeating(&SharedWorkerFactoryImpl::Create), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| binders->Add<mojom::ResourceUsageReporter>( |
| base::BindRepeating(&CreateResourceUsageReporter, render_thread), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| #if BUILDFLAG(IS_ANDROID) |
| binders->Add<auction_worklet::mojom::AuctionWorkletService>( |
| base::BindRepeating( |
| &auction_worklet::AuctionWorkletServiceImpl::CreateForRenderer), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| #endif |
| |
| auto task_runner_for_service_worker_startup = |
| base::ThreadPool::CreateSingleThreadTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); |
| // TODO(crbug.com/40753993): Remove the feature flag and |
| // `CreateEmbeddedWorkerWithRenderMainThread()` once we confirmed this |
| // approach is fine. |
| // |
| // The kServiceWorkerAvoidMainThreadForInitialization feature flag is the |
| // experimental flag to avoid the additional thread hop over the main thread |
| // for the ServiceWorker initialization. Currently it's on the main thread as |
| // CreateEmbeddedWorker accesses `cors_exempt_header_list` from |
| // `render_thread`. When this feature flag is enabled, binds on |
| // `task_runner_for_service_worker_startup` instead of the main thread, so |
| // startup isn't blocked on the main thread. |
| if (base::FeatureList::IsEnabled( |
| features::kServiceWorkerAvoidMainThreadForInitialization)) { |
| binders->Add<blink::mojom::EmbeddedWorkerInstanceClient>( |
| base::BindRepeating(&CreateEmbeddedWorker, |
| task_runner_for_service_worker_startup), |
| task_runner_for_service_worker_startup); |
| } else { |
| binders->Add<blink::mojom::EmbeddedWorkerInstanceClient>( |
| base::BindRepeating(&CreateEmbeddedWorkerWithRenderMainThread, |
| task_runner_for_service_worker_startup, |
| render_thread), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| } |
| |
| GetContentClient()->renderer()->ExposeInterfacesToBrowser(binders); |
| } |
| |
| } // namespace content |