| // Copyright 2013 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_version.h" |
| |
| #include <stddef.h> |
| |
| #include <limits> |
| #include <map> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/check.h" |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/guid.h" |
| #include "base/location.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/post_task.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/default_clock.h" |
| #include "base/time/default_tick_clock.h" |
| #include "base/trace_event/trace_event.h" |
| #include "content/browser/bad_message.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/renderer_host/back_forward_cache_can_store_document_result.h" |
| #include "content/browser/service_worker/payment_handler_support.h" |
| #include "content/browser/service_worker/service_worker_consts.h" |
| #include "content/browser/service_worker/service_worker_container_host.h" |
| #include "content/browser/service_worker/service_worker_context_core.h" |
| #include "content/browser/service_worker/service_worker_context_wrapper.h" |
| #include "content/browser/service_worker/service_worker_host.h" |
| #include "content/browser/service_worker/service_worker_installed_scripts_sender.h" |
| #include "content/browser/service_worker/service_worker_security_utils.h" |
| #include "content/common/content_navigation_policy.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/page_navigator.h" |
| #include "content/public/browser/service_worker_external_request_result.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/result_codes.h" |
| #include "ipc/ipc_message.h" |
| #include "mojo/public/c/system/types.h" |
| #include "net/base/net_errors.h" |
| #include "net/cookies/site_for_cookies.h" |
| #include "net/http/http_response_headers.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/origin_trials/trial_token_validator.h" |
| #include "third_party/blink/public/common/service_worker/service_worker_type_converters.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| |
| namespace content { |
| namespace { |
| |
| // Timeout for an installed worker to start. |
| constexpr base::TimeDelta kStartInstalledWorkerTimeout = base::Seconds(60); |
| |
| // Timeout for a request to be handled. |
| constexpr base::TimeDelta kRequestTimeout = base::Minutes(5); |
| |
| const base::FeatureParam<int> kUpdateDelayParam{ |
| &blink::features::kServiceWorkerUpdateDelay, "update_delay_in_ms", 1000}; |
| |
| // The default value is set to max since it's not used when the feature is |
| // disabled. In that case, the service worker will be terminated by the idle |
| // timeout. |
| const base::FeatureParam<int> kTerminationDelayParam{ |
| &features::kServiceWorkerTerminationOnNoControllee, |
| "termination_delay_in_ms", std::numeric_limits<int>::max()}; |
| |
| const char kClaimClientsStateErrorMesage[] = |
| "Only the active worker can claim clients."; |
| |
| const char kClaimClientsShutdownErrorMesage[] = |
| "Failed to claim clients due to Service Worker system shutdown."; |
| |
| const char kNotRespondingErrorMesage[] = "Service Worker is not responding."; |
| const char kForceUpdateInfoMessage[] = |
| "Service Worker was updated because \"Update on reload\" was " |
| "checked in the DevTools Application panel."; |
| |
| void RunSoon(base::OnceClosure callback) { |
| if (callback) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, |
| std::move(callback)); |
| } |
| } |
| |
| // An adapter to run a |callback| after StartWorker. |
| void RunCallbackAfterStartWorker(base::WeakPtr<ServiceWorkerVersion> version, |
| ServiceWorkerVersion::StatusCallback callback, |
| blink::ServiceWorkerStatusCode status) { |
| if (status == blink::ServiceWorkerStatusCode::kOk && |
| version->running_status() != EmbeddedWorkerStatus::RUNNING) { |
| // We've tried to start the worker (and it has succeeded), but |
| // it looks it's not running yet. |
| NOTREACHED() << "The worker's not running after successful StartWorker"; |
| std::move(callback).Run( |
| blink::ServiceWorkerStatusCode::kErrorStartWorkerFailed); |
| return; |
| } |
| std::move(callback).Run(status); |
| } |
| |
| void ClearTick(base::TimeTicks* time) { |
| *time = base::TimeTicks(); |
| } |
| |
| const int kInvalidTraceId = -1; |
| |
| int NextTraceId() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| static int trace_id = 0; |
| if (trace_id == std::numeric_limits<int>::max()) |
| trace_id = 0; |
| else |
| ++trace_id; |
| DCHECK_NE(kInvalidTraceId, trace_id); |
| return trace_id; |
| } |
| |
| void OnConnectionError(base::WeakPtr<EmbeddedWorkerInstance> embedded_worker) { |
| if (!embedded_worker) |
| return; |
| |
| switch (embedded_worker->status()) { |
| case EmbeddedWorkerStatus::STARTING: |
| case EmbeddedWorkerStatus::RUNNING: |
| // In this case the disconnection might be happening because of sudden |
| // renderer shutdown like crash. |
| embedded_worker->Detach(); |
| break; |
| case EmbeddedWorkerStatus::STOPPING: |
| case EmbeddedWorkerStatus::STOPPED: |
| // Do nothing |
| break; |
| } |
| } |
| |
| void OnOpenWindowFinished( |
| blink::mojom::ServiceWorkerHost::OpenNewTabCallback callback, |
| blink::ServiceWorkerStatusCode status, |
| blink::mojom::ServiceWorkerClientInfoPtr client_info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| const bool success = (status == blink::ServiceWorkerStatusCode::kOk); |
| absl::optional<std::string> error_msg; |
| if (!success) { |
| DCHECK(!client_info); |
| error_msg.emplace("Something went wrong while trying to open the window."); |
| } |
| std::move(callback).Run(success, std::move(client_info), error_msg); |
| } |
| |
| void DidShowPaymentHandlerWindow( |
| const GURL& url, |
| const blink::StorageKey& key, |
| const base::WeakPtr<ServiceWorkerContextCore>& context, |
| blink::mojom::ServiceWorkerHost::OpenPaymentHandlerWindowCallback callback, |
| bool success, |
| int render_process_id, |
| int render_frame_id) { |
| if (success) { |
| service_worker_client_utils::DidNavigate( |
| context, url.DeprecatedGetOriginAsURL(), key, |
| base::BindOnce(&OnOpenWindowFinished, std::move(callback)), |
| GlobalRenderFrameHostId(render_process_id, render_frame_id)); |
| } else { |
| OnOpenWindowFinished(std::move(callback), |
| blink::ServiceWorkerStatusCode::kErrorFailed, |
| nullptr /* client_info */); |
| } |
| } |
| |
| void DidNavigateClient( |
| blink::mojom::ServiceWorkerHost::NavigateClientCallback callback, |
| const GURL& url, |
| blink::ServiceWorkerStatusCode status, |
| blink::mojom::ServiceWorkerClientInfoPtr client) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| const bool success = (status == blink::ServiceWorkerStatusCode::kOk); |
| absl::optional<std::string> error_msg; |
| if (!success) { |
| DCHECK(!client); |
| error_msg.emplace("Cannot navigate to URL: " + url.spec()); |
| } |
| std::move(callback).Run(success, std::move(client), error_msg); |
| } |
| |
| base::TimeDelta GetUpdateDelay() { |
| return base::Milliseconds(kUpdateDelayParam.Get()); |
| } |
| |
| } // namespace |
| |
| constexpr base::TimeDelta ServiceWorkerVersion::kTimeoutTimerDelay; |
| constexpr base::TimeDelta ServiceWorkerVersion::kStartNewWorkerTimeout; |
| constexpr base::TimeDelta ServiceWorkerVersion::kStopWorkerTimeout; |
| |
| ServiceWorkerVersion::MainScriptResponse::MainScriptResponse( |
| const network::mojom::URLResponseHead& response_head) { |
| response_time = response_head.response_time; |
| if (response_head.headers) |
| response_head.headers->GetLastModifiedValue(&last_modified); |
| headers = response_head.headers; |
| if (response_head.ssl_info.has_value()) |
| ssl_info = response_head.ssl_info.value(); |
| } |
| |
| ServiceWorkerVersion::MainScriptResponse::~MainScriptResponse() = default; |
| |
| void ServiceWorkerVersion::RestartTick(base::TimeTicks* time) const { |
| *time = tick_clock_->NowTicks(); |
| } |
| |
| bool ServiceWorkerVersion::RequestExpired( |
| const base::TimeTicks& expiration) const { |
| if (expiration.is_null()) |
| return false; |
| return tick_clock_->NowTicks() >= expiration; |
| } |
| |
| base::TimeDelta ServiceWorkerVersion::GetTickDuration( |
| const base::TimeTicks& time) const { |
| if (time.is_null()) |
| return base::TimeDelta(); |
| return tick_clock_->NowTicks() - time; |
| } |
| |
| ServiceWorkerVersion::ServiceWorkerVersion( |
| ServiceWorkerRegistration* registration, |
| const GURL& script_url, |
| blink::mojom::ScriptType script_type, |
| int64_t version_id, |
| mojo::PendingRemote<storage::mojom::ServiceWorkerLiveVersionRef> |
| remote_reference, |
| base::WeakPtr<ServiceWorkerContextCore> context) |
| : version_id_(version_id), |
| registration_id_(registration->id()), |
| script_url_(script_url), |
| key_(registration->key()), |
| scope_(registration->scope()), |
| script_type_(script_type), |
| fetch_handler_existence_(FetchHandlerExistence::UNKNOWN), |
| registration_status_(registration->status()), |
| context_(context), |
| script_cache_map_(this, context), |
| tick_clock_(base::DefaultTickClock::GetInstance()), |
| clock_(base::DefaultClock::GetInstance()), |
| ping_controller_(this), |
| remote_reference_(std::move(remote_reference)), |
| ukm_source_id_(ukm::ConvertToSourceId(ukm::AssignNewSourceId(), |
| ukm::SourceIdType::WORKER_ID)), |
| reporting_source_(base::UnguessableToken::Create()) { |
| DCHECK_NE(blink::mojom::kInvalidServiceWorkerVersionId, version_id); |
| DCHECK(context_); |
| DCHECK(registration); |
| DCHECK(script_url_.is_valid()); |
| embedded_worker_ = std::make_unique<EmbeddedWorkerInstance>(this); |
| embedded_worker_->AddObserver(this); |
| context_->AddLiveVersion(this); |
| } |
| |
| ServiceWorkerVersion::~ServiceWorkerVersion() { |
| // TODO(falken): Investigate whether this can be removed. The destructor used |
| // to be more complicated and could result in various methods being called. |
| in_dtor_ = true; |
| |
| // Record UMA if the worker was trying to start. One way we get here is if the |
| // user closed the tab before the SW could start up. |
| if (!start_callbacks_.empty()) { |
| // RecordStartWorkerResult must be the first element of start_callbacks_. |
| StatusCallback record_start_worker_result = std::move(start_callbacks_[0]); |
| start_callbacks_.clear(); |
| std::move(record_start_worker_result) |
| .Run(blink::ServiceWorkerStatusCode::kErrorAbort); |
| } |
| |
| if (context_) |
| context_->RemoveLiveVersion(version_id_); |
| |
| embedded_worker_->RemoveObserver(this); |
| } |
| |
| void ServiceWorkerVersion::SetNavigationPreloadState( |
| const blink::mojom::NavigationPreloadState& state) { |
| navigation_preload_state_ = state; |
| } |
| |
| void ServiceWorkerVersion::SetRegistrationStatus( |
| ServiceWorkerRegistration::Status registration_status) { |
| registration_status_ = registration_status; |
| } |
| |
| void ServiceWorkerVersion::SetStatus(Status status) { |
| if (status_ == status) |
| return; |
| |
| TRACE_EVENT2("ServiceWorker", "ServiceWorkerVersion::SetStatus", "Script URL", |
| script_url_.spec(), "New Status", VersionStatusToString(status)); |
| |
| // |fetch_handler_existence_| must be set before setting the status to |
| // INSTALLED, |
| // ACTIVATING or ACTIVATED. |
| DCHECK(fetch_handler_existence_ != FetchHandlerExistence::UNKNOWN || |
| !(status == INSTALLED || status == ACTIVATING || status == ACTIVATED)); |
| |
| status_ = status; |
| if (skip_waiting_) { |
| switch (status_) { |
| case NEW: |
| // |skip_waiting_| should not be set before the version is NEW. |
| NOTREACHED(); |
| return; |
| case INSTALLING: |
| // Do nothing until INSTALLED time. |
| break; |
| case INSTALLED: |
| // Start recording the time when the version is trying to skip waiting. |
| RestartTick(&skip_waiting_time_); |
| break; |
| case ACTIVATING: |
| // Do nothing until ACTIVATED time. |
| break; |
| case ACTIVATED: |
| // Resolve skip waiting promises. |
| ClearTick(&skip_waiting_time_); |
| for (SkipWaitingCallback& callback : pending_skip_waiting_requests_) { |
| std::move(callback).Run(true); |
| } |
| pending_skip_waiting_requests_.clear(); |
| break; |
| case REDUNDANT: |
| // Fail any pending skip waiting requests since this version is dead. |
| for (SkipWaitingCallback& callback : pending_skip_waiting_requests_) { |
| std::move(callback).Run(false); |
| } |
| pending_skip_waiting_requests_.clear(); |
| break; |
| } |
| } |
| |
| // OnVersionStateChanged() invokes updates of the status using state |
| // change IPC at ServiceWorkerObjectHost (for JS-land on renderer process) and |
| // ServiceWorkerContextCore (for devtools and serviceworker-internals). |
| // This should be done before using the new status by |
| // |status_change_callbacks_| which sends the IPC for resolving the .ready |
| // property. |
| // TODO(shimazu): Clarify the dependency of OnVersionStateChanged and |
| // |status_change_callbacks_| |
| for (auto& observer : observers_) |
| observer.OnVersionStateChanged(this); |
| |
| std::vector<base::OnceClosure> callbacks; |
| callbacks.swap(status_change_callbacks_); |
| for (auto& callback : callbacks) |
| std::move(callback).Run(); |
| |
| if (status == INSTALLED) { |
| embedded_worker_->OnWorkerVersionInstalled(); |
| } else if (status == REDUNDANT) { |
| embedded_worker_->OnWorkerVersionDoomed(); |
| |
| // Drop the remote reference to tell the storage system that the worker |
| // script resources can now be deleted. |
| remote_reference_.reset(); |
| } |
| } |
| |
| void ServiceWorkerVersion::RegisterStatusChangeCallback( |
| base::OnceClosure callback) { |
| status_change_callbacks_.push_back(std::move(callback)); |
| } |
| |
| ServiceWorkerVersionInfo ServiceWorkerVersion::GetInfo() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ServiceWorkerVersionInfo info( |
| running_status(), status(), fetch_handler_existence(), script_url(), |
| scope(), key(), registration_id(), version_id(), |
| embedded_worker()->process_id(), embedded_worker()->thread_id(), |
| embedded_worker()->worker_devtools_agent_route_id(), ukm_source_id()); |
| for (const auto& controllee : controllee_map_) { |
| ServiceWorkerContainerHost* container_host = controllee.second.get(); |
| info.clients.emplace(container_host->client_uuid(), |
| container_host->GetServiceWorkerClientInfo()); |
| } |
| |
| info.script_response_time = script_response_time_for_devtools_; |
| if (!main_script_response_) |
| return info; |
| |
| // If the service worker hasn't started, then |main_script_response_| is not |
| // set, so we use |script_response_time_for_devtools_| to populate |info|. If |
| // the worker has started, this value should match with the timestamp stored |
| // in |main_script_response_|. |
| DCHECK_EQ(info.script_response_time, main_script_response_->response_time); |
| info.script_last_modified = main_script_response_->last_modified; |
| |
| return info; |
| } |
| |
| void ServiceWorkerVersion::set_fetch_handler_existence( |
| FetchHandlerExistence existence) { |
| DCHECK_EQ(fetch_handler_existence_, FetchHandlerExistence::UNKNOWN); |
| DCHECK_NE(existence, FetchHandlerExistence::UNKNOWN); |
| fetch_handler_existence_ = existence; |
| } |
| |
| void ServiceWorkerVersion::StartWorker(ServiceWorkerMetrics::EventType purpose, |
| StatusCallback callback) { |
| TRACE_EVENT_INSTANT2( |
| "ServiceWorker", "ServiceWorkerVersion::StartWorker (instant)", |
| TRACE_EVENT_SCOPE_THREAD, "Script", script_url_.spec(), "Purpose", |
| ServiceWorkerMetrics::EventTypeToString(purpose)); |
| |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| const bool is_browser_startup_complete = |
| GetContentClient()->browser()->IsBrowserStartupComplete(); |
| if (!context_) { |
| RecordStartWorkerResult(purpose, status_, kInvalidTraceId, |
| is_browser_startup_complete, |
| blink::ServiceWorkerStatusCode::kErrorAbort); |
| RunSoon(base::BindOnce(std::move(callback), |
| blink::ServiceWorkerStatusCode::kErrorAbort)); |
| return; |
| } |
| if (is_redundant()) { |
| RecordStartWorkerResult(purpose, status_, kInvalidTraceId, |
| is_browser_startup_complete, |
| blink::ServiceWorkerStatusCode::kErrorRedundant); |
| RunSoon(base::BindOnce(std::move(callback), |
| blink::ServiceWorkerStatusCode::kErrorRedundant)); |
| return; |
| } |
| if (!IsStartWorkerAllowed()) { |
| RecordStartWorkerResult(purpose, status_, kInvalidTraceId, |
| is_browser_startup_complete, |
| blink::ServiceWorkerStatusCode::kErrorDisallowed); |
| RunSoon(base::BindOnce(std::move(callback), |
| blink::ServiceWorkerStatusCode::kErrorDisallowed)); |
| return; |
| } |
| |
| if (is_running_start_callbacks_) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&ServiceWorkerVersion::StartWorker, |
| weak_factory_.GetWeakPtr(), purpose, |
| std::move(callback))); |
| return; |
| } |
| |
| // Ensure the live registration during starting worker so that the worker can |
| // get associated with it in |
| // ServiceWorkerHost::CompleteStartWorkerPreparation. |
| context_->registry()->FindRegistrationForId( |
| registration_id_, key_, |
| base::BindOnce( |
| &ServiceWorkerVersion::DidEnsureLiveRegistrationForStartWorker, |
| weak_factory_.GetWeakPtr(), purpose, status_, |
| is_browser_startup_complete, std::move(callback))); |
| } |
| |
| void ServiceWorkerVersion::StopWorker(base::OnceClosure callback) { |
| TRACE_EVENT_INSTANT2("ServiceWorker", |
| "ServiceWorkerVersion::StopWorker (instant)", |
| TRACE_EVENT_SCOPE_THREAD, "Script", script_url_.spec(), |
| "Status", VersionStatusToString(status_)); |
| |
| switch (running_status()) { |
| case EmbeddedWorkerStatus::STARTING: |
| case EmbeddedWorkerStatus::RUNNING: { |
| // EmbeddedWorkerInstance::Stop() may synchronously call |
| // ServiceWorkerVersion::OnStopped() and destroy |this|. This protection |
| // avoids it. |
| scoped_refptr<ServiceWorkerVersion> protect = this; |
| embedded_worker_->Stop(); |
| if (running_status() == EmbeddedWorkerStatus::STOPPED) { |
| RunSoon(std::move(callback)); |
| return; |
| } |
| stop_callbacks_.push_back(std::move(callback)); |
| |
| // Protect |this| until Stop() correctly finished. Otherwise the |
| // |stop_callbacks_| might not be called. The destruction of |this| could |
| // happen before the message OnStopped() when the final |
| // ServiceWorkerObjectHost is destructed because of the termination. |
| // Note that this isn't necessary to be the final element of |
| // |stop_callbacks_| because there's another logic to protect |this| when |
| // calling |stop_callbacks_|. |
| stop_callbacks_.push_back(base::BindOnce( |
| [](scoped_refptr<content::ServiceWorkerVersion>) {}, protect)); |
| return; |
| } |
| case EmbeddedWorkerStatus::STOPPING: |
| stop_callbacks_.push_back(std::move(callback)); |
| return; |
| case EmbeddedWorkerStatus::STOPPED: |
| RunSoon(std::move(callback)); |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| void ServiceWorkerVersion::TriggerIdleTerminationAsap() { |
| needs_to_be_terminated_asap_ = true; |
| endpoint()->SetIdleDelay(base::Seconds(0)); |
| } |
| |
| bool ServiceWorkerVersion::OnRequestTermination() { |
| if (running_status() == EmbeddedWorkerStatus::STOPPING) |
| return true; |
| DCHECK_EQ(EmbeddedWorkerStatus::RUNNING, running_status()); |
| |
| worker_is_idle_on_renderer_ = true; |
| |
| // Determine if the worker can be terminated. |
| bool will_be_terminated = HasNoWork(); |
| if (embedded_worker_->devtools_attached()) { |
| // Basically the service worker won't be terminated if DevTools is attached. |
| // But when activation is happening and this worker needs to be terminated |
| // asap, it'll be terminated. |
| will_be_terminated = needs_to_be_terminated_asap_; |
| |
| if (!will_be_terminated) { |
| // When the worker is being kept alive due to devtools, it's important to |
| // set the service worker's idle delay back to the default value rather |
| // than zero. Otherwise, the service worker might see that it has no work |
| // and immediately send a RequestTermination() back to the browser again, |
| // repeating this over and over. In the non-devtools case, it's |
| // necessarily being kept alive due to an inflight request, and will only |
| // send a RequestTermination() once that request settles (which is the |
| // intended behavior). |
| endpoint()->SetIdleDelay( |
| base::Seconds(blink::mojom::kServiceWorkerDefaultIdleDelayInSeconds)); |
| } |
| } |
| |
| if (will_be_terminated) { |
| embedded_worker_->Stop(); |
| } else { |
| // The worker needs to run more. The worker should start handling queued |
| // events dispatched to the worker directly (e.g. FetchEvent for |
| // subresources). |
| worker_is_idle_on_renderer_ = false; |
| } |
| |
| return will_be_terminated; |
| } |
| |
| void ServiceWorkerVersion::ScheduleUpdate() { |
| if (!context_) |
| return; |
| if (update_timer_.IsRunning()) { |
| update_timer_.Reset(); |
| return; |
| } |
| if (is_update_scheduled_) |
| return; |
| is_update_scheduled_ = true; |
| |
| // Protect |this| until the timer fires, since we may be stopping |
| // and soon no one might hold a reference to us. |
| context_->ProtectVersion(base::WrapRefCounted(this)); |
| |
| update_timer_.Start(FROM_HERE, GetUpdateDelay(), |
| base::BindOnce(&ServiceWorkerVersion::StartUpdate, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ServiceWorkerVersion::StartUpdate() { |
| if (!context_) |
| return; |
| context_->registry()->FindRegistrationForId( |
| registration_id_, key_, |
| base::BindOnce(&ServiceWorkerVersion::FoundRegistrationForUpdate, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| int ServiceWorkerVersion::StartRequest( |
| ServiceWorkerMetrics::EventType event_type, |
| StatusCallback error_callback) { |
| return StartRequestWithCustomTimeout(event_type, std::move(error_callback), |
| kRequestTimeout, KILL_ON_TIMEOUT); |
| } |
| |
| int ServiceWorkerVersion::StartRequestWithCustomTimeout( |
| ServiceWorkerMetrics::EventType event_type, |
| StatusCallback error_callback, |
| const base::TimeDelta& timeout, |
| TimeoutBehavior timeout_behavior) { |
| DCHECK(EmbeddedWorkerStatus::RUNNING == running_status() || |
| EmbeddedWorkerStatus::STARTING == running_status()) |
| << "Can only start a request with a running or starting worker."; |
| DCHECK(event_type == ServiceWorkerMetrics::EventType::INSTALL || |
| event_type == ServiceWorkerMetrics::EventType::ACTIVATE || |
| event_type == ServiceWorkerMetrics::EventType::MESSAGE || |
| event_type == ServiceWorkerMetrics::EventType::EXTERNAL_REQUEST || |
| status() == ACTIVATED) |
| << "Event of type " << static_cast<int>(event_type) |
| << " can only be dispatched to an active worker: " << status(); |
| |
| // |context_| is needed for some bookkeeping. If there's no context, the |
| // request will be aborted soon, so don't bother aborting the request directly |
| // here, and just skip this bookkeeping. |
| if (context_) { |
| if (event_type != ServiceWorkerMetrics::EventType::INSTALL && |
| event_type != ServiceWorkerMetrics::EventType::ACTIVATE && |
| event_type != ServiceWorkerMetrics::EventType::MESSAGE) { |
| // Reset the self-update delay iff this is not an event that can triggered |
| // by a service worker itself. Otherwise, service workers can use update() |
| // to keep running forever via install and activate events, or |
| // postMessage() between themselves to reset the delay via message event. |
| // postMessage() resets the delay in ServiceWorkerObjectHost, iff it |
| // didn't come from a service worker. |
| scoped_refptr<ServiceWorkerRegistration> registration = |
| context_->GetLiveRegistration(registration_id_); |
| DCHECK(registration) << "running workers should have a live registration"; |
| registration->set_self_update_delay(base::TimeDelta()); |
| } |
| } |
| |
| auto request = std::make_unique<InflightRequest>( |
| std::move(error_callback), clock_->Now(), tick_clock_->NowTicks(), |
| event_type); |
| InflightRequest* request_rawptr = request.get(); |
| int request_id = inflight_requests_.Add(std::move(request)); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN2( |
| "ServiceWorker", "ServiceWorkerVersion::Request", |
| TRACE_ID_LOCAL(request_rawptr), "Request id", request_id, "Event type", |
| ServiceWorkerMetrics::EventTypeToString(event_type)); |
| |
| base::TimeTicks expiration_time = tick_clock_->NowTicks() + timeout; |
| bool is_inserted = false; |
| std::set<InflightRequestTimeoutInfo>::iterator iter; |
| std::tie(iter, is_inserted) = request_timeouts_.emplace( |
| request_id, event_type, expiration_time, timeout_behavior); |
| DCHECK(is_inserted); |
| request_rawptr->timeout_iter = iter; |
| if (expiration_time > max_request_expiration_time_) |
| max_request_expiration_time_ = expiration_time; |
| |
| // Even if the worker is in the idle state, the new event which is about to |
| // be dispatched will reset the idle status. That means the worker can receive |
| // events directly from any client, so we cannot trigger OnNoWork after this |
| // point. |
| worker_is_idle_on_renderer_ = false; |
| return request_id; |
| } |
| |
| ServiceWorkerExternalRequestResult ServiceWorkerVersion::StartExternalRequest( |
| const std::string& request_uuid) { |
| if (running_status() == EmbeddedWorkerStatus::STARTING) { |
| return pending_external_requests_.insert(request_uuid).second |
| ? ServiceWorkerExternalRequestResult::kOk |
| : ServiceWorkerExternalRequestResult::kBadRequestId; |
| } |
| |
| if (running_status() == EmbeddedWorkerStatus::STOPPING || |
| running_status() == EmbeddedWorkerStatus::STOPPED) { |
| return ServiceWorkerExternalRequestResult::kWorkerNotRunning; |
| } |
| |
| if (base::Contains(external_request_uuid_to_request_id_, request_uuid)) |
| return ServiceWorkerExternalRequestResult::kBadRequestId; |
| |
| int request_id = |
| StartRequest(ServiceWorkerMetrics::EventType::EXTERNAL_REQUEST, |
| base::BindOnce(&ServiceWorkerVersion::CleanUpExternalRequest, |
| this, request_uuid)); |
| external_request_uuid_to_request_id_[request_uuid] = request_id; |
| return ServiceWorkerExternalRequestResult::kOk; |
| } |
| |
| bool ServiceWorkerVersion::FinishRequest(int request_id, bool was_handled) { |
| return FinishRequestWithFetchCount(request_id, was_handled, |
| /*fetch_count=*/0); |
| } |
| |
| bool ServiceWorkerVersion::FinishRequestWithFetchCount(int request_id, |
| bool was_handled, |
| uint32_t fetch_count) { |
| InflightRequest* request = inflight_requests_.Lookup(request_id); |
| if (!request) |
| return false; |
| ServiceWorkerMetrics::RecordEventDuration( |
| request->event_type, tick_clock_->NowTicks() - request->start_time_ticks, |
| was_handled, fetch_count); |
| |
| TRACE_EVENT_NESTABLE_ASYNC_END1( |
| "ServiceWorker", "ServiceWorkerVersion::Request", TRACE_ID_LOCAL(request), |
| "Handled", was_handled); |
| request_timeouts_.erase(request->timeout_iter); |
| inflight_requests_.Remove(request_id); |
| |
| if (!HasWorkInBrowser()) |
| OnNoWorkInBrowser(); |
| return true; |
| } |
| |
| ServiceWorkerExternalRequestResult ServiceWorkerVersion::FinishExternalRequest( |
| const std::string& request_uuid) { |
| if (running_status() == EmbeddedWorkerStatus::STARTING) { |
| return pending_external_requests_.erase(request_uuid) > 0u |
| ? ServiceWorkerExternalRequestResult::kOk |
| : ServiceWorkerExternalRequestResult::kBadRequestId; |
| } |
| |
| // If it's STOPPED, there is no request to finish. We could just consider this |
| // a success, but the caller may want to know about it. (If it's STOPPING, |
| // proceed with finishing the request as normal.) |
| if (running_status() == EmbeddedWorkerStatus::STOPPED) |
| return ServiceWorkerExternalRequestResult::kWorkerNotRunning; |
| |
| auto iter = external_request_uuid_to_request_id_.find(request_uuid); |
| if (iter != external_request_uuid_to_request_id_.end()) { |
| int request_id = iter->second; |
| external_request_uuid_to_request_id_.erase(iter); |
| return FinishRequest(request_id, /*was_handled=*/true) |
| ? ServiceWorkerExternalRequestResult::kOk |
| : ServiceWorkerExternalRequestResult::kBadRequestId; |
| } |
| |
| // It is possible that the request was cancelled or timed out before and we |
| // won't find it in |external_request_uuid_to_request_id_|. Just return |
| // kOk. |
| // TODO(falken): Consider keeping track of these so we can return |
| // kBadRequestId for invalid requests ids. |
| return ServiceWorkerExternalRequestResult::kOk; |
| } |
| |
| ServiceWorkerVersion::SimpleEventCallback |
| ServiceWorkerVersion::CreateSimpleEventCallback(int request_id) { |
| // The weak reference to |this| is safe because storage of the callbacks, the |
| // inflight responses of blink::mojom::ServiceWorker messages, is owned by |
| // |this|. |
| return base::BindOnce(&ServiceWorkerVersion::OnSimpleEventFinished, |
| base::Unretained(this), request_id); |
| } |
| |
| void ServiceWorkerVersion::RunAfterStartWorker( |
| ServiceWorkerMetrics::EventType purpose, |
| StatusCallback callback) { |
| if (running_status() == EmbeddedWorkerStatus::RUNNING) { |
| DCHECK(start_callbacks_.empty()); |
| std::move(callback).Run(blink::ServiceWorkerStatusCode::kOk); |
| return; |
| } |
| StartWorker(purpose, |
| base::BindOnce(&RunCallbackAfterStartWorker, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ServiceWorkerVersion::AddControllee( |
| ServiceWorkerContainerHost* container_host) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // TODO(crbug.com/1021718): Remove this CHECK once we figure out the cause of |
| // crash. |
| CHECK(container_host); |
| const std::string& uuid = container_host->client_uuid(); |
| CHECK(!container_host->client_uuid().empty()); |
| // TODO(crbug.com/1021718): Change to DCHECK once we figure out the cause of |
| // crash. |
| CHECK(!base::Contains(controllee_map_, uuid)); |
| |
| // Set the idle timeout to the default value if there's no controllee and the |
| // worker is running because the worker's idle delay has been set to a shorter |
| // value when all controllee are gone. |
| MaybeUpdateIdleDelayForTerminationOnNoControllee( |
| base::Seconds(blink::mojom::kServiceWorkerDefaultIdleDelayInSeconds)); |
| |
| controllee_map_[uuid] = container_host->GetWeakPtr(); |
| embedded_worker_->UpdateForegroundPriority(); |
| ClearTick(&no_controllees_time_); |
| |
| scoped_refptr<ServiceWorkerRegistration> registration = |
| context_->GetLiveRegistration(registration_id_); |
| if (registration) { |
| registration->set_self_update_delay(base::TimeDelta()); |
| } |
| |
| // Notify observers asynchronously for consistency with RemoveControllee. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&ServiceWorkerVersion::NotifyControlleeAdded, |
| weak_factory_.GetWeakPtr(), uuid, |
| container_host->GetServiceWorkerClientInfo())); |
| |
| // Also send a notification if OnEndNavigationCommit() was already invoked for |
| // this container. |
| if (container_host->navigation_commit_ended()) { |
| OnControlleeNavigationCommitted(container_host->client_uuid(), |
| container_host->GetRenderFrameHostId()); |
| } |
| } |
| |
| void ServiceWorkerVersion::RemoveControllee(const std::string& client_uuid) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // TODO(crbug.com/1015692): Remove this once RemoveControllee() matches with |
| // AddControllee(). |
| if (!base::Contains(controllee_map_, client_uuid)) |
| return; |
| |
| controllee_map_.erase(client_uuid); |
| |
| embedded_worker_->UpdateForegroundPriority(); |
| |
| // Notify observers asynchronously since this gets called during |
| // ServiceWorkerHost's destructor, and we don't want observers to do work |
| // during that. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&ServiceWorkerVersion::NotifyControlleeRemoved, |
| weak_factory_.GetWeakPtr(), client_uuid)); |
| |
| // When a new controllee checks in before the delay passes, the idle delay |
| // is set to the default in AddControllee(). |
| MaybeUpdateIdleDelayForTerminationOnNoControllee( |
| base::Milliseconds(kTerminationDelayParam.Get())); |
| } |
| |
| void ServiceWorkerVersion::OnControlleeNavigationCommitted( |
| const std::string& client_uuid, |
| const GlobalRenderFrameHostId& rfh_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| #if DCHECK_IS_ON() |
| // Ensures this function is only called for a known window client. |
| auto it = controllee_map_.find(client_uuid); |
| DCHECK(it != controllee_map_.end()); |
| |
| DCHECK_EQ(it->second->GetClientType(), |
| blink::mojom::ServiceWorkerClientType::kWindow); |
| #endif // DCHECK_IS_ON() |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ServiceWorkerVersion::NotifyControlleeNavigationCommitted, |
| weak_factory_.GetWeakPtr(), client_uuid, rfh_id)); |
| } |
| |
| void ServiceWorkerVersion::MoveControlleeToBackForwardCacheMap( |
| const std::string& client_uuid) { |
| DCHECK(IsBackForwardCacheEnabled()); |
| DCHECK(base::Contains(controllee_map_, client_uuid)); |
| DCHECK(!base::Contains(bfcached_controllee_map_, client_uuid)); |
| bfcached_controllee_map_[client_uuid] = controllee_map_[client_uuid]; |
| RemoveControllee(client_uuid); |
| } |
| |
| void ServiceWorkerVersion::RestoreControlleeFromBackForwardCacheMap( |
| const std::string& client_uuid) { |
| // TODO(crbug.com/1021718): Change these to DCHECK once we figure out the |
| // cause of crash. |
| CHECK(IsBackForwardCacheEnabled()); |
| CHECK(!base::Contains(controllee_map_, client_uuid)); |
| if (!base::Contains(bfcached_controllee_map_, client_uuid)) { |
| // We are navigating to the page using BackForwardCache, which is being |
| // evicted due to activation, postMessage or claim. In this case, we reload |
| // the page without using BackForwardCache, so we can assume that |
| // ContainerHost will be deleted soon. |
| // TODO(crbug.com/1021718): Remove this CHECK once we fix the crash. |
| CHECK(base::Contains(controllees_to_be_evicted_, client_uuid)); |
| // TODO(crbug.com/1021718): Remove DumpWithoutCrashing once we confirm the |
| // cause of the crash. |
| BackForwardCacheCanStoreDocumentResult can_store; |
| can_store.No(controllees_to_be_evicted_.at(client_uuid)); |
| TRACE_EVENT( |
| "navigation", |
| "ServiceWorkerVersion::RestoreControlleeFromBackForwardCacheMap", |
| ChromeTrackEvent::kBackForwardCacheCanStoreDocumentResult, can_store); |
| SCOPED_CRASH_KEY_STRING32("RestoreForBFCache", "no_controllee_reason", |
| can_store.ToString()); |
| base::debug::DumpWithoutCrashing(); |
| return; |
| } |
| AddControllee(bfcached_controllee_map_.at(client_uuid).get()); |
| bfcached_controllee_map_.erase(client_uuid); |
| } |
| |
| void ServiceWorkerVersion::RemoveControlleeFromBackForwardCacheMap( |
| const std::string& client_uuid) { |
| DCHECK(IsBackForwardCacheEnabled()); |
| DCHECK(base::Contains(bfcached_controllee_map_, client_uuid)); |
| bfcached_controllee_map_.erase(client_uuid); |
| } |
| |
| void ServiceWorkerVersion::Uncontrol(const std::string& client_uuid) { |
| if (!IsBackForwardCacheEnabled()) { |
| RemoveControllee(client_uuid); |
| } else { |
| if (base::Contains(controllee_map_, client_uuid)) { |
| RemoveControllee(client_uuid); |
| } else if (base::Contains(bfcached_controllee_map_, client_uuid)) { |
| RemoveControlleeFromBackForwardCacheMap(client_uuid); |
| } else { |
| // It is possible that the controllee belongs to neither |controllee_map_| |
| // or |bfcached_controllee_map_|. This happens when a BackForwardCached |
| // controllee is deleted after eviction, which has already removed it from |
| // |bfcached_controllee_map_|. |
| // In this case, |controllees_to_be_evicted_| should contain the |
| // controllee. |
| // TODO(crbug.com/1021718): Remove this CHECK once we fix the crash. |
| CHECK(base::Contains(controllees_to_be_evicted_, client_uuid)); |
| controllees_to_be_evicted_.erase(client_uuid); |
| } |
| } |
| } |
| |
| void ServiceWorkerVersion::EvictBackForwardCachedControllees( |
| BackForwardCacheMetrics::NotRestoredReason reason) { |
| DCHECK(IsBackForwardCacheEnabled()); |
| while (!bfcached_controllee_map_.empty()) { |
| auto controllee = bfcached_controllee_map_.begin(); |
| EvictBackForwardCachedControllee(controllee->second.get(), reason); |
| } |
| } |
| |
| void ServiceWorkerVersion::EvictBackForwardCachedControllee( |
| ServiceWorkerContainerHost* controllee, |
| BackForwardCacheMetrics::NotRestoredReason reason) { |
| controllee->EvictFromBackForwardCache(reason); |
| controllees_to_be_evicted_[controllee->client_uuid()] = reason; |
| RemoveControlleeFromBackForwardCacheMap(controllee->client_uuid()); |
| } |
| |
| void ServiceWorkerVersion::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void ServiceWorkerVersion::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void ServiceWorkerVersion::ReportError(blink::ServiceWorkerStatusCode status, |
| const std::string& status_message) { |
| if (status_message.empty()) { |
| OnReportException( |
| base::UTF8ToUTF16(blink::ServiceWorkerStatusToString(status)), -1, -1, |
| GURL()); |
| } else { |
| OnReportException(base::UTF8ToUTF16(status_message), -1, -1, GURL()); |
| } |
| } |
| |
| void ServiceWorkerVersion::ReportForceUpdateToDevTools() { |
| AddMessageToConsole(blink::mojom::ConsoleMessageLevel::kWarning, |
| kForceUpdateInfoMessage); |
| } |
| |
| void ServiceWorkerVersion::SetStartWorkerStatusCode( |
| blink::ServiceWorkerStatusCode status) { |
| start_worker_status_ = status; |
| } |
| |
| void ServiceWorkerVersion::Doom() { |
| // Protect |this| because NotifyControllerLost() and Stop() callees |
| // may drop references to |this|. |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| |
| // Tell controllees that this version is dead. Each controllee will call |
| // ServiceWorkerVersion::RemoveControllee(), so be careful with iterators. |
| auto iter = controllee_map_.begin(); |
| while (iter != controllee_map_.end()) { |
| ServiceWorkerContainerHost* container_host = iter->second.get(); |
| ++iter; |
| container_host->NotifyControllerLost(); |
| } |
| // Tell the bfcached controllees that this version is dead. Each controllee |
| // will call ServiceWorkerContainerHost:EvictFromBackForwardCache(). |
| // Called when this container host's controller has been terminated and |
| // doomed. This can happen in several cases: |
| // - A fatal error when trying to start the service worker, like an installed |
| // script is unable to read from storage. |
| // - The service worker was forcibly remoevd due to ClearSiteData or browser |
| // setting. |
| // - If this is a client in the back/forward cache, the service worker may |
| // still be normally unregistered, because back/forward cached clients do not |
| // count as true controllees for service worker lifecycle purposes. |
| auto bf_iter = bfcached_controllee_map_.begin(); |
| while (bf_iter != bfcached_controllee_map_.end()) { |
| ServiceWorkerContainerHost* bf_container_host = bf_iter->second.get(); |
| ++bf_iter; |
| bf_container_host->NotifyControllerLost(); |
| } |
| |
| // Any controllee this version had should have removed itself. |
| DCHECK(!HasControllee()); |
| |
| SetStatus(REDUNDANT); |
| if (running_status() == EmbeddedWorkerStatus::STARTING || |
| running_status() == EmbeddedWorkerStatus::RUNNING) { |
| // |start_worker_status_| == kErrorExists means that this version was |
| // created for update but the script was identical to the incumbent version. |
| // In this case we should stop the worker immediately even when DevTools is |
| // attached. Otherwise the redundant worker stays as a selectable context |
| // in DevTools' console. |
| // TODO(bashi): Remove this workaround when byte-for-byte update check is |
| // shipped. |
| bool stop_immediately = |
| start_worker_status_ == blink::ServiceWorkerStatusCode::kErrorExists; |
| if (stop_immediately || !embedded_worker()->devtools_attached()) { |
| embedded_worker_->Stop(); |
| } else { |
| stop_when_devtools_detached_ = true; |
| } |
| } |
| } |
| |
| void ServiceWorkerVersion::InitializeGlobalScope() { |
| receiver_.reset(); |
| receiver_.Bind(service_worker_host_.InitWithNewEndpointAndPassReceiver()); |
| |
| scoped_refptr<ServiceWorkerRegistration> registration = |
| context_->GetLiveRegistration(registration_id_); |
| // The registration must exist since we keep a reference to it during |
| // service worker startup. |
| DCHECK(registration); |
| DCHECK(worker_host_); |
| DCHECK(service_worker_remote_); |
| service_worker_remote_->InitializeGlobalScope( |
| std::move(service_worker_host_), |
| worker_host_->container_host()->CreateServiceWorkerRegistrationObjectInfo( |
| std::move(registration)), |
| worker_host_->container_host()->CreateServiceWorkerObjectInfoToSend(this), |
| fetch_handler_existence_, std::move(reporting_observer_receiver_)); |
| |
| is_endpoint_ready_ = true; |
| } |
| |
| bool ServiceWorkerVersion::IsControlleeProcessID(int process_id) const { |
| for (const auto& controllee : controllee_map_) { |
| if (controllee.second && controllee.second->GetProcessId() == process_id) |
| return true; |
| } |
| return false; |
| } |
| |
| void ServiceWorkerVersion::ExecuteScriptForTest( |
| const std::string& script, |
| ServiceWorkerScriptExecutionCallback callback) { |
| DCHECK(running_status() == EmbeddedWorkerStatus::STARTING || |
| running_status() == EmbeddedWorkerStatus::RUNNING) |
| << "Cannot execute a script in a non-running worker!"; |
| bool wants_result = !callback.is_null(); |
| endpoint()->ExecuteScriptForTest( // IN-TEST |
| base::UTF8ToUTF16(script), wants_result, std::move(callback)); |
| } |
| |
| void ServiceWorkerVersion::SetValidOriginTrialTokens( |
| const blink::TrialTokenValidator::FeatureToTokensMap& tokens) { |
| origin_trial_tokens_ = validator_.GetValidTokens( |
| url::Origin::Create(scope()), tokens, clock_->Now()); |
| } |
| |
| void ServiceWorkerVersion::SetDevToolsAttached(bool attached) { |
| embedded_worker()->SetDevToolsAttached(attached); |
| |
| if (stop_when_devtools_detached_ && !attached) { |
| DCHECK_EQ(REDUNDANT, status()); |
| if (running_status() == EmbeddedWorkerStatus::STARTING || |
| running_status() == EmbeddedWorkerStatus::RUNNING) { |
| embedded_worker_->Stop(); |
| } |
| return; |
| } |
| if (attached) { |
| // TODO(falken): Canceling the timeouts when debugging could cause |
| // heisenbugs; we should instead run them as normal show an educational |
| // message in DevTools when they occur. crbug.com/470419 |
| |
| // Don't record the startup time metric once DevTools is attached. |
| ClearTick(&start_time_); |
| skip_recording_startup_time_ = true; |
| |
| // Cancel request timeouts. |
| SetAllRequestExpirations(base::TimeTicks()); |
| return; |
| } |
| if (!start_callbacks_.empty()) { |
| // Reactivate the timer for start timeout. |
| DCHECK(timeout_timer_.IsRunning()); |
| DCHECK(running_status() == EmbeddedWorkerStatus::STARTING || |
| running_status() == EmbeddedWorkerStatus::STOPPING) |
| << static_cast<int>(running_status()); |
| RestartTick(&start_time_); |
| } |
| |
| // Reactivate request timeouts, setting them all to the same expiration time. |
| SetAllRequestExpirations(tick_clock_->NowTicks() + kRequestTimeout); |
| } |
| |
| void ServiceWorkerVersion::SetMainScriptResponse( |
| std::unique_ptr<MainScriptResponse> response) { |
| script_response_time_for_devtools_ = response->response_time; |
| main_script_response_ = std::move(response); |
| |
| // Updates |origin_trial_tokens_| if it is not set yet. This happens when: |
| // 1) The worker is a new one. |
| // OR |
| // 2) The worker is an existing one but the entry in ServiceWorkerDatabase |
| // was written by old version Chrome (< M56), so |origin_trial_tokens| |
| // wasn't set in the entry. |
| if (!origin_trial_tokens_) { |
| origin_trial_tokens_ = validator_.GetValidTokensFromHeaders( |
| url::Origin::Create(scope()), main_script_response_->headers.get(), |
| clock_->Now()); |
| } |
| |
| if (context_) { |
| context_->OnMainScriptResponseSet(version_id(), *main_script_response_); |
| } |
| } |
| |
| void ServiceWorkerVersion::SimulatePingTimeoutForTesting() { |
| ping_controller_.SimulateTimeoutForTesting(); |
| } |
| |
| void ServiceWorkerVersion::SetTickClockForTesting( |
| const base::TickClock* tick_clock) { |
| tick_clock_ = tick_clock; |
| } |
| |
| bool ServiceWorkerVersion::HasNoWork() const { |
| return !HasWorkInBrowser() && worker_is_idle_on_renderer_; |
| } |
| |
| const ServiceWorkerVersion::MainScriptResponse* |
| ServiceWorkerVersion::GetMainScriptResponse() { |
| return main_script_response_.get(); |
| } |
| |
| ServiceWorkerVersion::InflightRequestTimeoutInfo::InflightRequestTimeoutInfo( |
| int id, |
| ServiceWorkerMetrics::EventType event_type, |
| const base::TimeTicks& expiration, |
| TimeoutBehavior timeout_behavior) |
| : id(id), |
| event_type(event_type), |
| expiration(expiration), |
| timeout_behavior(timeout_behavior) {} |
| |
| ServiceWorkerVersion::InflightRequestTimeoutInfo:: |
| ~InflightRequestTimeoutInfo() {} |
| |
| bool ServiceWorkerVersion::InflightRequestTimeoutInfo::operator<( |
| const InflightRequestTimeoutInfo& other) const { |
| if (expiration == other.expiration) |
| return id < other.id; |
| return expiration < other.expiration; |
| } |
| |
| ServiceWorkerVersion::InflightRequest::InflightRequest( |
| StatusCallback callback, |
| base::Time time, |
| const base::TimeTicks& time_ticks, |
| ServiceWorkerMetrics::EventType event_type) |
| : error_callback(std::move(callback)), |
| start_time(time), |
| start_time_ticks(time_ticks), |
| event_type(event_type) {} |
| |
| ServiceWorkerVersion::InflightRequest::~InflightRequest() {} |
| |
| void ServiceWorkerVersion::OnScriptEvaluationStart() { |
| DCHECK_EQ(EmbeddedWorkerStatus::STARTING, running_status()); |
| // Activate ping/pong now that JavaScript execution will start. |
| ping_controller_.Activate(); |
| } |
| |
| void ServiceWorkerVersion::OnStarting() { |
| for (auto& observer : observers_) |
| observer.OnRunningStateChanged(this); |
| } |
| |
| void ServiceWorkerVersion::OnStarted( |
| blink::mojom::ServiceWorkerStartStatus start_status, |
| bool has_fetch_handler) { |
| DCHECK_EQ(EmbeddedWorkerStatus::RUNNING, running_status()); |
| |
| // TODO(falken): This maps kAbruptCompletion to kErrorScriptEvaluated, which |
| // most start callbacks will consider to be a failure. But the worker thread |
| // is running, and the spec considers it a success, so the callbacks should |
| // change to treat kErrorScriptEvaluated as success, or use |
| // ServiceWorkerStartStatus directly. |
| blink::ServiceWorkerStatusCode status = |
| mojo::ConvertTo<blink::ServiceWorkerStatusCode>(start_status); |
| |
| if (status == blink::ServiceWorkerStatusCode::kOk && |
| fetch_handler_existence_ == FetchHandlerExistence::UNKNOWN) { |
| set_fetch_handler_existence(has_fetch_handler |
| ? FetchHandlerExistence::EXISTS |
| : FetchHandlerExistence::DOES_NOT_EXIST); |
| } |
| |
| // Fire all start callbacks. |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| FinishStartWorker(status); |
| for (auto& observer : observers_) |
| observer.OnRunningStateChanged(this); |
| |
| if (!pending_external_requests_.empty()) { |
| std::set<std::string> pending_external_requests; |
| std::swap(pending_external_requests_, pending_external_requests); |
| for (const std::string& request_uuid : pending_external_requests) |
| StartExternalRequest(request_uuid); |
| } |
| |
| MaybeUpdateIdleDelayForTerminationOnNoControllee( |
| base::Milliseconds(kTerminationDelayParam.Get())); |
| } |
| |
| void ServiceWorkerVersion::OnStopping() { |
| DCHECK(stop_time_.is_null()); |
| RestartTick(&stop_time_); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN2( |
| "ServiceWorker", "ServiceWorkerVersion::StopWorker", |
| TRACE_ID_WITH_SCOPE("ServiceWorkerVersion::StopWorker", |
| stop_time_.since_origin().InMicroseconds()), |
| "Script", script_url_.spec(), "Version Status", |
| VersionStatusToString(status_)); |
| |
| // Endpoint isn't available after calling EmbeddedWorkerInstance::Stop(). |
| // This needs to be set here without waiting until the worker is actually |
| // stopped because subsequent StartWorker() may read the flag to decide |
| // whether an event can be dispatched or not. |
| is_endpoint_ready_ = false; |
| |
| // Shorten the interval so stalling in stopped can be fixed quickly. Once the |
| // worker stops, the timer is disabled. The interval will be reset to normal |
| // when the worker starts up again. |
| SetTimeoutTimerInterval(kStopWorkerTimeout); |
| for (auto& observer : observers_) |
| observer.OnRunningStateChanged(this); |
| } |
| |
| void ServiceWorkerVersion::OnStopped(EmbeddedWorkerStatus old_status) { |
| OnStoppedInternal(old_status); |
| } |
| |
| void ServiceWorkerVersion::OnDetached(EmbeddedWorkerStatus old_status) { |
| OnStoppedInternal(old_status); |
| } |
| |
| void ServiceWorkerVersion::OnRegisteredToDevToolsManager() { |
| for (auto& observer : observers_) |
| observer.OnDevToolsRoutingIdChanged(this); |
| } |
| |
| void ServiceWorkerVersion::OnReportException( |
| const std::u16string& error_message, |
| int line_number, |
| int column_number, |
| const GURL& source_url) { |
| for (auto& observer : observers_) { |
| observer.OnErrorReported(this, error_message, line_number, column_number, |
| source_url); |
| } |
| } |
| |
| void ServiceWorkerVersion::OnReportConsoleMessage( |
| blink::mojom::ConsoleMessageSource source, |
| blink::mojom::ConsoleMessageLevel message_level, |
| const std::u16string& message, |
| int line_number, |
| const GURL& source_url) { |
| for (auto& observer : observers_) { |
| observer.OnReportConsoleMessage(this, source, message_level, message, |
| line_number, source_url); |
| } |
| } |
| |
| void ServiceWorkerVersion::OnStartSent(blink::ServiceWorkerStatusCode status) { |
| if (status != blink::ServiceWorkerStatusCode::kOk) { |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| FinishStartWorker(DeduceStartWorkerFailureReason(status)); |
| } |
| } |
| |
| void ServiceWorkerVersion::SetCachedMetadata(const GURL& url, |
| base::span<const uint8_t> data) { |
| int64_t callback_id = |
| base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds(); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN1( |
| "ServiceWorker", "ServiceWorkerVersion::SetCachedMetadata", |
| TRACE_ID_WITH_SCOPE("ServiceWorkerVersion::SetCachedMetadata", |
| callback_id), |
| "URL", url.spec()); |
| script_cache_map_.WriteMetadata( |
| url, data, |
| base::BindOnce(&ServiceWorkerVersion::OnSetCachedMetadataFinished, |
| weak_factory_.GetWeakPtr(), callback_id, data.size())); |
| } |
| |
| void ServiceWorkerVersion::ClearCachedMetadata(const GURL& url) { |
| int64_t callback_id = |
| base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds(); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN1( |
| "ServiceWorker", "ServiceWorkerVersion::ClearCachedMetadata", |
| TRACE_ID_WITH_SCOPE("ServiceWorkerVersion::ClearCachedMetadata", |
| callback_id), |
| "URL", url.spec()); |
| script_cache_map_.ClearMetadata( |
| url, base::BindOnce(&ServiceWorkerVersion::OnClearCachedMetadataFinished, |
| weak_factory_.GetWeakPtr(), callback_id)); |
| } |
| |
| void ServiceWorkerVersion::ClaimClients(ClaimClientsCallback callback) { |
| if (status_ != ACTIVATING && status_ != ACTIVATED) { |
| std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kState, |
| std::string(kClaimClientsStateErrorMesage)); |
| return; |
| } |
| if (!context_) { |
| std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kAbort, |
| std::string(kClaimClientsShutdownErrorMesage)); |
| return; |
| } |
| |
| scoped_refptr<ServiceWorkerRegistration> registration = |
| context_->GetLiveRegistration(registration_id_); |
| // Registration must be kept alive by ServiceWorkerGlobalScope#registration. |
| if (!registration) { |
| mojo::ReportBadMessage("ClaimClients: No live registration"); |
| // ReportBadMessage() will kill the renderer process, but Mojo complains if |
| // the callback is not run. Just run it with nonsense arguments. |
| std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown, |
| std::string()); |
| return; |
| } |
| |
| registration->ClaimClients(); |
| std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kNone, |
| absl::nullopt); |
| } |
| |
| void ServiceWorkerVersion::GetClients( |
| blink::mojom::ServiceWorkerClientQueryOptionsPtr options, |
| GetClientsCallback callback) { |
| service_worker_client_utils::GetClients( |
| weak_factory_.GetWeakPtr(), std::move(options), std::move(callback)); |
| } |
| |
| void ServiceWorkerVersion::GetClient(const std::string& client_uuid, |
| GetClientCallback callback) { |
| if (!context_) { |
| // The promise will be resolved to 'undefined'. |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| ServiceWorkerContainerHost* container_host = |
| context_->GetContainerHostByClientID(client_uuid); |
| if (!container_host || container_host->url().DeprecatedGetOriginAsURL() != |
| script_url_.DeprecatedGetOriginAsURL()) { |
| // The promise will be resolved to 'undefined'. |
| // Note that we don't BadMessage here since Clients#get() can be passed an |
| // arbitrary UUID. The BadMessages for the origin mismatches below are |
| // appropriate because the UUID is taken directly from a Client object so we |
| // expect it to be valid. |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| if (!container_host->is_execution_ready()) { |
| container_host->AddExecutionReadyCallback( |
| base::BindOnce(&ServiceWorkerVersion::GetClientInternal, this, |
| client_uuid, std::move(callback))); |
| return; |
| } |
| service_worker_client_utils::GetClient(container_host, std::move(callback)); |
| } |
| |
| void ServiceWorkerVersion::GetClientInternal(const std::string& client_uuid, |
| GetClientCallback callback) { |
| ServiceWorkerContainerHost* container_host = |
| context_->GetContainerHostByClientID(client_uuid); |
| if (!container_host || !container_host->is_execution_ready()) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| service_worker_client_utils::GetClient(container_host, std::move(callback)); |
| } |
| |
| void ServiceWorkerVersion::OpenNewTab(const GURL& url, |
| OpenNewTabCallback callback) { |
| // TODO(crbug.com/1199077): After StorageKey implements partitioning update |
| // this to reject with InvalidAccessError if key_ is partitioned. |
| OpenWindow(url, service_worker_client_utils::WindowType::NEW_TAB_WINDOW, |
| std::move(callback)); |
| } |
| |
| void ServiceWorkerVersion::OpenPaymentHandlerWindow( |
| const GURL& url, |
| OpenPaymentHandlerWindowCallback callback) { |
| // Just respond failure if we are shutting down. |
| if (!context_) { |
| std::move(callback).Run( |
| false /* success */, nullptr /* client */, |
| std::string("The service worker system is shutting down.")); |
| return; |
| } |
| |
| if (!url.is_valid() || !key_.origin().IsSameOriginWith(url)) { |
| mojo::ReportBadMessage( |
| "Received PaymentRequestEvent#openWindow() request for a cross-origin " |
| "URL."); |
| receiver_.reset(); |
| return; |
| } |
| |
| PaymentHandlerSupport::ShowPaymentHandlerWindow( |
| url, context_.get(), |
| base::BindOnce(&DidShowPaymentHandlerWindow, url, key_, context_), |
| base::BindOnce( |
| &ServiceWorkerVersion::OpenWindow, weak_factory_.GetWeakPtr(), url, |
| service_worker_client_utils::WindowType::PAYMENT_HANDLER_WINDOW), |
| std::move(callback)); |
| } |
| |
| void ServiceWorkerVersion::PostMessageToClient( |
| const std::string& client_uuid, |
| blink::TransferableMessage message) { |
| if (!context_) |
| return; |
| ServiceWorkerContainerHost* container_host = |
| context_->GetContainerHostByClientID(client_uuid); |
| if (!container_host) { |
| // The client may already have been closed, just ignore. |
| return; |
| } |
| |
| if (IsBackForwardCacheEnabled()) { |
| // When |PostMessageToClient| is called on a client that is in bfcache, |
| // evict the bfcache entry. |
| if (container_host->IsInBackForwardCache()) { |
| EvictBackForwardCachedControllee( |
| container_host, BackForwardCacheMetrics::NotRestoredReason:: |
| kServiceWorkerPostMessage); |
| return; |
| } |
| } |
| |
| if (container_host->url().DeprecatedGetOriginAsURL() != |
| script_url_.DeprecatedGetOriginAsURL()) { |
| mojo::ReportBadMessage( |
| "Received Client#postMessage() request for a cross-origin client."); |
| receiver_.reset(); |
| return; |
| } |
| if (!container_host->is_execution_ready()) { |
| // It's subtle why this ReportBadMessage is correct. Consider the |
| // sequence: |
| // 1. Page does ServiceWorker.postMessage(). |
| // 2. Service worker does onmessage = (evt) => {evt.source.postMessage()};. |
| // |
| // The IPC sequence is: |
| // 1. Page sends NotifyExecutionReady() to its ServiceWorkerContainerHost |
| // once created. |
| // 2. Page sends PostMessageToServiceWorker() to the object's |
| // ServiceWorkerObjectHost. |
| // 3. Service worker sends PostMessageToClient() to its ServiceWorkerHost. |
| // |
| // It's guaranteed that 1. arrives before 2., since the |
| // ServiceWorkerObjectHost must have been sent over |
| // ServiceWorkerContainerHost (using Register, GetRegistrationForReady), so |
| // they are associated. After that 3. occurs and we get here and are |
| // guaranteed execution ready the above ordering. |
| // |
| // The above reasoning would break if there is a way for a page to get a |
| // ServiceWorkerObjectHost not associated with its |
| // ServiceWorkerContainerHost. If that world should occur, we should queue |
| // the message instead of crashing. |
| mojo::ReportBadMessage( |
| "Received Client#postMessage() request for a reserved client."); |
| receiver_.reset(); |
| return; |
| } |
| container_host->PostMessageToClient(this, std::move(message)); |
| } |
| |
| void ServiceWorkerVersion::FocusClient(const std::string& client_uuid, |
| FocusClientCallback callback) { |
| if (!context_) { |
| std::move(callback).Run(nullptr /* client */); |
| return; |
| } |
| ServiceWorkerContainerHost* container_host = |
| context_->GetContainerHostByClientID(client_uuid); |
| if (!container_host) { |
| // The client may already have been closed, just fail. |
| std::move(callback).Run(nullptr /* client */); |
| return; |
| } |
| if (container_host->url().DeprecatedGetOriginAsURL() != |
| script_url_.DeprecatedGetOriginAsURL()) { |
| mojo::ReportBadMessage( |
| "Received WindowClient#focus() request for a cross-origin client."); |
| receiver_.reset(); |
| return; |
| } |
| if (!container_host->IsContainerForWindowClient()) { |
| // focus() should be called only for WindowClient. |
| mojo::ReportBadMessage( |
| "Received WindowClient#focus() request for a non-window client."); |
| receiver_.reset(); |
| return; |
| } |
| |
| service_worker_client_utils::FocusWindowClient(container_host, |
| std::move(callback)); |
| } |
| |
| void ServiceWorkerVersion::NavigateClient(const std::string& client_uuid, |
| const GURL& url, |
| NavigateClientCallback callback) { |
| if (!context_) { |
| std::move(callback).Run( |
| false /* success */, nullptr /* client */, |
| std::string("The service worker system is shutting down.")); |
| return; |
| } |
| |
| if (!url.is_valid() || !base::IsValidGUID(client_uuid)) { |
| mojo::ReportBadMessage( |
| "Received unexpected invalid URL/UUID from renderer process."); |
| receiver_.reset(); |
| return; |
| } |
| |
| // Reject requests for URLs that the process is not allowed to access. It's |
| // possible to receive such requests since the renderer-side checks are |
| // slightly different. For example, the view-source scheme will not be |
| // filtered out by Blink. |
| if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanRequestURL( |
| embedded_worker_->process_id(), url)) { |
| std::move(callback).Run( |
| false /* success */, nullptr /* client */, |
| "The service worker is not allowed to access URL: " + url.spec()); |
| return; |
| } |
| |
| ServiceWorkerContainerHost* container_host = |
| context_->GetContainerHostByClientID(client_uuid); |
| if (!container_host) { |
| std::move(callback).Run(false /* success */, nullptr /* client */, |
| std::string("The client was not found.")); |
| return; |
| } |
| if (container_host->url().DeprecatedGetOriginAsURL() != |
| script_url_.DeprecatedGetOriginAsURL()) { |
| mojo::ReportBadMessage( |
| "Received WindowClient#navigate() request for a cross-origin client."); |
| receiver_.reset(); |
| return; |
| } |
| if (!container_host->IsContainerForWindowClient()) { |
| // navigate() should be called only for WindowClient. |
| mojo::ReportBadMessage( |
| "Received WindowClient#navigate() request for a non-window client."); |
| receiver_.reset(); |
| return; |
| } |
| if (container_host->controller() != this) { |
| std::move(callback).Run( |
| false /* success */, nullptr /* client */, |
| std::string( |
| "This service worker is not the client's active service worker.")); |
| return; |
| } |
| |
| service_worker_client_utils::NavigateClient( |
| url, script_url_, key_, container_host->GetRenderFrameHostId(), context_, |
| base::BindOnce(&DidNavigateClient, std::move(callback), url)); |
| } |
| |
| void ServiceWorkerVersion::SkipWaiting(SkipWaitingCallback callback) { |
| skip_waiting_ = true; |
| |
| // Per spec, resolve the skip waiting promise now if activation won't be |
| // triggered here. The ActivateWaitingVersionWhenReady() call below only |
| // triggers it if we're in INSTALLED state. So if we're not in INSTALLED |
| // state, resolve the promise now. Even if we're in INSTALLED state, there are |
| // still cases where ActivateWaitingVersionWhenReady() won't trigger the |
| // activation. In that case, it's a slight spec violation to not resolve now, |
| // but we'll eventually resolve the promise in SetStatus(). |
| if (status_ != INSTALLED) { |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| if (!context_) { |
| std::move(callback).Run(false); |
| return; |
| } |
| scoped_refptr<ServiceWorkerRegistration> registration = |
| context_->GetLiveRegistration(registration_id_); |
| // TODO(leonhsl): Here we should be guaranteed a registration since |
| // ServiceWorkerGlobalScope#registration should be keeping the registration |
| // alive currently. So we need to confirm and remove this nullable check |
| // later. |
| if (!registration) { |
| std::move(callback).Run(false); |
| return; |
| } |
| if (skip_waiting_time_.is_null()) |
| RestartTick(&skip_waiting_time_); |
| pending_skip_waiting_requests_.push_back(std::move(callback)); |
| if (pending_skip_waiting_requests_.size() == 1) |
| registration->ActivateWaitingVersionWhenReady(); |
| } |
| |
| void ServiceWorkerVersion::OnSetCachedMetadataFinished(int64_t callback_id, |
| size_t size, |
| int result) { |
| TRACE_EVENT_NESTABLE_ASYNC_END1( |
| "ServiceWorker", "ServiceWorkerVersion::SetCachedMetadata", |
| TRACE_ID_WITH_SCOPE("ServiceWorkerVersion::SetCachedMetadata", |
| callback_id), |
| "result", result); |
| for (auto& observer : observers_) |
| observer.OnCachedMetadataUpdated(this, size); |
| } |
| |
| void ServiceWorkerVersion::OnClearCachedMetadataFinished(int64_t callback_id, |
| int result) { |
| TRACE_EVENT_NESTABLE_ASYNC_END1( |
| "ServiceWorker", "ServiceWorkerVersion::ClearCachedMetadata", |
| TRACE_ID_WITH_SCOPE("ServiceWorkerVersion::ClearCachedMetadata", |
| callback_id), |
| "result", result); |
| for (auto& observer : observers_) |
| observer.OnCachedMetadataUpdated(this, 0); |
| } |
| |
| void ServiceWorkerVersion::OpenWindow( |
| GURL url, |
| service_worker_client_utils::WindowType type, |
| OpenNewTabCallback callback) { |
| // Just respond failure if we are shutting down. |
| if (!context_) { |
| std::move(callback).Run( |
| false /* success */, nullptr /* client */, |
| std::string("The service worker system is shutting down.")); |
| return; |
| } |
| |
| if (!url.is_valid()) { |
| mojo::ReportBadMessage( |
| "Received unexpected invalid URL from renderer process."); |
| receiver_.reset(); |
| return; |
| } |
| |
| // The renderer treats all URLs in the about: scheme as being about:blank. |
| // Canonicalize about: URLs to about:blank. |
| if (url.SchemeIs(url::kAboutScheme)) |
| url = GURL(url::kAboutBlankURL); |
| |
| // Reject requests for URLs that the process is not allowed to access. It's |
| // possible to receive such requests since the renderer-side checks are |
| // slightly different. For example, the view-source scheme will not be |
| // filtered out by Blink. |
| if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanRequestURL( |
| embedded_worker_->process_id(), url)) { |
| std::move(callback).Run(false /* success */, nullptr /* client */, |
| url.spec() + " cannot be opened."); |
| return; |
| } |
| |
| service_worker_client_utils::OpenWindow( |
| url, script_url_, key_, embedded_worker_->embedded_worker_id(), |
| embedded_worker_->process_id(), context_, type, |
| base::BindOnce(&OnOpenWindowFinished, std::move(callback))); |
| } |
| |
| bool ServiceWorkerVersion::HasWorkInBrowser() const { |
| return !inflight_requests_.IsEmpty() || !start_callbacks_.empty(); |
| } |
| |
| void ServiceWorkerVersion::OnSimpleEventFinished( |
| int request_id, |
| blink::mojom::ServiceWorkerEventStatus status) { |
| InflightRequest* request = inflight_requests_.Lookup(request_id); |
| // |request| will be null when the request has been timed out. |
| if (!request) |
| return; |
| // Copy error callback before calling FinishRequest. |
| StatusCallback error_callback = std::move(request->error_callback); |
| |
| FinishRequest(request_id, |
| status == blink::mojom::ServiceWorkerEventStatus::COMPLETED); |
| // TODO(http://crbug.com/1251834): Why are we running the "error callback" |
| // even when there is no error? Clean this up. |
| std::move(error_callback) |
| .Run(mojo::ConvertTo<blink::ServiceWorkerStatusCode>(status)); |
| } |
| |
| void ServiceWorkerVersion::CountFeature(blink::mojom::WebFeature feature) { |
| if (!used_features_.insert(feature).second) |
| return; |
| for (auto container_host_by_uuid : controllee_map_) { |
| const base::WeakPtr<ServiceWorkerContainerHost>& container_host = |
| container_host_by_uuid.second; |
| // TODO(crbug.com/1253581 crbug.com/1021718): controllee_map_ should be only |
| // containing live container hosts. The below "if" check is a workaround for |
| // unmatched AddControllee / RemoveControllee calls. |
| if (container_host) |
| container_host->CountFeature(feature); |
| } |
| } |
| |
| void ServiceWorkerVersion::set_cross_origin_embedder_policy( |
| network::CrossOriginEmbedderPolicy cross_origin_embedder_policy) { |
| // Once it is set, the CrossOriginEmbedderPolicy is immutable. |
| DCHECK(!cross_origin_embedder_policy_ || |
| cross_origin_embedder_policy_ == cross_origin_embedder_policy); |
| cross_origin_embedder_policy_ = std::move(cross_origin_embedder_policy); |
| } |
| |
| // static |
| bool ServiceWorkerVersion::IsInstalled(ServiceWorkerVersion::Status status) { |
| switch (status) { |
| case ServiceWorkerVersion::NEW: |
| case ServiceWorkerVersion::INSTALLING: |
| case ServiceWorkerVersion::REDUNDANT: |
| return false; |
| case ServiceWorkerVersion::INSTALLED: |
| case ServiceWorkerVersion::ACTIVATING: |
| case ServiceWorkerVersion::ACTIVATED: |
| return true; |
| } |
| NOTREACHED() << "Unexpected status: " << status; |
| return false; |
| } |
| |
| // static |
| std::string ServiceWorkerVersion::VersionStatusToString( |
| ServiceWorkerVersion::Status status) { |
| switch (status) { |
| case ServiceWorkerVersion::NEW: |
| return "new"; |
| case ServiceWorkerVersion::INSTALLING: |
| return "installing"; |
| case ServiceWorkerVersion::INSTALLED: |
| return "installed"; |
| case ServiceWorkerVersion::ACTIVATING: |
| return "activating"; |
| case ServiceWorkerVersion::ACTIVATED: |
| return "activated"; |
| case ServiceWorkerVersion::REDUNDANT: |
| return "redundant"; |
| } |
| NOTREACHED() << status; |
| return std::string(); |
| } |
| |
| void ServiceWorkerVersion::IncrementPendingUpdateHintCount() { |
| pending_update_hint_count_++; |
| } |
| |
| void ServiceWorkerVersion::DecrementPendingUpdateHintCount() { |
| DCHECK_GT(pending_update_hint_count_, 0); |
| pending_update_hint_count_--; |
| if (pending_update_hint_count_ == 0) |
| ScheduleUpdate(); |
| } |
| |
| void ServiceWorkerVersion::OnPongFromWorker() { |
| ping_controller_.OnPongReceived(); |
| } |
| |
| void ServiceWorkerVersion::DidEnsureLiveRegistrationForStartWorker( |
| ServiceWorkerMetrics::EventType purpose, |
| Status prestart_status, |
| bool is_browser_startup_complete, |
| StatusCallback callback, |
| blink::ServiceWorkerStatusCode status, |
| scoped_refptr<ServiceWorkerRegistration> registration) { |
| scoped_refptr<ServiceWorkerRegistration> protect = registration; |
| if (status == blink::ServiceWorkerStatusCode::kErrorNotFound) { |
| // When the registration has already been deleted from the storage but its |
| // active worker is still controlling clients, the event should be |
| // dispatched on the worker. However, the storage cannot find the |
| // registration. To handle the case, check the live registrations here. |
| protect = context_->GetLiveRegistration(registration_id_); |
| if (protect) { |
| DCHECK(protect->is_uninstalling()); |
| status = blink::ServiceWorkerStatusCode::kOk; |
| } |
| } |
| if (status != blink::ServiceWorkerStatusCode::kOk) { |
| RecordStartWorkerResult(purpose, prestart_status, kInvalidTraceId, |
| is_browser_startup_complete, status); |
| RunSoon(base::BindOnce( |
| std::move(callback), |
| blink::ServiceWorkerStatusCode::kErrorStartWorkerFailed)); |
| return; |
| } |
| if (is_redundant()) { |
| RecordStartWorkerResult(purpose, prestart_status, kInvalidTraceId, |
| is_browser_startup_complete, |
| blink::ServiceWorkerStatusCode::kErrorRedundant); |
| RunSoon(base::BindOnce(std::move(callback), |
| blink::ServiceWorkerStatusCode::kErrorRedundant)); |
| return; |
| } |
| |
| MarkIfStale(); |
| |
| switch (running_status()) { |
| case EmbeddedWorkerStatus::RUNNING: |
| RunSoon(base::BindOnce(std::move(callback), |
| blink::ServiceWorkerStatusCode::kOk)); |
| return; |
| case EmbeddedWorkerStatus::STARTING: |
| DCHECK(!start_callbacks_.empty()); |
| break; |
| case EmbeddedWorkerStatus::STOPPING: |
| case EmbeddedWorkerStatus::STOPPED: |
| if (start_callbacks_.empty()) { |
| int trace_id = NextTraceId(); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN2( |
| "ServiceWorker", "ServiceWorkerVersion::StartWorker", |
| TRACE_ID_WITH_SCOPE("ServiceWorkerVersion::StartWorker", trace_id), |
| "Script", script_url_.spec(), "Purpose", |
| ServiceWorkerMetrics::EventTypeToString(purpose)); |
| start_callbacks_.push_back( |
| base::BindOnce(&ServiceWorkerVersion::RecordStartWorkerResult, |
| weak_factory_.GetWeakPtr(), purpose, prestart_status, |
| trace_id, is_browser_startup_complete)); |
| } |
| break; |
| } |
| |
| // Keep the live registration while starting the worker. |
| start_callbacks_.push_back(base::BindOnce( |
| [](StatusCallback callback, |
| scoped_refptr<ServiceWorkerRegistration> protect, |
| blink::ServiceWorkerStatusCode status) { |
| std::move(callback).Run(status); |
| }, |
| std::move(callback), protect)); |
| |
| if (running_status() == EmbeddedWorkerStatus::STOPPED) |
| StartWorkerInternal(); |
| // Warning: StartWorkerInternal() might have deleted `this` on failure. |
| } |
| |
| void ServiceWorkerVersion::StartWorkerInternal() { |
| DCHECK_EQ(EmbeddedWorkerStatus::STOPPED, running_status()); |
| DCHECK(inflight_requests_.IsEmpty()); |
| DCHECK(request_timeouts_.empty()); |
| |
| // Don't try to start a new worker thread if the `context_` has been |
| // destroyed. This can happen during browser shutdown or if corruption |
| // forced a storage reset. |
| if (!context_) { |
| FinishStartWorker(blink::ServiceWorkerStatusCode::kErrorAbort); |
| return; |
| } |
| |
| StartTimeoutTimer(); |
| |
| // Set expiration time in advance so that the service worker can |
| // call postMessage() to itself immediately after it starts. |
| max_request_expiration_time_ = tick_clock_->NowTicks() + kRequestTimeout; |
| |
| worker_is_idle_on_renderer_ = false; |
| needs_to_be_terminated_asap_ = false; |
| |
| auto provider_info = |
| blink::mojom::ServiceWorkerProviderInfoForStartWorker::New(); |
| DCHECK(!worker_host_); |
| worker_host_ = std::make_unique<content::ServiceWorkerHost>( |
| provider_info->host_remote.InitWithNewEndpointAndPassReceiver(), this, |
| context()); |
| |
| auto params = blink::mojom::EmbeddedWorkerStartParams::New(); |
| params->service_worker_version_id = version_id_; |
| params->scope = scope_; |
| params->script_url = script_url_; |
| params->script_type = script_type_; |
| // Need to clone this object because StartWorkerInternal() can/ be called |
| // more than once. |
| params->outside_fetch_client_settings_object = |
| outside_fetch_client_settings_object_.Clone(); |
| |
| ContentBrowserClient* browser_client = GetContentClient()->browser(); |
| params->user_agent = (origin_trial_tokens_ && |
| origin_trial_tokens_->contains("UserAgentReduction")) |
| ? browser_client->GetReducedUserAgent() |
| : browser_client->GetUserAgentBasedOnPolicy( |
| context_->wrapper()->browser_context()); |
| params->ua_metadata = browser_client->GetUserAgentMetadata(); |
| params->is_installed = IsInstalled(status_); |
| params->script_url_to_skip_throttling = updated_script_url_; |
| params->main_script_load_params = std::move(main_script_load_params_); |
| |
| if (IsInstalled(status())) { |
| DCHECK(!installed_scripts_sender_); |
| installed_scripts_sender_ = |
| std::make_unique<ServiceWorkerInstalledScriptsSender>(this); |
| params->installed_scripts_info = |
| installed_scripts_sender_->CreateInfoAndBind(); |
| installed_scripts_sender_->Start(); |
| } |
| |
| params->service_worker_receiver = |
| service_worker_remote_.BindNewPipeAndPassReceiver(); |
| // TODO(horo): These CHECKs are for debugging crbug.com/759938. |
| CHECK(service_worker_remote_.is_bound()); |
| CHECK(params->service_worker_receiver.is_valid()); |
| service_worker_remote_.set_disconnect_handler( |
| base::BindOnce(&OnConnectionError, embedded_worker_->AsWeakPtr())); |
| |
| if (!controller_receiver_.is_valid()) { |
| controller_receiver_ = remote_controller_.BindNewPipeAndPassReceiver(); |
| } |
| params->controller_receiver = std::move(controller_receiver_); |
| |
| params->provider_info = std::move(provider_info); |
| |
| params->ukm_source_id = ukm_source_id_; |
| |
| embedded_worker_->Start(std::move(params), |
| base::BindOnce(&ServiceWorkerVersion::OnStartSent, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ServiceWorkerVersion::StartTimeoutTimer() { |
| DCHECK(!timeout_timer_.IsRunning()); |
| |
| if (embedded_worker_->devtools_attached()) { |
| // Don't record the startup time metric once DevTools is attached. |
| ClearTick(&start_time_); |
| skip_recording_startup_time_ = true; |
| } else { |
| RestartTick(&start_time_); |
| skip_recording_startup_time_ = false; |
| } |
| |
| // Ping will be activated in OnScriptEvaluationStart. |
| ping_controller_.Deactivate(); |
| |
| timeout_timer_.Start(FROM_HERE, kTimeoutTimerDelay, this, |
| &ServiceWorkerVersion::OnTimeoutTimer); |
| } |
| |
| void ServiceWorkerVersion::StopTimeoutTimer() { |
| timeout_timer_.Stop(); |
| |
| // Trigger update if worker is stale. |
| if (!in_dtor_ && !stale_time_.is_null()) { |
| ClearTick(&stale_time_); |
| if (!update_timer_.IsRunning()) |
| ScheduleUpdate(); |
| } |
| } |
| |
| void ServiceWorkerVersion::SetTimeoutTimerInterval(base::TimeDelta interval) { |
| DCHECK(timeout_timer_.IsRunning()); |
| if (timeout_timer_.GetCurrentDelay() != interval) { |
| timeout_timer_.Stop(); |
| timeout_timer_.Start(FROM_HERE, interval, this, |
| &ServiceWorkerVersion::OnTimeoutTimer); |
| } |
| } |
| |
| void ServiceWorkerVersion::OnTimeoutTimer() { |
| // TODO(horo): This CHECK is for debugging crbug.com/759938. |
| CHECK(running_status() == EmbeddedWorkerStatus::STARTING || |
| running_status() == EmbeddedWorkerStatus::RUNNING || |
| running_status() == EmbeddedWorkerStatus::STOPPING) |
| << static_cast<int>(running_status()); |
| |
| if (!context_) |
| return; |
| |
| MarkIfStale(); |
| |
| // Stopping the worker hasn't finished within a certain period. |
| if (GetTickDuration(stop_time_) > kStopWorkerTimeout) { |
| DCHECK_EQ(EmbeddedWorkerStatus::STOPPING, running_status()); |
| ReportError(blink::ServiceWorkerStatusCode::kErrorTimeout, |
| "DETACH_STALLED_IN_STOPPING"); |
| |
| // Detach the worker. Remove |this| as a listener first; otherwise |
| // OnStoppedInternal might try to restart before the new worker |
| // is created. Also, protect |this|, since swapping out the |
| // EmbeddedWorkerInstance could destroy our ServiceWorkerHost which could in |
| // turn destroy |this|. |
| scoped_refptr<ServiceWorkerVersion> protect_this(this); |
| embedded_worker_->RemoveObserver(this); |
| embedded_worker_->Detach(); |
| embedded_worker_ = std::make_unique<EmbeddedWorkerInstance>(this); |
| embedded_worker_->AddObserver(this); |
| |
| // Call OnStoppedInternal to fail callbacks and possibly restart. |
| OnStoppedInternal(EmbeddedWorkerStatus::STOPPING); |
| return; |
| } |
| |
| // Trigger update if worker is stale and we waited long enough for it to go |
| // idle. |
| if (GetTickDuration(stale_time_) > kRequestTimeout) { |
| ClearTick(&stale_time_); |
| if (!update_timer_.IsRunning()) |
| ScheduleUpdate(); |
| } |
| |
| // Starting a worker hasn't finished within a certain period. |
| const base::TimeDelta start_limit = IsInstalled(status()) |
| ? kStartInstalledWorkerTimeout |
| : kStartNewWorkerTimeout; |
| if (GetTickDuration(start_time_) > start_limit) { |
| DCHECK(running_status() == EmbeddedWorkerStatus::STARTING || |
| running_status() == EmbeddedWorkerStatus::STOPPING) |
| << static_cast<int>(running_status()); |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| FinishStartWorker(blink::ServiceWorkerStatusCode::kErrorTimeout); |
| if (running_status() == EmbeddedWorkerStatus::STARTING) |
| embedded_worker_->Stop(); |
| return; |
| } |
| |
| // Requests have not finished before their expiration. |
| bool stop_for_timeout = false; |
| auto timeout_iter = request_timeouts_.begin(); |
| while (timeout_iter != request_timeouts_.end()) { |
| const InflightRequestTimeoutInfo& info = *timeout_iter; |
| if (!RequestExpired(info.expiration)) |
| break; |
| if (MaybeTimeoutRequest(info)) { |
| stop_for_timeout = |
| stop_for_timeout || info.timeout_behavior == KILL_ON_TIMEOUT; |
| } |
| timeout_iter = request_timeouts_.erase(timeout_iter); |
| } |
| if (stop_for_timeout && running_status() != EmbeddedWorkerStatus::STOPPING) |
| embedded_worker_->Stop(); |
| |
| // For the timeouts below, there are no callbacks to timeout so there is |
| // nothing more to do if the worker is already stopping. |
| if (running_status() == EmbeddedWorkerStatus::STOPPING) |
| return; |
| |
| // Check ping status. |
| ping_controller_.CheckPingStatus(); |
| } |
| |
| void ServiceWorkerVersion::PingWorker() { |
| // TODO(horo): This CHECK is for debugging crbug.com/759938. |
| CHECK(running_status() == EmbeddedWorkerStatus::STARTING || |
| running_status() == EmbeddedWorkerStatus::RUNNING); |
| // base::Unretained here is safe because endpoint() is owned by |
| // |this|. |
| endpoint()->Ping(base::BindOnce(&ServiceWorkerVersion::OnPongFromWorker, |
| base::Unretained(this))); |
| } |
| |
| void ServiceWorkerVersion::OnPingTimeout() { |
| DCHECK(running_status() == EmbeddedWorkerStatus::STARTING || |
| running_status() == EmbeddedWorkerStatus::RUNNING); |
| MaybeReportConsoleMessageToInternals( |
| blink::mojom::ConsoleMessageLevel::kVerbose, kNotRespondingErrorMesage); |
| embedded_worker_->StopIfNotAttachedToDevTools(); |
| } |
| |
| void ServiceWorkerVersion::RecordStartWorkerResult( |
| ServiceWorkerMetrics::EventType purpose, |
| Status prestart_status, |
| int trace_id, |
| bool is_browser_startup_complete, |
| blink::ServiceWorkerStatusCode status) { |
| if (trace_id != kInvalidTraceId) { |
| TRACE_EVENT_NESTABLE_ASYNC_END1( |
| "ServiceWorker", "ServiceWorkerVersion::StartWorker", |
| TRACE_ID_WITH_SCOPE("ServiceWorkerVersion::StartWorker", trace_id), |
| "Status", blink::ServiceWorkerStatusToString(status)); |
| } |
| base::TimeTicks start_time = start_time_; |
| ClearTick(&start_time_); |
| |
| if (context_ && IsInstalled(prestart_status)) |
| context_->UpdateVersionFailureCount(version_id_, status); |
| |
| if (IsInstalled(prestart_status)) |
| ServiceWorkerMetrics::RecordStartInstalledWorkerStatus(status, purpose); |
| |
| if (status == blink::ServiceWorkerStatusCode::kOk && !start_time.is_null() && |
| !skip_recording_startup_time_) { |
| ServiceWorkerMetrics::RecordStartWorkerTime( |
| GetTickDuration(start_time), IsInstalled(prestart_status), |
| embedded_worker_->start_situation(), purpose); |
| } |
| |
| if (status != blink::ServiceWorkerStatusCode::kErrorTimeout) |
| return; |
| EmbeddedWorkerInstance::StartingPhase phase = |
| EmbeddedWorkerInstance::NOT_STARTING; |
| EmbeddedWorkerStatus running_status = embedded_worker_->status(); |
| // Build an artifical JavaScript exception to show in the ServiceWorker |
| // log for developers; it's not user-facing so it's not a localized resource. |
| std::string message = "ServiceWorker startup timed out. "; |
| if (running_status != EmbeddedWorkerStatus::STARTING) { |
| message.append("The worker had unexpected status: "); |
| message.append(EmbeddedWorkerInstance::StatusToString(running_status)); |
| } else { |
| phase = embedded_worker_->starting_phase(); |
| message.append("The worker was in startup phase: "); |
| message.append(EmbeddedWorkerInstance::StartingPhaseToString(phase)); |
| } |
| message.append("."); |
| OnReportException(base::UTF8ToUTF16(message), -1, -1, GURL()); |
| DVLOG(1) << message; |
| UMA_HISTOGRAM_ENUMERATION("ServiceWorker.StartWorker.TimeoutPhase", phase, |
| EmbeddedWorkerInstance::STARTING_PHASE_MAX_VALUE); |
| } |
| |
| bool ServiceWorkerVersion::MaybeTimeoutRequest( |
| const InflightRequestTimeoutInfo& info) { |
| InflightRequest* request = inflight_requests_.Lookup(info.id); |
| if (!request) |
| return false; |
| |
| TRACE_EVENT_NESTABLE_ASYNC_END1("ServiceWorker", |
| "ServiceWorkerVersion::Request", |
| TRACE_ID_LOCAL(request), "Error", "Timeout"); |
| std::move(request->error_callback) |
| .Run(blink::ServiceWorkerStatusCode::kErrorTimeout); |
| inflight_requests_.Remove(info.id); |
| return true; |
| } |
| |
| void ServiceWorkerVersion::SetAllRequestExpirations( |
| const base::TimeTicks& expiration) { |
| std::set<InflightRequestTimeoutInfo> new_timeouts; |
| for (const auto& info : request_timeouts_) { |
| bool is_inserted = false; |
| std::set<InflightRequestTimeoutInfo>::iterator iter; |
| std::tie(iter, is_inserted) = new_timeouts.emplace( |
| info.id, info.event_type, expiration, info.timeout_behavior); |
| DCHECK(is_inserted); |
| InflightRequest* request = inflight_requests_.Lookup(info.id); |
| DCHECK(request); |
| request->timeout_iter = iter; |
| } |
| request_timeouts_.swap(new_timeouts); |
| } |
| |
| blink::ServiceWorkerStatusCode |
| ServiceWorkerVersion::DeduceStartWorkerFailureReason( |
| blink::ServiceWorkerStatusCode default_code) { |
| if (ping_controller_.IsTimedOut()) |
| return blink::ServiceWorkerStatusCode::kErrorTimeout; |
| |
| if (start_worker_status_ != blink::ServiceWorkerStatusCode::kOk) |
| return start_worker_status_; |
| |
| int main_script_net_error = script_cache_map()->main_script_net_error(); |
| if (main_script_net_error != net::OK) { |
| if (net::IsCertificateError(main_script_net_error)) |
| return blink::ServiceWorkerStatusCode::kErrorSecurity; |
| switch (main_script_net_error) { |
| case net::ERR_INSECURE_RESPONSE: |
| case net::ERR_UNSAFE_REDIRECT: |
| return blink::ServiceWorkerStatusCode::kErrorSecurity; |
| case net::ERR_ABORTED: |
| return blink::ServiceWorkerStatusCode::kErrorAbort; |
| default: |
| return blink::ServiceWorkerStatusCode::kErrorNetwork; |
| } |
| } |
| |
| return default_code; |
| } |
| |
| void ServiceWorkerVersion::MarkIfStale() { |
| if (!context_) |
| return; |
| if (update_timer_.IsRunning() || !stale_time_.is_null()) |
| return; |
| scoped_refptr<ServiceWorkerRegistration> registration = |
| context_->GetLiveRegistration(registration_id_); |
| if (!registration || registration->active_version() != this) |
| return; |
| base::TimeDelta time_since_last_check = |
| clock_->Now() - registration->last_update_check(); |
| if (time_since_last_check > |
| ServiceWorkerConsts::kServiceWorkerScriptMaxCacheAge) |
| RestartTick(&stale_time_); |
| } |
| |
| void ServiceWorkerVersion::FoundRegistrationForUpdate( |
| blink::ServiceWorkerStatusCode status, |
| scoped_refptr<ServiceWorkerRegistration> registration) { |
| if (!context_) |
| return; |
| |
| const scoped_refptr<ServiceWorkerVersion> protect = this; |
| if (is_update_scheduled_) { |
| context_->UnprotectVersion(version_id_); |
| is_update_scheduled_ = false; |
| } |
| |
| if (status != blink::ServiceWorkerStatusCode::kOk || |
| registration->active_version() != this) |
| return; |
| context_->UpdateServiceWorker(registration.get(), |
| false /* force_bypass_cache */); |
| } |
| |
| void ServiceWorkerVersion::OnStoppedInternal(EmbeddedWorkerStatus old_status) { |
| DCHECK_EQ(EmbeddedWorkerStatus::STOPPED, running_status()); |
| scoped_refptr<ServiceWorkerVersion> protect; |
| if (!in_dtor_) |
| protect = this; |
| |
| // |start_callbacks_| can be non-empty if a start worker request arrived while |
| // the worker was stopping. The worker must be restarted to fulfill the |
| // request. |
| bool should_restart = !start_callbacks_.empty(); |
| if (is_redundant() || in_dtor_) { |
| // This worker will be destroyed soon. |
| should_restart = false; |
| } else if (ping_controller_.IsTimedOut()) { |
| // This worker exhausted its time to run, don't let it restart. |
| should_restart = false; |
| } else if (old_status == EmbeddedWorkerStatus::STARTING) { |
| // This worker unexpectedly stopped because start failed. Attempting to |
| // restart on start failure could cause an endless loop of start attempts, |
| // so don't try to restart now. |
| should_restart = false; |
| } |
| |
| if (!stop_time_.is_null()) { |
| TRACE_EVENT_NESTABLE_ASYNC_END1( |
| "ServiceWorker", "ServiceWorkerVersion::StopWorker", |
| TRACE_ID_WITH_SCOPE("ServiceWorkerVersion::StopWorker", |
| stop_time_.since_origin().InMicroseconds()), |
| "Restart", should_restart); |
| ClearTick(&stop_time_); |
| } |
| StopTimeoutTimer(); |
| |
| // Fire all stop callbacks. |
| std::vector<base::OnceClosure> callbacks; |
| callbacks.swap(stop_callbacks_); |
| for (auto& callback : callbacks) |
| std::move(callback).Run(); |
| |
| if (!should_restart) { |
| // Let all start callbacks fail. |
| FinishStartWorker(DeduceStartWorkerFailureReason( |
| blink::ServiceWorkerStatusCode::kErrorStartWorkerFailed)); |
| } |
| |
| // Let all message callbacks fail (this will also fire and clear all |
| // callbacks for events). |
| // TODO(kinuko): Consider if we want to add queue+resend mechanism here. |
| base::IDMap<std::unique_ptr<InflightRequest>>::iterator iter( |
| &inflight_requests_); |
| while (!iter.IsAtEnd()) { |
| TRACE_EVENT_NESTABLE_ASYNC_END1( |
| "ServiceWorker", "ServiceWorkerVersion::Request", |
| TRACE_ID_LOCAL(iter.GetCurrentValue()), "Error", "Worker Stopped"); |
| std::move(iter.GetCurrentValue()->error_callback) |
| .Run(blink::ServiceWorkerStatusCode::kErrorFailed); |
| iter.Advance(); |
| } |
| inflight_requests_.Clear(); |
| request_timeouts_.clear(); |
| external_request_uuid_to_request_id_.clear(); |
| service_worker_remote_.reset(); |
| is_endpoint_ready_ = false; |
| remote_controller_.reset(); |
| DCHECK(!controller_receiver_.is_valid()); |
| installed_scripts_sender_.reset(); |
| receiver_.reset(); |
| pending_external_requests_.clear(); |
| worker_is_idle_on_renderer_ = true; |
| worker_host_.reset(); |
| |
| for (auto& observer : observers_) |
| observer.OnRunningStateChanged(this); |
| if (should_restart) { |
| StartWorkerInternal(); |
| } else if (!HasWorkInBrowser()) { |
| OnNoWorkInBrowser(); |
| } |
| } |
| |
| void ServiceWorkerVersion::FinishStartWorker( |
| blink::ServiceWorkerStatusCode status) { |
| std::vector<StatusCallback> callbacks; |
| callbacks.swap(start_callbacks_); |
| is_running_start_callbacks_ = true; |
| for (auto& callback : callbacks) |
| std::move(callback).Run(status); |
| is_running_start_callbacks_ = false; |
| } |
| |
| void ServiceWorkerVersion::CleanUpExternalRequest( |
| const std::string& request_uuid, |
| blink::ServiceWorkerStatusCode status) { |
| if (status == blink::ServiceWorkerStatusCode::kOk) |
| return; |
| external_request_uuid_to_request_id_.erase(request_uuid); |
| } |
| |
| void ServiceWorkerVersion::OnNoWorkInBrowser() { |
| DCHECK(!HasWorkInBrowser()); |
| if (context_ && worker_is_idle_on_renderer_) { |
| scoped_refptr<ServiceWorkerRegistration> registration = |
| context_->GetLiveRegistration(registration_id()); |
| if (registration) |
| registration->OnNoWork(this); |
| |
| for (auto& observer : observers_) |
| observer.OnNoWork(this); |
| } |
| } |
| |
| bool ServiceWorkerVersion::IsStartWorkerAllowed() const { |
| // Check that the worker is allowed on this origin. It's possible a |
| // worker was previously allowed and installed, but later the embedder's |
| // policy or binary changed to disallow this origin. |
| if (!service_worker_security_utils::AllOriginsMatchAndCanAccessServiceWorkers( |
| {script_url_})) { |
| return false; |
| } |
| |
| // Check that the worker is allowed on the given scope. It's possible a worker |
| // was previously allowed and installed, but later content settings changed to |
| // disallow this scope. Since this worker might not be used for a specific |
| // tab, pass a null callback as WebContents getter. |
| if (!GetContentClient()->browser()->AllowServiceWorker( |
| scope_, net::SiteForCookies::FromUrl(scope_), |
| url::Origin::Create(scope_), script_url_, |
| context_->wrapper()->browser_context())) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void ServiceWorkerVersion::NotifyControlleeAdded( |
| const std::string& uuid, |
| const ServiceWorkerClientInfo& info) { |
| if (context_) |
| context_->OnControlleeAdded(this, uuid, info); |
| } |
| |
| void ServiceWorkerVersion::NotifyControlleeRemoved(const std::string& uuid) { |
| if (!context_) |
| return; |
| |
| // The OnNoControllees() can destroy |this|, so protect it first. |
| auto protect = base::WrapRefCounted(this); |
| context_->OnControlleeRemoved(this, uuid); |
| if (!HasControllee()) { |
| RestartTick(&no_controllees_time_); |
| context_->OnNoControllees(this); |
| } |
| } |
| |
| void ServiceWorkerVersion::NotifyControlleeNavigationCommitted( |
| const std::string& uuid, |
| GlobalRenderFrameHostId render_frame_host_id) { |
| if (context_) |
| context_->OnControlleeNavigationCommitted(this, uuid, render_frame_host_id); |
| } |
| |
| void ServiceWorkerVersion::PrepareForUpdate( |
| std::map<GURL, ServiceWorkerUpdateChecker::ComparedScriptInfo> |
| compared_script_info_map, |
| const GURL& updated_script_url, |
| network::CrossOriginEmbedderPolicy cross_origin_embedder_policy) { |
| compared_script_info_map_ = std::move(compared_script_info_map); |
| updated_script_url_ = updated_script_url; |
| set_cross_origin_embedder_policy(cross_origin_embedder_policy); |
| } |
| |
| const std::map<GURL, ServiceWorkerUpdateChecker::ComparedScriptInfo>& |
| ServiceWorkerVersion::compared_script_info_map() const { |
| return compared_script_info_map_; |
| } |
| |
| ServiceWorkerUpdateChecker::ComparedScriptInfo |
| ServiceWorkerVersion::TakeComparedScriptInfo(const GURL& script_url) { |
| auto it = compared_script_info_map_.find(script_url); |
| DCHECK(it != compared_script_info_map_.end()); |
| ServiceWorkerUpdateChecker::ComparedScriptInfo info = std::move(it->second); |
| compared_script_info_map_.erase(it); |
| return info; |
| } |
| |
| bool ServiceWorkerVersion::ShouldRequireForegroundPriority( |
| int worker_process_id) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // Currently FetchEvents are the only type of event we need to really process |
| // at foreground priority. If the service worker does not have a FetchEvent |
| // handler then we can always allow it to go to the background. |
| if (fetch_handler_existence_ != FetchHandlerExistence::EXISTS) |
| return false; |
| |
| // Keep the service worker at foreground priority if it has clients from |
| // different foreground processes. In this situation we are likely to need to |
| // quickly service FetchEvents when the worker's process does not have any |
| // visible windows and would have otherwise been moved to the background. |
| // |
| // For now the requirement for cross-process clients should filter out most |
| // service workers. The impact of foreground service workers is further |
| // limited by the automatic shutdown mechanism. |
| for (const auto& controllee : controllee_map_) { |
| const int controllee_process_id = controllee.second->GetProcessId(); |
| RenderProcessHost* render_host = |
| RenderProcessHost::FromID(controllee_process_id); |
| |
| // It's possible that |controllee_process_id| and |render_host| won't be |
| // valid until the controllee commits. Require foreground priority in this |
| // case. |
| if (!render_host) |
| return true; |
| |
| // Require foreground if the controllee is in different process and is |
| // foreground. |
| if (controllee_process_id != worker_process_id && |
| (!base::FeatureList::IsEnabled( |
| features:: |
| kChangeServiceWorkerPriorityForClientForegroundStateChange) || |
| !render_host->IsProcessBackgrounded())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void ServiceWorkerVersion::UpdateForegroundPriority() { |
| embedded_worker_->UpdateForegroundPriority(); |
| } |
| |
| void ServiceWorkerVersion::AddMessageToConsole( |
| blink::mojom::ConsoleMessageLevel message_level, |
| const std::string& message) { |
| if (running_status() == EmbeddedWorkerStatus::STARTING || |
| running_status() == EmbeddedWorkerStatus::RUNNING) { |
| endpoint()->AddMessageToConsole(message_level, message); |
| } |
| } |
| |
| void ServiceWorkerVersion::MaybeReportConsoleMessageToInternals( |
| blink::mojom::ConsoleMessageLevel message_level, |
| const std::string& message) { |
| // When the internals UI page is opened, the page listens to |
| // OnReportConsoleMessage(). |
| OnReportConsoleMessage(blink::mojom::ConsoleMessageSource::kOther, |
| message_level, base::UTF8ToUTF16(message), -1, |
| script_url_); |
| } |
| |
| storage::mojom::ServiceWorkerLiveVersionInfoPtr |
| ServiceWorkerVersion::RebindStorageReference() { |
| DCHECK(context_); |
| |
| std::vector<int64_t> purgeable_resources; |
| // Resources associated with this version are purgeable when the corresponding |
| // registration is uninstalling or uninstalled. |
| switch (registration_status_) { |
| case ServiceWorkerRegistration::Status::kIntact: |
| break; |
| case ServiceWorkerRegistration::Status::kUninstalling: |
| case ServiceWorkerRegistration::Status::kUninstalled: { |
| std::vector<storage::mojom::ServiceWorkerResourceRecordPtr> resources; |
| script_cache_map_.GetResources(&resources); |
| for (auto& resource : resources) { |
| purgeable_resources.push_back(resource->resource_id); |
| } |
| break; |
| } |
| } |
| |
| remote_reference_.reset(); |
| return storage::mojom::ServiceWorkerLiveVersionInfo::New( |
| version_id_, std::move(purgeable_resources), |
| remote_reference_.BindNewPipeAndPassReceiver()); |
| } |
| |
| void ServiceWorkerVersion::MaybeUpdateIdleDelayForTerminationOnNoControllee( |
| base::TimeDelta delay) { |
| if (!base::FeatureList::IsEnabled( |
| features::kServiceWorkerTerminationOnNoControllee) || |
| HasControllee() || running_status() != EmbeddedWorkerStatus::RUNNING) { |
| return; |
| } |
| |
| // The idle delay can be updated only when the worker is running. |
| bool update_idle_delay = running_status() == EmbeddedWorkerStatus::RUNNING; |
| |
| // The idle delay should not be updated when the worker needs to be |
| // terminated ASAP so that the new worker can be activated soon. |
| update_idle_delay = update_idle_delay && !needs_to_be_terminated_asap_; |
| |
| if (update_idle_delay) { |
| endpoint()->SetIdleDelay(delay); |
| } |
| } |
| |
| } // namespace content |