| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/service_worker/service_worker_object_host.h" |
| |
| #include "content/browser/service_worker/service_worker_client_utils.h" |
| #include "content/browser/service_worker/service_worker_context_core.h" |
| #include "content/browser/service_worker/service_worker_provider_host.h" |
| #include "content/browser/service_worker/service_worker_registration.h" |
| #include "content/browser/service_worker/service_worker_type_converters.h" |
| #include "content/common/service_worker/service_worker_types.h" |
| #include "content/common/service_worker/service_worker_utils.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/browser_side_navigation_policy.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using StatusCallback = base::OnceCallback<void(blink::ServiceWorkerStatusCode)>; |
| using SetExtendableMessageEventSourceCallback = |
| base::OnceCallback<bool(mojom::ExtendableMessageEventPtr*)>; |
| |
| void DispatchExtendableMessageEventAfterStartWorker( |
| scoped_refptr<ServiceWorkerVersion> worker, |
| blink::TransferableMessage message, |
| const url::Origin& source_origin, |
| const base::Optional<base::TimeDelta>& timeout, |
| StatusCallback callback, |
| SetExtendableMessageEventSourceCallback set_source_callback, |
| blink::ServiceWorkerStatusCode start_worker_status) { |
| if (start_worker_status != blink::ServiceWorkerStatusCode::kOk) { |
| std::move(callback).Run(start_worker_status); |
| return; |
| } |
| |
| mojom::ExtendableMessageEventPtr event = mojom::ExtendableMessageEvent::New(); |
| event->message = std::move(message); |
| event->source_origin = source_origin; |
| if (!std::move(set_source_callback).Run(&event)) { |
| std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorFailed); |
| return; |
| } |
| |
| int request_id; |
| if (timeout) { |
| request_id = worker->StartRequestWithCustomTimeout( |
| ServiceWorkerMetrics::EventType::MESSAGE, std::move(callback), *timeout, |
| ServiceWorkerVersion::CONTINUE_ON_TIMEOUT); |
| } else { |
| request_id = worker->StartRequest(ServiceWorkerMetrics::EventType::MESSAGE, |
| std::move(callback)); |
| } |
| worker->event_dispatcher()->DispatchExtendableMessageEvent( |
| std::move(event), worker->CreateSimpleEventCallback(request_id)); |
| } |
| |
| void StartWorkerToDispatchExtendableMessageEvent( |
| scoped_refptr<ServiceWorkerVersion> worker, |
| blink::TransferableMessage message, |
| const url::Origin& source_origin, |
| const base::Optional<base::TimeDelta>& timeout, |
| StatusCallback callback, |
| SetExtendableMessageEventSourceCallback set_source_callback) { |
| // If not enough time is left to actually process the event don't even |
| // bother starting the worker and sending the event. |
| if (timeout && *timeout < base::TimeDelta::FromMilliseconds(100)) { |
| std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorTimeout); |
| return; |
| } |
| |
| worker->RunAfterStartWorker( |
| ServiceWorkerMetrics::EventType::MESSAGE, |
| base::BindOnce(&DispatchExtendableMessageEventAfterStartWorker, worker, |
| std::move(message), source_origin, timeout, |
| std::move(callback), std::move(set_source_callback))); |
| } |
| |
| bool SetSourceClientInfo( |
| blink::mojom::ServiceWorkerClientInfoPtr source_client_info, |
| mojom::ExtendableMessageEventPtr* event) { |
| DCHECK(source_client_info && !source_client_info->client_uuid.empty()); |
| (*event)->source_info_for_client = std::move(source_client_info); |
| // Hide the client url if the client has a unique origin. |
| if ((*event)->source_origin.unique()) |
| (*event)->source_info_for_client->url = GURL(); |
| return true; |
| } |
| |
| // The output |event| must be sent over Mojo immediately after this function |
| // returns. See ServiceWorkerObjectHost::CreateCompleteObjectInfoToSend() for |
| // details. |
| bool SetSourceServiceWorkerInfo(scoped_refptr<ServiceWorkerVersion> worker, |
| base::WeakPtr<ServiceWorkerProviderHost> |
| source_service_worker_provider_host, |
| mojom::ExtendableMessageEventPtr* event) { |
| // The service worker execution context may have been destroyed by the time we |
| // get here. |
| if (!source_service_worker_provider_host) |
| return false; |
| |
| DCHECK_EQ(blink::mojom::ServiceWorkerProviderType::kForServiceWorker, |
| source_service_worker_provider_host->provider_type()); |
| blink::mojom::ServiceWorkerObjectInfoPtr source_worker_info; |
| base::WeakPtr<ServiceWorkerObjectHost> service_worker_object_host = |
| worker->provider_host()->GetOrCreateServiceWorkerObjectHost( |
| source_service_worker_provider_host->running_hosted_version()); |
| if (service_worker_object_host) { |
| // CreateCompleteObjectInfoToSend() is safe because |source_worker_info| |
| // will be sent immediately by the caller of this function. |
| source_worker_info = |
| service_worker_object_host->CreateCompleteObjectInfoToSend(); |
| } |
| |
| (*event)->source_info_for_service_worker = std::move(source_worker_info); |
| // Hide the service worker url if the service worker has a unique origin. |
| if ((*event)->source_origin.unique()) |
| (*event)->source_info_for_service_worker->url = GURL(); |
| return true; |
| } |
| |
| void DispatchExtendableMessageEventFromClient( |
| scoped_refptr<ServiceWorkerVersion> worker, |
| blink::TransferableMessage message, |
| const url::Origin& source_origin, |
| StatusCallback callback, |
| blink::mojom::ServiceWorkerClientInfoPtr source_client_info) { |
| // |source_client_info| may be null if a client sent the message but its |
| // info could not be retrieved. |
| if (!source_client_info) { |
| std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorFailed); |
| return; |
| } |
| |
| StartWorkerToDispatchExtendableMessageEvent( |
| worker, std::move(message), source_origin, base::nullopt /* timeout */, |
| std::move(callback), |
| base::BindOnce(&SetSourceClientInfo, std::move(source_client_info))); |
| } |
| |
| void DispatchExtendableMessageEventFromServiceWorker( |
| scoped_refptr<ServiceWorkerVersion> worker, |
| blink::TransferableMessage message, |
| const url::Origin& source_origin, |
| const base::Optional<base::TimeDelta>& timeout, |
| StatusCallback callback, |
| base::WeakPtr<ServiceWorkerProviderHost> |
| source_service_worker_provider_host) { |
| if (!source_service_worker_provider_host) { |
| std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorFailed); |
| return; |
| } |
| |
| DCHECK_EQ(blink::mojom::ServiceWorkerProviderType::kForServiceWorker, |
| source_service_worker_provider_host->provider_type()); |
| StartWorkerToDispatchExtendableMessageEvent( |
| worker, std::move(message), source_origin, timeout, std::move(callback), |
| base::BindOnce(&SetSourceServiceWorkerInfo, worker, |
| source_service_worker_provider_host)); |
| } |
| |
| } // namespace |
| |
| ServiceWorkerObjectHost::ServiceWorkerObjectHost( |
| base::WeakPtr<ServiceWorkerContextCore> context, |
| ServiceWorkerProviderHost* provider_host, |
| scoped_refptr<ServiceWorkerVersion> version) |
| : context_(context), |
| provider_host_(provider_host), |
| provider_origin_(url::Origin::Create(provider_host->document_url())), |
| provider_id_(provider_host->provider_id()), |
| version_(std::move(version)), |
| weak_ptr_factory_(this) { |
| DCHECK(context_ && provider_host_ && version_); |
| DCHECK(context_->GetLiveRegistration(version_->registration_id())); |
| version_->AddObserver(this); |
| bindings_.set_connection_error_handler(base::BindRepeating( |
| &ServiceWorkerObjectHost::OnConnectionError, base::Unretained(this))); |
| } |
| |
| ServiceWorkerObjectHost::~ServiceWorkerObjectHost() { |
| // TODO(crbug.com/838410): These CHECKs are temporary debugging for the linked |
| // bug. |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| CHECK(!in_dtor_); |
| in_dtor_ = true; |
| |
| version_->RemoveObserver(this); |
| } |
| |
| void ServiceWorkerObjectHost::OnVersionStateChanged( |
| ServiceWorkerVersion* version) { |
| DCHECK(version); |
| blink::mojom::ServiceWorkerState state = |
| mojo::ConvertTo<blink::mojom::ServiceWorkerState>(version->status()); |
| remote_objects_.ForAllPtrs( |
| [state](blink::mojom::ServiceWorkerObject* remote_object) { |
| remote_object->StateChanged(state); |
| }); |
| } |
| |
| blink::mojom::ServiceWorkerObjectInfoPtr |
| ServiceWorkerObjectHost::CreateCompleteObjectInfoToSend() { |
| auto info = CreateIncompleteObjectInfo(); |
| blink::mojom::ServiceWorkerObjectAssociatedPtr remote_object; |
| info->request = mojo::MakeRequest(&remote_object); |
| remote_objects_.AddPtr(std::move(remote_object)); |
| return info; |
| } |
| |
| blink::mojom::ServiceWorkerObjectInfoPtr |
| ServiceWorkerObjectHost::CreateIncompleteObjectInfo() { |
| auto info = blink::mojom::ServiceWorkerObjectInfo::New(); |
| info->url = version_->script_url(); |
| info->state = |
| mojo::ConvertTo<blink::mojom::ServiceWorkerState>(version_->status()); |
| info->version_id = version_->version_id(); |
| bindings_.AddBinding(this, mojo::MakeRequest(&info->host_ptr_info)); |
| return info; |
| } |
| |
| void ServiceWorkerObjectHost::AddRemoteObjectPtrAndUpdateState( |
| blink::mojom::ServiceWorkerObjectAssociatedPtrInfo remote_object_ptr_info, |
| blink::mojom::ServiceWorkerState sent_state) { |
| DCHECK(remote_object_ptr_info.is_valid()); |
| blink::mojom::ServiceWorkerObjectAssociatedPtr remote_object; |
| remote_object.Bind(std::move(remote_object_ptr_info)); |
| auto state = |
| mojo::ConvertTo<blink::mojom::ServiceWorkerState>(version_->status()); |
| if (sent_state != state) |
| remote_object->StateChanged(state); |
| remote_objects_.AddPtr(std::move(remote_object)); |
| } |
| |
| base::WeakPtr<ServiceWorkerObjectHost> ServiceWorkerObjectHost::AsWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void ServiceWorkerObjectHost::PostMessageToServiceWorker( |
| ::blink::TransferableMessage message) { |
| // When this method is called the encoded_message inside message could just |
| // point to the IPC message's buffer. But that buffer can become invalid |
| // before the message is passed on to the service worker, so make sure |
| // message owns its data. |
| message.EnsureDataIsOwned(); |
| |
| DispatchExtendableMessageEvent(std::move(message), base::DoNothing()); |
| } |
| |
| void ServiceWorkerObjectHost::TerminateForTesting( |
| TerminateForTestingCallback callback) { |
| version_->StopWorker(std::move(callback)); |
| } |
| |
| void ServiceWorkerObjectHost::DispatchExtendableMessageEvent( |
| ::blink::TransferableMessage message, |
| StatusCallback callback) { |
| if (!context_) { |
| std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort); |
| return; |
| } |
| DCHECK_EQ(provider_origin_, |
| url::Origin::Create(provider_host_->document_url())); |
| switch (provider_host_->provider_type()) { |
| case blink::mojom::ServiceWorkerProviderType::kForWindow: |
| service_worker_client_utils::GetClient( |
| provider_host_, |
| base::BindOnce(&DispatchExtendableMessageEventFromClient, version_, |
| std::move(message), provider_origin_, |
| std::move(callback))); |
| return; |
| case blink::mojom::ServiceWorkerProviderType::kForServiceWorker: { |
| // Clamp timeout to the sending worker's remaining timeout, to prevent |
| // postMessage from keeping workers alive forever. |
| base::TimeDelta timeout = |
| provider_host_->running_hosted_version()->remaining_timeout(); |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DispatchExtendableMessageEventFromServiceWorker, |
| version_, std::move(message), provider_origin_, |
| base::make_optional(timeout), std::move(callback), |
| provider_host_->AsWeakPtr())); |
| return; |
| } |
| case blink::mojom::ServiceWorkerProviderType::kForSharedWorker: |
| // Shared workers don't yet have access to ServiceWorker objects, so they |
| // can't postMessage to one (https://crbug.com/371690). |
| case blink::mojom::ServiceWorkerProviderType::kUnknown: |
| break; |
| } |
| NOTREACHED() << provider_host_->provider_type(); |
| } |
| |
| void ServiceWorkerObjectHost::OnConnectionError() { |
| // If there are still bindings, |this| is still being used. |
| if (!bindings_.empty()) |
| return; |
| // Will destroy |this|. |
| provider_host_->RemoveServiceWorkerObjectHost(version_->version_id()); |
| } |
| |
| } // namespace content |