| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/performance_manager/service_worker_context_adapter.h" |
| |
| #include "base/check_op.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/no_destructor.h" |
| #include "base/notreached.h" |
| #include "base/observer_list.h" |
| #include "base/scoped_observation.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_process_host_observer.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| |
| namespace performance_manager { |
| |
| // ServiceWorkerContextAdapterImpl::RunningServiceWorker ----------------------- |
| |
| // Observes when the render process of a running service worker exits and |
| // notifies its owner. |
| class ServiceWorkerContextAdapterImpl::RunningServiceWorker |
| : content::RenderProcessHostObserver { |
| public: |
| RunningServiceWorker(int64_t version_id, |
| ServiceWorkerContextAdapterImpl* adapter); |
| ~RunningServiceWorker() override; |
| |
| void Subscribe(content::RenderProcessHost* worker_process_host); |
| void Unsubscribe(); |
| |
| // content::RenderProcessHostObserver: |
| void RenderProcessExited( |
| content::RenderProcessHost* host, |
| const content::ChildProcessTerminationInfo& info) override; |
| void InProcessRendererExiting(content::RenderProcessHost* host) override; |
| void RenderProcessHostDestroyed(content::RenderProcessHost* host) override; |
| |
| private: |
| // The version ID of the service worker. |
| int const version_id_; |
| |
| // The adapter that owns |this|. Notified when RenderProcessExited() is |
| // called. |
| const raw_ptr<ServiceWorkerContextAdapterImpl> adapter_; |
| |
| base::ScopedObservation<content::RenderProcessHost, |
| content::RenderProcessHostObserver> |
| scoped_observation_{this}; |
| }; |
| |
| ServiceWorkerContextAdapterImpl::RunningServiceWorker::RunningServiceWorker( |
| int64_t version_id, |
| ServiceWorkerContextAdapterImpl* adapter) |
| : version_id_(version_id), adapter_(adapter) {} |
| |
| ServiceWorkerContextAdapterImpl::RunningServiceWorker::~RunningServiceWorker() { |
| DCHECK(!scoped_observation_.IsObserving()); |
| } |
| |
| void ServiceWorkerContextAdapterImpl::RunningServiceWorker::Subscribe( |
| content::RenderProcessHost* worker_process_host) { |
| DCHECK(!scoped_observation_.IsObserving()); |
| scoped_observation_.Observe(worker_process_host); |
| } |
| |
| void ServiceWorkerContextAdapterImpl::RunningServiceWorker::Unsubscribe() { |
| DCHECK(scoped_observation_.IsObserving()); |
| |
| scoped_observation_.Reset(); |
| } |
| |
| void ServiceWorkerContextAdapterImpl::RunningServiceWorker::RenderProcessExited( |
| content::RenderProcessHost* host, |
| const content::ChildProcessTerminationInfo& info) { |
| adapter_->OnRenderProcessExited(version_id_); |
| |
| /* This object is deleted inside the above, don't touch "this". */ |
| } |
| |
| void ServiceWorkerContextAdapterImpl::RunningServiceWorker:: |
| InProcessRendererExiting(content::RenderProcessHost* host) { |
| CHECK(content::RenderProcessHost::run_renderer_in_process()); |
| adapter_->OnRenderProcessExited(version_id_); |
| |
| /* This object is deleted inside the above, don't touch "this". */ |
| } |
| |
| void ServiceWorkerContextAdapterImpl::RunningServiceWorker:: |
| RenderProcessHostDestroyed(content::RenderProcessHost* host) { |
| NOTREACHED(); |
| } |
| |
| // ServiceWorkerContextAdapterImpl --------------------------------------------- |
| |
| ServiceWorkerContextAdapterImpl::ServiceWorkerContextAdapterImpl( |
| content::ServiceWorkerContext* underlying_context) { |
| scoped_underlying_context_observation_.Observe(underlying_context); |
| } |
| |
| ServiceWorkerContextAdapterImpl::~ServiceWorkerContextAdapterImpl() { |
| // Clean up any outstanding running service worker process subscriptions. |
| for (const auto& item : running_service_workers_) |
| item.second->Unsubscribe(); |
| running_service_workers_.clear(); |
| } |
| |
| void ServiceWorkerContextAdapterImpl::AddObserver( |
| content::ServiceWorkerContextObserver* observer) { |
| observer_list_.AddObserver(observer); |
| } |
| |
| void ServiceWorkerContextAdapterImpl::RemoveObserver( |
| content::ServiceWorkerContextObserver* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| void ServiceWorkerContextAdapterImpl::OnVersionStartedRunning( |
| int64_t version_id, |
| const content::ServiceWorkerRunningInfo& running_info) { |
| content::RenderProcessHost* worker_process_host = |
| content::RenderProcessHost::FromID(running_info.render_process_id); |
| |
| // It's possible that the renderer is already gone since the notification |
| // comes asynchronously. Ignore this service worker. |
| if (!worker_process_host || !worker_process_host->IsInitializedAndNotDead()) { |
| #if DCHECK_IS_ON() |
| // A OnVersionStoppedRunning() notification is still expected to be sent. |
| bool inserted = stopped_service_workers_.insert(version_id).second; |
| DCHECK(inserted); |
| #endif // DCHECK_IS_ON() |
| return; |
| } |
| |
| AddRunningServiceWorker(version_id, worker_process_host); |
| for (auto& observer : observer_list_) |
| observer.OnVersionStartedRunning(version_id, running_info); |
| } |
| |
| void ServiceWorkerContextAdapterImpl::OnVersionStoppedRunning( |
| int64_t version_id) { |
| bool removed = MaybeRemoveRunningServiceWorker(version_id); |
| if (!removed) { |
| #if DCHECK_IS_ON() |
| // If this service worker could not be found, then it must be because its |
| // render process exited early. |
| size_t removed_count = stopped_service_workers_.erase(version_id); |
| DCHECK_EQ(removed_count, 1u); |
| #endif // DCHECK_IS_ON() |
| return; |
| } |
| |
| for (auto& observer : observer_list_) |
| observer.OnVersionStoppedRunning(version_id); |
| } |
| |
| void ServiceWorkerContextAdapterImpl::OnControlleeAdded( |
| int64_t version_id, |
| const std::string& client_uuid, |
| const content::ServiceWorkerClientInfo& client_info) { |
| // If |client_uuid| is already marked as a client of |version_id|, the |
| // notification is dropped. |
| bool inserted = |
| service_worker_clients_[version_id].insert(client_uuid).second; |
| if (!inserted) { |
| DUMP_WILL_BE_NOTREACHED(); |
| return; |
| } |
| |
| for (auto& observer : observer_list_) |
| observer.OnControlleeAdded(version_id, client_uuid, client_info); |
| } |
| |
| void ServiceWorkerContextAdapterImpl::OnControlleeRemoved( |
| int64_t version_id, |
| const std::string& client_uuid) { |
| // If |client_uuid| is not already marked as a client of |version_id|, the |
| // notification is dropped. |
| auto it = service_worker_clients_.find(version_id); |
| if (it == service_worker_clients_.end()) { |
| DUMP_WILL_BE_NOTREACHED(); |
| return; |
| } |
| |
| size_t removed = it->second.erase(client_uuid); |
| if (!removed) { |
| DUMP_WILL_BE_NOTREACHED(); |
| return; |
| } |
| |
| // If a service worker no longer has any clients, it is removed entirely from |
| // |service_worker_clients_|. |
| if (it->second.empty()) |
| service_worker_clients_.erase(it); |
| |
| for (auto& observer : observer_list_) |
| observer.OnControlleeRemoved(version_id, client_uuid); |
| } |
| |
| void ServiceWorkerContextAdapterImpl::OnControlleeNavigationCommitted( |
| int64_t version_id, |
| const std::string& client_uuid, |
| content::GlobalRenderFrameHostId render_frame_host_id) { |
| // The navigation committed notification should not be sent if the frame is |
| // not already a client of |version_id|. |
| auto it = service_worker_clients_.find(version_id); |
| if (it == service_worker_clients_.end()) { |
| DUMP_WILL_BE_NOTREACHED(); |
| return; |
| } |
| |
| if (it->second.find(client_uuid) == it->second.end()) { |
| DUMP_WILL_BE_NOTREACHED(); |
| return; |
| } |
| |
| for (auto& observer : observer_list_) |
| observer.OnControlleeNavigationCommitted(version_id, client_uuid, |
| render_frame_host_id); |
| } |
| |
| void ServiceWorkerContextAdapterImpl::OnRenderProcessExited( |
| int64_t version_id) { |
| bool removed = MaybeRemoveRunningServiceWorker(version_id); |
| DCHECK(removed); |
| |
| for (auto& observer : observer_list_) |
| observer.OnVersionStoppedRunning(version_id); |
| |
| #if DCHECK_IS_ON() |
| // Now expect that OnVersionStoppedRunning() will be called for that |
| // version_id. |
| bool inserted = stopped_service_workers_.insert(version_id).second; |
| DCHECK(inserted); |
| #endif // DCHECK_IS_ON() |
| } |
| |
| void ServiceWorkerContextAdapterImpl::AddRunningServiceWorker( |
| int64_t version_id, |
| content::RenderProcessHost* worker_process_host) { |
| std::unique_ptr<ServiceWorkerContextAdapterImpl::RunningServiceWorker> |
| running_service_worker = |
| std::make_unique<RunningServiceWorker>(version_id, this); |
| |
| running_service_worker->Subscribe(worker_process_host); |
| bool inserted = running_service_workers_ |
| .emplace(version_id, std::move(running_service_worker)) |
| .second; |
| DCHECK(inserted); |
| } |
| |
| bool ServiceWorkerContextAdapterImpl::MaybeRemoveRunningServiceWorker( |
| int64_t version_id) { |
| auto it = running_service_workers_.find(version_id); |
| if (it == running_service_workers_.end()) |
| return false; |
| |
| it->second->Unsubscribe(); |
| running_service_workers_.erase(it); |
| |
| return true; |
| } |
| |
| } // namespace performance_manager |