| // 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 "base/command_line.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "content/browser/bad_message.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/message_port_message_filter.h" |
| #include "content/browser/message_port_service.h" |
| #include "content/browser/service_worker/embedded_worker_instance.h" |
| #include "content/browser/service_worker/embedded_worker_registry.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_registration.h" |
| #include "content/browser/service_worker/service_worker_utils.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/common/service_worker/service_worker_messages.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/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/child_process_host.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/result_codes.h" |
| #include "net/http/http_response_info.h" |
| |
| namespace content { |
| |
| using StatusCallback = ServiceWorkerVersion::StatusCallback; |
| using GetClientsCallback = |
| base::Callback<void(const std::vector<ServiceWorkerClientInfo>&)>; |
| |
| namespace { |
| |
| // Delay between the timeout timer firing. |
| const int kTimeoutTimerDelaySeconds = 30; |
| |
| // Time to wait until stopping an idle worker. |
| const int kIdleWorkerTimeoutSeconds = 30; |
| |
| // Default delay for scheduled update. |
| const int kUpdateDelaySeconds = 1; |
| |
| // Timeout for waiting for a response to a ping. |
| const int kPingTimeoutSeconds = 30; |
| |
| // If the SW was destructed while starting up, how many seconds it |
| // had to start up for this to be considered a timeout occurrence. |
| const int kDestructedStartingWorkerTimeoutThresholdSeconds = 5; |
| |
| const char kClaimClientsStateErrorMesage[] = |
| "Only the active worker can claim clients."; |
| |
| const char kClaimClientsShutdownErrorMesage[] = |
| "Failed to claim clients due to Service Worker system shutdown."; |
| |
| void RunSoon(const base::Closure& callback) { |
| if (!callback.is_null()) |
| base::MessageLoop::current()->PostTask(FROM_HERE, callback); |
| } |
| |
| template <typename CallbackArray, typename Arg> |
| void RunCallbacks(ServiceWorkerVersion* version, |
| CallbackArray* callbacks_ptr, |
| const Arg& arg) { |
| CallbackArray callbacks; |
| callbacks.swap(*callbacks_ptr); |
| scoped_refptr<ServiceWorkerVersion> protect(version); |
| for (const auto& callback : callbacks) |
| callback.Run(arg); |
| } |
| |
| template <typename IDMAP, typename... Params> |
| void RunIDMapCallbacks(IDMAP* callbacks, const Params&... params) { |
| typename IDMAP::iterator iter(callbacks); |
| while (!iter.IsAtEnd()) { |
| iter.GetCurrentValue()->Run(params...); |
| iter.Advance(); |
| } |
| callbacks->Clear(); |
| } |
| |
| void RunStartWorkerCallback( |
| const StatusCallback& callback, |
| scoped_refptr<ServiceWorkerRegistration> protect, |
| ServiceWorkerStatusCode status) { |
| callback.Run(status); |
| } |
| |
| // A callback adapter to start a |task| after StartWorker. |
| void RunTaskAfterStartWorker( |
| base::WeakPtr<ServiceWorkerVersion> version, |
| const StatusCallback& error_callback, |
| const base::Closure& task, |
| ServiceWorkerStatusCode status) { |
| if (status != SERVICE_WORKER_OK) { |
| if (!error_callback.is_null()) |
| error_callback.Run(status); |
| return; |
| } |
| if (version->running_status() != ServiceWorkerVersion::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"; |
| if (!error_callback.is_null()) |
| error_callback.Run(SERVICE_WORKER_ERROR_START_WORKER_FAILED); |
| return; |
| } |
| task.Run(); |
| } |
| |
| void RunErrorFetchCallback(const ServiceWorkerVersion::FetchCallback& callback, |
| ServiceWorkerStatusCode status) { |
| callback.Run(status, |
| SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, |
| ServiceWorkerResponse()); |
| } |
| |
| void RunErrorMessageCallback( |
| const std::vector<TransferredMessagePort>& sent_message_ports, |
| const ServiceWorkerVersion::StatusCallback& callback, |
| ServiceWorkerStatusCode status) { |
| // Transfering the message ports failed, so destroy the ports. |
| for (const TransferredMessagePort& port : sent_message_ports) { |
| MessagePortService::GetInstance()->ClosePort(port.id); |
| } |
| callback.Run(status); |
| } |
| |
| void RunErrorCrossOriginConnectCallback( |
| const ServiceWorkerVersion::CrossOriginConnectCallback& callback, |
| ServiceWorkerStatusCode status) { |
| callback.Run(status, false); |
| } |
| |
| using WindowOpenedCallback = base::Callback<void(int, int)>; |
| |
| // The WindowOpenedObserver class is a WebContentsObserver that will wait for a |
| // new Window's WebContents to be initialized, run the |callback| passed to its |
| // constructor then self destroy. |
| // The callback will receive the process and frame ids. If something went wrong |
| // those will be (kInvalidUniqueID, MSG_ROUTING_NONE). |
| // The callback will be called in the IO thread. |
| class WindowOpenedObserver : public WebContentsObserver { |
| public: |
| WindowOpenedObserver(WebContents* web_contents, |
| const WindowOpenedCallback& callback) |
| : WebContentsObserver(web_contents), |
| callback_(callback) |
| {} |
| |
| void DidCommitProvisionalLoadForFrame( |
| RenderFrameHost* render_frame_host, |
| const GURL& validated_url, |
| ui::PageTransition transition_type) override { |
| DCHECK(web_contents()); |
| |
| if (render_frame_host != web_contents()->GetMainFrame()) |
| return; |
| |
| RunCallback(render_frame_host->GetProcess()->GetID(), |
| render_frame_host->GetRoutingID()); |
| } |
| |
| void RenderProcessGone(base::TerminationStatus status) override { |
| RunCallback(ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE); |
| } |
| |
| void WebContentsDestroyed() override { |
| RunCallback(ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE); |
| } |
| |
| private: |
| void RunCallback(int render_process_id, int render_frame_id) { |
| // After running the callback, |this| will stop observing, thus |
| // web_contents() should return nullptr and |RunCallback| should no longer |
| // be called. Then, |this| will self destroy. |
| DCHECK(web_contents()); |
| |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| base::Bind(callback_, |
| render_process_id, |
| render_frame_id)); |
| Observe(nullptr); |
| base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); |
| } |
| |
| const WindowOpenedCallback callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(WindowOpenedObserver); |
| }; |
| |
| void DidOpenURL(const WindowOpenedCallback& callback, |
| WebContents* web_contents) { |
| DCHECK(web_contents); |
| |
| new WindowOpenedObserver(web_contents, callback); |
| } |
| |
| void OpenWindowOnUI( |
| const GURL& url, |
| const GURL& script_url, |
| int process_id, |
| const scoped_refptr<ServiceWorkerContextWrapper>& context_wrapper, |
| const WindowOpenedCallback& callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| BrowserContext* browser_context = context_wrapper->storage_partition() |
| ? context_wrapper->storage_partition()->browser_context() |
| : nullptr; |
| // We are shutting down. |
| if (!browser_context) |
| return; |
| |
| RenderProcessHost* render_process_host = |
| RenderProcessHost::FromID(process_id); |
| if (render_process_host->IsIsolatedGuest()) { |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| base::Bind(callback, |
| ChildProcessHost::kInvalidUniqueID, |
| MSG_ROUTING_NONE)); |
| return; |
| } |
| |
| OpenURLParams params( |
| url, Referrer::SanitizeForRequest( |
| url, Referrer(script_url, blink::WebReferrerPolicyDefault)), |
| NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_AUTO_TOPLEVEL, |
| true /* is_renderer_initiated */); |
| |
| GetContentClient()->browser()->OpenURL( |
| browser_context, params, |
| base::Bind(&DidOpenURL, callback)); |
| } |
| |
| void KillEmbeddedWorkerProcess(int process_id, ResultCode code) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| RenderProcessHost* render_process_host = |
| RenderProcessHost::FromID(process_id); |
| if (render_process_host->GetHandle() != base::kNullProcessHandle) { |
| bad_message::ReceivedBadMessage(render_process_host, |
| bad_message::SERVICE_WORKER_BAD_URL); |
| } |
| } |
| |
| void ClearTick(base::TimeTicks* time) { |
| *time = base::TimeTicks(); |
| } |
| |
| void RestartTick(base::TimeTicks* time) { |
| *time = base::TimeTicks().Now(); |
| } |
| |
| base::TimeDelta GetTickDuration(const base::TimeTicks& time) { |
| if (time.is_null()) |
| return base::TimeDelta(); |
| return base::TimeTicks().Now() - time; |
| } |
| |
| void OnGetClientsFromUI( |
| // The tuple contains process_id, frame_id, client_uuid. |
| const std::vector<Tuple<int,int,std::string>>& clients_info, |
| const GURL& script_url, |
| const GetClientsCallback& callback) { |
| std::vector<ServiceWorkerClientInfo> clients; |
| |
| for (const auto& it : clients_info) { |
| ServiceWorkerClientInfo info = |
| ServiceWorkerProviderHost::GetClientInfoOnUI(get<0>(it), get<1>(it)); |
| |
| // If the request to the provider_host returned an empty |
| // ServiceWorkerClientInfo, that means that it wasn't possible to associate |
| // it with a valid RenderFrameHost. It might be because the frame was killed |
| // or navigated in between. |
| if (info.IsEmpty()) |
| continue; |
| |
| // We can get info for a frame that was navigating end ended up with a |
| // different URL than expected. In such case, we should make sure to not |
| // expose cross-origin WindowClient. |
| if (info.url.GetOrigin() != script_url.GetOrigin()) |
| return; |
| |
| info.client_uuid = get<2>(it); |
| clients.push_back(info); |
| } |
| |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| base::Bind(callback, clients)); |
| } |
| |
| } // namespace |
| |
| const int ServiceWorkerVersion::kStartWorkerTimeoutMinutes = 5; |
| |
| ServiceWorkerVersion::ServiceWorkerVersion( |
| ServiceWorkerRegistration* registration, |
| const GURL& script_url, |
| int64 version_id, |
| base::WeakPtr<ServiceWorkerContextCore> context) |
| : version_id_(version_id), |
| registration_id_(kInvalidServiceWorkerVersionId), |
| script_url_(script_url), |
| status_(NEW), |
| context_(context), |
| script_cache_map_(this, context), |
| ping_state_(NOT_PINGING), |
| weak_factory_(this) { |
| DCHECK(context_); |
| DCHECK(registration); |
| if (registration) { |
| registration_id_ = registration->id(); |
| scope_ = registration->pattern(); |
| } |
| context_->AddLiveVersion(this); |
| embedded_worker_ = context_->embedded_worker_registry()->CreateWorker(); |
| embedded_worker_->AddListener(this); |
| } |
| |
| ServiceWorkerVersion::~ServiceWorkerVersion() { |
| // The user may have closed the tab waiting for SW to start up. |
| if (GetTickDuration(start_time_) > |
| base::TimeDelta::FromSeconds( |
| kDestructedStartingWorkerTimeoutThresholdSeconds)) { |
| DCHECK(timeout_timer_.IsRunning()); |
| DCHECK(!embedded_worker_->devtools_attached()); |
| RecordStartWorkerResult(SERVICE_WORKER_ERROR_TIMEOUT); |
| } |
| |
| embedded_worker_->RemoveListener(this); |
| if (context_) |
| context_->RemoveLiveVersion(version_id_); |
| // EmbeddedWorker's dtor sends StopWorker if it's still running. |
| } |
| |
| void ServiceWorkerVersion::SetStatus(Status status) { |
| if (status_ == status) |
| return; |
| |
| status_ = status; |
| |
| if (skip_waiting_ && status_ == ACTIVATED) { |
| for (int request_id : pending_skip_waiting_requests_) |
| DidSkipWaiting(request_id); |
| pending_skip_waiting_requests_.clear(); |
| } |
| |
| std::vector<base::Closure> callbacks; |
| callbacks.swap(status_change_callbacks_); |
| for (const auto& callback : callbacks) |
| callback.Run(); |
| |
| FOR_EACH_OBSERVER(Listener, listeners_, OnVersionStateChanged(this)); |
| } |
| |
| void ServiceWorkerVersion::RegisterStatusChangeCallback( |
| const base::Closure& callback) { |
| status_change_callbacks_.push_back(callback); |
| } |
| |
| ServiceWorkerVersionInfo ServiceWorkerVersion::GetInfo() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return ServiceWorkerVersionInfo( |
| running_status(), status(), script_url(), registration_id(), version_id(), |
| embedded_worker()->process_id(), embedded_worker()->thread_id(), |
| embedded_worker()->worker_devtools_agent_route_id()); |
| } |
| |
| void ServiceWorkerVersion::StartWorker(const StatusCallback& callback) { |
| StartWorker(false, callback); |
| } |
| |
| void ServiceWorkerVersion::StartWorker( |
| bool pause_after_download, |
| const StatusCallback& callback) { |
| if (!context_) { |
| RunSoon(base::Bind(callback, SERVICE_WORKER_ERROR_START_WORKER_FAILED)); |
| return; |
| } |
| |
| // Ensure the live registration during starting worker so that the worker can |
| // get associated with it in SWDispatcherHost::OnSetHostedVersionId(). |
| context_->storage()->FindRegistrationForId( |
| registration_id_, |
| scope_.GetOrigin(), |
| base::Bind(&ServiceWorkerVersion::DidEnsureLiveRegistrationForStartWorker, |
| weak_factory_.GetWeakPtr(), |
| pause_after_download, |
| callback)); |
| } |
| |
| void ServiceWorkerVersion::StopWorker(const StatusCallback& callback) { |
| if (running_status() == STOPPED) { |
| RunSoon(base::Bind(callback, SERVICE_WORKER_OK)); |
| return; |
| } |
| if (stop_callbacks_.empty()) { |
| ServiceWorkerStatusCode status = embedded_worker_->Stop(); |
| if (status != SERVICE_WORKER_OK) { |
| RunSoon(base::Bind(callback, status)); |
| return; |
| } |
| } |
| stop_callbacks_.push_back(callback); |
| } |
| |
| void ServiceWorkerVersion::ScheduleUpdate() { |
| if (update_timer_.IsRunning()) { |
| update_timer_.Reset(); |
| return; |
| } |
| update_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(kUpdateDelaySeconds), |
| base::Bind(&ServiceWorkerVersion::StartUpdate, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ServiceWorkerVersion::DeferScheduledUpdate() { |
| if (update_timer_.IsRunning()) |
| update_timer_.Reset(); |
| } |
| |
| void ServiceWorkerVersion::StartUpdate() { |
| update_timer_.Stop(); |
| if (!context_) |
| return; |
| ServiceWorkerRegistration* registration = |
| context_->GetLiveRegistration(registration_id_); |
| if (!registration || !registration->GetNewestVersion()) |
| return; |
| context_->UpdateServiceWorker(registration); |
| } |
| |
| void ServiceWorkerVersion::DispatchMessageEvent( |
| const base::string16& message, |
| const std::vector<TransferredMessagePort>& sent_message_ports, |
| const StatusCallback& callback) { |
| for (const TransferredMessagePort& port : sent_message_ports) { |
| MessagePortService::GetInstance()->HoldMessages(port.id); |
| } |
| |
| DispatchMessageEventInternal(message, sent_message_ports, callback); |
| } |
| |
| void ServiceWorkerVersion::DispatchMessageEventInternal( |
| const base::string16& message, |
| const std::vector<TransferredMessagePort>& sent_message_ports, |
| const StatusCallback& callback) { |
| if (running_status() != RUNNING) { |
| // Schedule calling this method after starting the worker. |
| StartWorker(base::Bind( |
| &RunTaskAfterStartWorker, weak_factory_.GetWeakPtr(), |
| base::Bind(&RunErrorMessageCallback, sent_message_ports, callback), |
| base::Bind(&self::DispatchMessageEventInternal, |
| weak_factory_.GetWeakPtr(), message, sent_message_ports, |
| callback))); |
| return; |
| } |
| |
| MessagePortMessageFilter* filter = |
| embedded_worker_->message_port_message_filter(); |
| std::vector<int> new_routing_ids; |
| filter->UpdateMessagePortsWithNewRoutes(sent_message_ports, &new_routing_ids); |
| ServiceWorkerStatusCode status = |
| embedded_worker_->SendMessage(ServiceWorkerMsg_MessageToWorker( |
| message, sent_message_ports, new_routing_ids)); |
| RunSoon(base::Bind(callback, status)); |
| } |
| |
| void ServiceWorkerVersion::DispatchInstallEvent( |
| const StatusCallback& callback) { |
| DCHECK_EQ(INSTALLING, status()) << status(); |
| |
| if (running_status() != RUNNING) { |
| // Schedule calling this method after starting the worker. |
| StartWorker( |
| base::Bind(&RunTaskAfterStartWorker, |
| weak_factory_.GetWeakPtr(), |
| callback, |
| base::Bind(&self::DispatchInstallEventAfterStartWorker, |
| weak_factory_.GetWeakPtr(), |
| callback))); |
| } else { |
| DispatchInstallEventAfterStartWorker(callback); |
| } |
| } |
| |
| void ServiceWorkerVersion::DispatchActivateEvent( |
| const StatusCallback& callback) { |
| DCHECK_EQ(ACTIVATING, status()) << status(); |
| |
| if (running_status() != RUNNING) { |
| // Schedule calling this method after starting the worker. |
| StartWorker( |
| base::Bind(&RunTaskAfterStartWorker, |
| weak_factory_.GetWeakPtr(), |
| callback, |
| base::Bind(&self::DispatchActivateEventAfterStartWorker, |
| weak_factory_.GetWeakPtr(), |
| callback))); |
| } else { |
| DispatchActivateEventAfterStartWorker(callback); |
| } |
| } |
| |
| void ServiceWorkerVersion::DispatchFetchEvent( |
| const ServiceWorkerFetchRequest& request, |
| const base::Closure& prepare_callback, |
| const FetchCallback& fetch_callback) { |
| DCHECK_EQ(ACTIVATED, status()) << status(); |
| |
| if (running_status() != RUNNING) { |
| // Schedule calling this method after starting the worker. |
| StartWorker(base::Bind(&RunTaskAfterStartWorker, |
| weak_factory_.GetWeakPtr(), |
| base::Bind(&RunErrorFetchCallback, fetch_callback), |
| base::Bind(&self::DispatchFetchEvent, |
| weak_factory_.GetWeakPtr(), |
| request, |
| prepare_callback, |
| fetch_callback))); |
| return; |
| } |
| |
| prepare_callback.Run(); |
| |
| int request_id = fetch_callbacks_.Add(new FetchCallback(fetch_callback)); |
| ServiceWorkerStatusCode status = embedded_worker_->SendMessage( |
| ServiceWorkerMsg_FetchEvent(request_id, request)); |
| if (status != SERVICE_WORKER_OK) { |
| fetch_callbacks_.Remove(request_id); |
| RunSoon(base::Bind(&RunErrorFetchCallback, |
| fetch_callback, |
| SERVICE_WORKER_ERROR_FAILED)); |
| } |
| } |
| |
| void ServiceWorkerVersion::DispatchSyncEvent(const StatusCallback& callback) { |
| DCHECK_EQ(ACTIVATED, status()) << status(); |
| |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableServiceWorkerSync)) { |
| callback.Run(SERVICE_WORKER_ERROR_ABORT); |
| return; |
| } |
| |
| if (running_status() != RUNNING) { |
| // Schedule calling this method after starting the worker. |
| StartWorker(base::Bind(&RunTaskAfterStartWorker, |
| weak_factory_.GetWeakPtr(), callback, |
| base::Bind(&self::DispatchSyncEvent, |
| weak_factory_.GetWeakPtr(), |
| callback))); |
| return; |
| } |
| |
| int request_id = sync_callbacks_.Add(new StatusCallback(callback)); |
| ServiceWorkerStatusCode status = embedded_worker_->SendMessage( |
| ServiceWorkerMsg_SyncEvent(request_id)); |
| if (status != SERVICE_WORKER_OK) { |
| sync_callbacks_.Remove(request_id); |
| RunSoon(base::Bind(callback, status)); |
| } |
| } |
| |
| void ServiceWorkerVersion::DispatchNotificationClickEvent( |
| const StatusCallback& callback, |
| const std::string& notification_id, |
| const PlatformNotificationData& notification_data) { |
| DCHECK_EQ(ACTIVATED, status()) << status(); |
| if (running_status() != RUNNING) { |
| // Schedule calling this method after starting the worker. |
| StartWorker(base::Bind(&RunTaskAfterStartWorker, |
| weak_factory_.GetWeakPtr(), callback, |
| base::Bind(&self::DispatchNotificationClickEvent, |
| weak_factory_.GetWeakPtr(), |
| callback, notification_id, |
| notification_data))); |
| return; |
| } |
| |
| int request_id = |
| notification_click_callbacks_.Add(new StatusCallback(callback)); |
| ServiceWorkerStatusCode status = embedded_worker_->SendMessage( |
| ServiceWorkerMsg_NotificationClickEvent(request_id, |
| notification_id, |
| notification_data)); |
| if (status != SERVICE_WORKER_OK) { |
| notification_click_callbacks_.Remove(request_id); |
| RunSoon(base::Bind(callback, status)); |
| } |
| } |
| |
| void ServiceWorkerVersion::DispatchPushEvent(const StatusCallback& callback, |
| const std::string& data) { |
| DCHECK_EQ(ACTIVATED, status()) << status(); |
| if (running_status() != RUNNING) { |
| // Schedule calling this method after starting the worker. |
| StartWorker(base::Bind(&RunTaskAfterStartWorker, |
| weak_factory_.GetWeakPtr(), callback, |
| base::Bind(&self::DispatchPushEvent, |
| weak_factory_.GetWeakPtr(), |
| callback, data))); |
| return; |
| } |
| |
| int request_id = push_callbacks_.Add(new StatusCallback(callback)); |
| ServiceWorkerStatusCode status = embedded_worker_->SendMessage( |
| ServiceWorkerMsg_PushEvent(request_id, data)); |
| if (status != SERVICE_WORKER_OK) { |
| push_callbacks_.Remove(request_id); |
| RunSoon(base::Bind(callback, status)); |
| } |
| } |
| |
| void ServiceWorkerVersion::DispatchGeofencingEvent( |
| const StatusCallback& callback, |
| blink::WebGeofencingEventType event_type, |
| const std::string& region_id, |
| const blink::WebCircularGeofencingRegion& region) { |
| DCHECK_EQ(ACTIVATED, status()) << status(); |
| |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures)) { |
| callback.Run(SERVICE_WORKER_ERROR_ABORT); |
| return; |
| } |
| |
| if (running_status() != RUNNING) { |
| // Schedule calling this method after starting the worker. |
| StartWorker(base::Bind(&RunTaskAfterStartWorker, |
| weak_factory_.GetWeakPtr(), |
| callback, |
| base::Bind(&self::DispatchGeofencingEvent, |
| weak_factory_.GetWeakPtr(), |
| callback, |
| event_type, |
| region_id, |
| region))); |
| return; |
| } |
| |
| int request_id = geofencing_callbacks_.Add(new StatusCallback(callback)); |
| ServiceWorkerStatusCode status = |
| embedded_worker_->SendMessage(ServiceWorkerMsg_GeofencingEvent( |
| request_id, event_type, region_id, region)); |
| if (status != SERVICE_WORKER_OK) { |
| geofencing_callbacks_.Remove(request_id); |
| RunSoon(base::Bind(callback, status)); |
| } |
| } |
| |
| void ServiceWorkerVersion::DispatchCrossOriginConnectEvent( |
| const CrossOriginConnectCallback& callback, |
| const NavigatorConnectClient& client) { |
| DCHECK_EQ(ACTIVATED, status()) << status(); |
| |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures)) { |
| callback.Run(SERVICE_WORKER_ERROR_ABORT, false); |
| return; |
| } |
| |
| if (running_status() != RUNNING) { |
| // Schedule calling this method after starting the worker. |
| StartWorker( |
| base::Bind(&RunTaskAfterStartWorker, weak_factory_.GetWeakPtr(), |
| base::Bind(&RunErrorCrossOriginConnectCallback, callback), |
| base::Bind(&self::DispatchCrossOriginConnectEvent, |
| weak_factory_.GetWeakPtr(), callback, client))); |
| return; |
| } |
| |
| int request_id = cross_origin_connect_callbacks_.Add( |
| new CrossOriginConnectCallback(callback)); |
| ServiceWorkerStatusCode status = embedded_worker_->SendMessage( |
| ServiceWorkerMsg_CrossOriginConnectEvent(request_id, client)); |
| if (status != SERVICE_WORKER_OK) { |
| cross_origin_connect_callbacks_.Remove(request_id); |
| RunSoon(base::Bind(callback, status, false)); |
| } |
| } |
| |
| void ServiceWorkerVersion::DispatchCrossOriginMessageEvent( |
| const NavigatorConnectClient& client, |
| const base::string16& message, |
| const std::vector<TransferredMessagePort>& sent_message_ports, |
| const StatusCallback& callback) { |
| // Unlike in the case of DispatchMessageEvent, here the caller is assumed to |
| // have already put all the sent message ports on hold. So no need to do that |
| // here again. |
| |
| if (running_status() != RUNNING) { |
| // Schedule calling this method after starting the worker. |
| StartWorker(base::Bind( |
| &RunTaskAfterStartWorker, weak_factory_.GetWeakPtr(), |
| base::Bind(&RunErrorMessageCallback, sent_message_ports, callback), |
| base::Bind(&self::DispatchCrossOriginMessageEvent, |
| weak_factory_.GetWeakPtr(), client, message, |
| sent_message_ports, callback))); |
| return; |
| } |
| |
| MessagePortMessageFilter* filter = |
| embedded_worker_->message_port_message_filter(); |
| std::vector<int> new_routing_ids; |
| filter->UpdateMessagePortsWithNewRoutes(sent_message_ports, &new_routing_ids); |
| ServiceWorkerStatusCode status = |
| embedded_worker_->SendMessage(ServiceWorkerMsg_CrossOriginMessageToWorker( |
| client, message, sent_message_ports, new_routing_ids)); |
| RunSoon(base::Bind(callback, status)); |
| } |
| |
| void ServiceWorkerVersion::AddControllee( |
| ServiceWorkerProviderHost* provider_host) { |
| const std::string& uuid = provider_host->client_uuid(); |
| CHECK(!provider_host->client_uuid().empty()); |
| DCHECK(!ContainsKey(controllee_map_, uuid)); |
| controllee_map_[uuid] = provider_host; |
| // Keep the worker alive a bit longer right after a new controllee is added. |
| RestartTick(&idle_time_); |
| } |
| |
| void ServiceWorkerVersion::RemoveControllee( |
| ServiceWorkerProviderHost* provider_host) { |
| const std::string& uuid = provider_host->client_uuid(); |
| DCHECK(ContainsKey(controllee_map_, uuid)); |
| controllee_map_.erase(uuid); |
| if (HasControllee()) |
| return; |
| FOR_EACH_OBSERVER(Listener, listeners_, OnNoControllees(this)); |
| if (is_doomed_) { |
| DoomInternal(); |
| return; |
| } |
| } |
| |
| void ServiceWorkerVersion::AddStreamingURLRequestJob( |
| const ServiceWorkerURLRequestJob* request_job) { |
| DCHECK(streaming_url_request_jobs_.find(request_job) == |
| streaming_url_request_jobs_.end()); |
| streaming_url_request_jobs_.insert(request_job); |
| } |
| |
| void ServiceWorkerVersion::RemoveStreamingURLRequestJob( |
| const ServiceWorkerURLRequestJob* request_job) { |
| streaming_url_request_jobs_.erase(request_job); |
| if (is_doomed_) |
| StopWorkerIfIdle(); |
| } |
| |
| void ServiceWorkerVersion::AddListener(Listener* listener) { |
| listeners_.AddObserver(listener); |
| } |
| |
| void ServiceWorkerVersion::RemoveListener(Listener* listener) { |
| listeners_.RemoveObserver(listener); |
| } |
| |
| void ServiceWorkerVersion::Doom() { |
| if (is_doomed_) |
| return; |
| is_doomed_ = true; |
| if (!HasControllee()) |
| DoomInternal(); |
| } |
| |
| void ServiceWorkerVersion::SetDevToolsAttached(bool attached) { |
| embedded_worker()->set_devtools_attached(attached); |
| if (attached) { |
| // Don't record the startup time metric once DevTools is attached. |
| ClearTick(&start_time_); |
| skip_recording_startup_time_ = true; |
| return; |
| } |
| if (!start_callbacks_.empty()) { |
| // Reactivate the timer for start timeout. |
| DCHECK(timeout_timer_.IsRunning()); |
| DCHECK(running_status() == STARTING || running_status() == STOPPING) |
| << running_status(); |
| RestartTick(&start_time_); |
| } |
| } |
| |
| void ServiceWorkerVersion::SetMainScriptHttpResponseInfo( |
| const net::HttpResponseInfo& http_info) { |
| main_script_http_info_.reset(new net::HttpResponseInfo(http_info)); |
| } |
| |
| const net::HttpResponseInfo* |
| ServiceWorkerVersion::GetMainScriptHttpResponseInfo() { |
| return main_script_http_info_.get(); |
| } |
| |
| void ServiceWorkerVersion::OnScriptLoaded() { |
| DCHECK_EQ(STARTING, running_status()); |
| // Activate ping/pong now that JavaScript execution will start. |
| ping_state_ = PINGING; |
| } |
| |
| void ServiceWorkerVersion::OnStarting() { |
| FOR_EACH_OBSERVER(Listener, listeners_, OnRunningStateChanged(this)); |
| } |
| |
| void ServiceWorkerVersion::OnStarted() { |
| DCHECK_EQ(RUNNING, running_status()); |
| RestartTick(&idle_time_); |
| |
| // Fire all start callbacks. |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| RunCallbacks(this, &start_callbacks_, SERVICE_WORKER_OK); |
| FOR_EACH_OBSERVER(Listener, listeners_, OnRunningStateChanged(this)); |
| } |
| |
| void ServiceWorkerVersion::OnStopping() { |
| FOR_EACH_OBSERVER(Listener, listeners_, OnRunningStateChanged(this)); |
| } |
| |
| void ServiceWorkerVersion::OnStopped( |
| EmbeddedWorkerInstance::Status old_status) { |
| DCHECK_EQ(STOPPED, running_status()); |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| |
| bool should_restart = !is_doomed() && !start_callbacks_.empty() && |
| (old_status != EmbeddedWorkerInstance::STARTING); |
| |
| StopTimeoutTimer(); |
| if (ping_state_ == PING_TIMED_OUT) |
| should_restart = false; |
| |
| // Fire all stop callbacks. |
| RunCallbacks(this, &stop_callbacks_, SERVICE_WORKER_OK); |
| |
| if (!should_restart) { |
| // Let all start callbacks fail. |
| RunCallbacks(this, &start_callbacks_, |
| SERVICE_WORKER_ERROR_START_WORKER_FAILED); |
| } |
| |
| // 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. |
| RunIDMapCallbacks(&activate_callbacks_, |
| SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED); |
| RunIDMapCallbacks(&install_callbacks_, |
| SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED); |
| RunIDMapCallbacks(&fetch_callbacks_, |
| SERVICE_WORKER_ERROR_FAILED, |
| SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, |
| ServiceWorkerResponse()); |
| RunIDMapCallbacks(&sync_callbacks_, |
| SERVICE_WORKER_ERROR_FAILED); |
| RunIDMapCallbacks(¬ification_click_callbacks_, |
| SERVICE_WORKER_ERROR_FAILED); |
| RunIDMapCallbacks(&push_callbacks_, |
| SERVICE_WORKER_ERROR_FAILED); |
| RunIDMapCallbacks(&geofencing_callbacks_, |
| SERVICE_WORKER_ERROR_FAILED); |
| RunIDMapCallbacks(&cross_origin_connect_callbacks_, |
| SERVICE_WORKER_ERROR_FAILED, |
| false); |
| |
| streaming_url_request_jobs_.clear(); |
| |
| FOR_EACH_OBSERVER(Listener, listeners_, OnRunningStateChanged(this)); |
| |
| // Restart worker if we have any start callbacks and the worker isn't doomed. |
| if (should_restart) |
| StartWorkerInternal(false /* pause_after_download */); |
| } |
| |
| void ServiceWorkerVersion::OnReportException( |
| const base::string16& error_message, |
| int line_number, |
| int column_number, |
| const GURL& source_url) { |
| FOR_EACH_OBSERVER( |
| Listener, |
| listeners_, |
| OnErrorReported( |
| this, error_message, line_number, column_number, source_url)); |
| } |
| |
| void ServiceWorkerVersion::OnReportConsoleMessage(int source_identifier, |
| int message_level, |
| const base::string16& message, |
| int line_number, |
| const GURL& source_url) { |
| FOR_EACH_OBSERVER(Listener, |
| listeners_, |
| OnReportConsoleMessage(this, |
| source_identifier, |
| message_level, |
| message, |
| line_number, |
| source_url)); |
| } |
| |
| bool ServiceWorkerVersion::OnMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(ServiceWorkerVersion, message) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_GetClients, |
| OnGetClients) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_ActivateEventFinished, |
| OnActivateEventFinished) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_InstallEventFinished, |
| OnInstallEventFinished) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FetchEventFinished, |
| OnFetchEventFinished) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SyncEventFinished, |
| OnSyncEventFinished) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_NotificationClickEventFinished, |
| OnNotificationClickEventFinished) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PushEventFinished, |
| OnPushEventFinished) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_GeofencingEventFinished, |
| OnGeofencingEventFinished) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_CrossOriginConnectEventFinished, |
| OnCrossOriginConnectEventFinished) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_OpenWindow, |
| OnOpenWindow) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SetCachedMetadata, |
| OnSetCachedMetadata) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_ClearCachedMetadata, |
| OnClearCachedMetadata) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PostMessageToClient, |
| OnPostMessageToClient) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FocusClient, |
| OnFocusClient) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SkipWaiting, |
| OnSkipWaiting) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_ClaimClients, |
| OnClaimClients) |
| IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_Pong, OnPongFromWorker) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| return handled; |
| } |
| |
| void ServiceWorkerVersion::OnStartSentAndScriptEvaluated( |
| ServiceWorkerStatusCode status) { |
| if (status != SERVICE_WORKER_OK) |
| RunCallbacks(this, &start_callbacks_, status); |
| } |
| |
| void ServiceWorkerVersion::DispatchInstallEventAfterStartWorker( |
| const StatusCallback& callback) { |
| DCHECK_EQ(RUNNING, running_status()) |
| << "Worker stopped too soon after it was started."; |
| |
| int request_id = install_callbacks_.Add(new StatusCallback(callback)); |
| ServiceWorkerStatusCode status = embedded_worker_->SendMessage( |
| ServiceWorkerMsg_InstallEvent(request_id)); |
| if (status != SERVICE_WORKER_OK) { |
| install_callbacks_.Remove(request_id); |
| RunSoon(base::Bind(callback, status)); |
| } |
| } |
| |
| void ServiceWorkerVersion::DispatchActivateEventAfterStartWorker( |
| const StatusCallback& callback) { |
| DCHECK_EQ(RUNNING, running_status()) |
| << "Worker stopped too soon after it was started."; |
| |
| int request_id = activate_callbacks_.Add(new StatusCallback(callback)); |
| ServiceWorkerStatusCode status = |
| embedded_worker_->SendMessage(ServiceWorkerMsg_ActivateEvent(request_id)); |
| if (status != SERVICE_WORKER_OK) { |
| activate_callbacks_.Remove(request_id); |
| RunSoon(base::Bind(callback, status)); |
| } |
| } |
| |
| void ServiceWorkerVersion::OnGetClients( |
| int request_id, |
| const ServiceWorkerClientQueryOptions& options) { |
| if (controllee_map_.empty() && !options.include_uncontrolled) { |
| if (running_status() == RUNNING) { |
| embedded_worker_->SendMessage( |
| ServiceWorkerMsg_DidGetClients(request_id, |
| std::vector<ServiceWorkerClientInfo>())); |
| } |
| return; |
| } |
| |
| TRACE_EVENT0("ServiceWorker", |
| "ServiceWorkerVersion::OnGetClients"); |
| |
| // 4.3.1 matchAll(options) |
| std::vector<Tuple<int,int,std::string>> clients_info; |
| if (!options.include_uncontrolled) { |
| for (auto& controllee : controllee_map_) { |
| int process_id = controllee.second->process_id(); |
| int frame_id = controllee.second->frame_id(); |
| const std::string& client_uuid = controllee.first; |
| clients_info.push_back(MakeTuple(process_id, frame_id, client_uuid)); |
| } |
| } else { |
| for (auto it = |
| context_->GetClientProviderHostIterator(script_url_.GetOrigin()); |
| !it->IsAtEnd(); it->Advance()) { |
| ServiceWorkerProviderHost* host = it->GetProviderHost(); |
| clients_info.push_back( |
| MakeTuple(host->process_id(), host->frame_id(), host->client_uuid())); |
| } |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&OnGetClientsFromUI, clients_info, script_url_, |
| base::Bind(&ServiceWorkerVersion::DidGetClients, |
| weak_factory_.GetWeakPtr(), |
| request_id))); |
| |
| } |
| |
| void ServiceWorkerVersion::OnActivateEventFinished( |
| int request_id, |
| blink::WebServiceWorkerEventResult result) { |
| DCHECK(ACTIVATING == status() || |
| REDUNDANT == status()) << status(); |
| TRACE_EVENT0("ServiceWorker", |
| "ServiceWorkerVersion::OnActivateEventFinished"); |
| |
| StatusCallback* callback = activate_callbacks_.Lookup(request_id); |
| if (!callback) { |
| NOTREACHED() << "Got unexpected message: " << request_id; |
| return; |
| } |
| ServiceWorkerStatusCode rv = SERVICE_WORKER_OK; |
| if (result == blink::WebServiceWorkerEventResultRejected || |
| status() != ACTIVATING) { |
| rv = SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED; |
| } |
| |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| callback->Run(rv); |
| RemoveCallbackAndStopIfDoomed(&activate_callbacks_, request_id); |
| } |
| |
| void ServiceWorkerVersion::OnInstallEventFinished( |
| int request_id, |
| blink::WebServiceWorkerEventResult result) { |
| // Status is REDUNDANT if the worker was doomed while handling the install |
| // event, and finished handling before being terminated. |
| DCHECK(status() == INSTALLING || status() == REDUNDANT) << status(); |
| TRACE_EVENT0("ServiceWorker", |
| "ServiceWorkerVersion::OnInstallEventFinished"); |
| |
| StatusCallback* callback = install_callbacks_.Lookup(request_id); |
| if (!callback) { |
| NOTREACHED() << "Got unexpected message: " << request_id; |
| return; |
| } |
| ServiceWorkerStatusCode status = SERVICE_WORKER_OK; |
| if (result == blink::WebServiceWorkerEventResultRejected) |
| status = SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED; |
| |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| callback->Run(status); |
| RemoveCallbackAndStopIfDoomed(&install_callbacks_, request_id); |
| } |
| |
| void ServiceWorkerVersion::OnFetchEventFinished( |
| int request_id, |
| ServiceWorkerFetchEventResult result, |
| const ServiceWorkerResponse& response) { |
| TRACE_EVENT1("ServiceWorker", |
| "ServiceWorkerVersion::OnFetchEventFinished", |
| "Request id", request_id); |
| FetchCallback* callback = fetch_callbacks_.Lookup(request_id); |
| if (!callback) { |
| NOTREACHED() << "Got unexpected message: " << request_id; |
| return; |
| } |
| |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| callback->Run(SERVICE_WORKER_OK, result, response); |
| RemoveCallbackAndStopIfDoomed(&fetch_callbacks_, request_id); |
| } |
| |
| void ServiceWorkerVersion::OnSyncEventFinished( |
| int request_id) { |
| TRACE_EVENT1("ServiceWorker", |
| "ServiceWorkerVersion::OnSyncEventFinished", |
| "Request id", request_id); |
| StatusCallback* callback = sync_callbacks_.Lookup(request_id); |
| if (!callback) { |
| NOTREACHED() << "Got unexpected message: " << request_id; |
| return; |
| } |
| |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| callback->Run(SERVICE_WORKER_OK); |
| RemoveCallbackAndStopIfDoomed(&sync_callbacks_, request_id); |
| } |
| |
| void ServiceWorkerVersion::OnNotificationClickEventFinished( |
| int request_id) { |
| TRACE_EVENT1("ServiceWorker", |
| "ServiceWorkerVersion::OnNotificationClickEventFinished", |
| "Request id", request_id); |
| StatusCallback* callback = notification_click_callbacks_.Lookup(request_id); |
| if (!callback) { |
| NOTREACHED() << "Got unexpected message: " << request_id; |
| return; |
| } |
| |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| callback->Run(SERVICE_WORKER_OK); |
| RemoveCallbackAndStopIfDoomed(¬ification_click_callbacks_, request_id); |
| } |
| |
| void ServiceWorkerVersion::OnPushEventFinished( |
| int request_id, |
| blink::WebServiceWorkerEventResult result) { |
| TRACE_EVENT1("ServiceWorker", |
| "ServiceWorkerVersion::OnPushEventFinished", |
| "Request id", request_id); |
| StatusCallback* callback = push_callbacks_.Lookup(request_id); |
| if (!callback) { |
| NOTREACHED() << "Got unexpected message: " << request_id; |
| return; |
| } |
| ServiceWorkerStatusCode status = SERVICE_WORKER_OK; |
| if (result == blink::WebServiceWorkerEventResultRejected) |
| status = SERVICE_WORKER_ERROR_EVENT_WAITUNTIL_REJECTED; |
| |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| callback->Run(status); |
| RemoveCallbackAndStopIfDoomed(&push_callbacks_, request_id); |
| } |
| |
| void ServiceWorkerVersion::OnGeofencingEventFinished(int request_id) { |
| TRACE_EVENT1("ServiceWorker", |
| "ServiceWorkerVersion::OnGeofencingEventFinished", |
| "Request id", |
| request_id); |
| StatusCallback* callback = geofencing_callbacks_.Lookup(request_id); |
| if (!callback) { |
| NOTREACHED() << "Got unexpected message: " << request_id; |
| return; |
| } |
| |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| callback->Run(SERVICE_WORKER_OK); |
| RemoveCallbackAndStopIfDoomed(&geofencing_callbacks_, request_id); |
| } |
| |
| void ServiceWorkerVersion::OnCrossOriginConnectEventFinished( |
| int request_id, |
| bool accept_connection) { |
| TRACE_EVENT1("ServiceWorker", |
| "ServiceWorkerVersion::OnCrossOriginConnectEventFinished", |
| "Request id", request_id); |
| CrossOriginConnectCallback* callback = |
| cross_origin_connect_callbacks_.Lookup(request_id); |
| if (!callback) { |
| NOTREACHED() << "Got unexpected message: " << request_id; |
| return; |
| } |
| |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| callback->Run(SERVICE_WORKER_OK, accept_connection); |
| RemoveCallbackAndStopIfDoomed(&cross_origin_connect_callbacks_, request_id); |
| } |
| |
| void ServiceWorkerVersion::OnOpenWindow(int request_id, GURL url) { |
| // Just abort if we are shutting down. |
| if (!context_) |
| return; |
| |
| if (!url.is_valid()) { |
| DVLOG(1) << "Received unexpected invalid URL from renderer process."; |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| base::Bind(&KillEmbeddedWorkerProcess, |
| embedded_worker_->process_id(), |
| RESULT_CODE_KILLED_BAD_MESSAGE)); |
| 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)) { |
| embedded_worker_->SendMessage(ServiceWorkerMsg_OpenWindowError( |
| request_id, url.spec() + " cannot be opened.")); |
| return; |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&OpenWindowOnUI, |
| url, |
| script_url_, |
| embedded_worker_->process_id(), |
| make_scoped_refptr(context_->wrapper()), |
| base::Bind(&ServiceWorkerVersion::DidOpenWindow, |
| weak_factory_.GetWeakPtr(), |
| request_id))); |
| } |
| |
| void ServiceWorkerVersion::DidOpenWindow(int request_id, |
| int render_process_id, |
| int render_frame_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (running_status() != RUNNING) |
| return; |
| |
| if (render_process_id == ChildProcessHost::kInvalidUniqueID && |
| render_frame_id == MSG_ROUTING_NONE) { |
| embedded_worker_->SendMessage(ServiceWorkerMsg_OpenWindowError( |
| request_id, "Something went wrong while trying to open the window.")); |
| return; |
| } |
| |
| for (auto it = |
| context_->GetClientProviderHostIterator(script_url_.GetOrigin()); |
| !it->IsAtEnd(); it->Advance()) { |
| ServiceWorkerProviderHost* provider_host = it->GetProviderHost(); |
| if (provider_host->process_id() != render_process_id || |
| provider_host->frame_id() != render_frame_id) { |
| continue; |
| } |
| provider_host->GetClientInfo(base::Bind( |
| &ServiceWorkerVersion::OnOpenWindowFinished, weak_factory_.GetWeakPtr(), |
| request_id, provider_host->client_uuid())); |
| return; |
| } |
| |
| // If here, it means that no provider_host was found, in which case, the |
| // renderer should still be informed that the window was opened. |
| OnOpenWindowFinished(request_id, std::string(), ServiceWorkerClientInfo()); |
| } |
| |
| void ServiceWorkerVersion::OnOpenWindowFinished( |
| int request_id, |
| const std::string& client_uuid, |
| const ServiceWorkerClientInfo& client_info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (running_status() != RUNNING) |
| return; |
| |
| ServiceWorkerClientInfo client(client_info); |
| |
| // If the |client_info| is empty, it means that the opened window wasn't |
| // controlled but the action still succeeded. The renderer process is |
| // expecting an empty client in such case. |
| if (!client.IsEmpty()) |
| client.client_uuid = client_uuid; |
| |
| embedded_worker_->SendMessage(ServiceWorkerMsg_OpenWindowResponse( |
| request_id, client)); |
| } |
| |
| void ServiceWorkerVersion::OnSetCachedMetadata(const GURL& url, |
| const std::vector<char>& data) { |
| int64 callback_id = base::TimeTicks::Now().ToInternalValue(); |
| TRACE_EVENT_ASYNC_BEGIN1("ServiceWorker", |
| "ServiceWorkerVersion::OnSetCachedMetadata", |
| callback_id, "URL", url.spec()); |
| script_cache_map_.WriteMetadata( |
| url, data, base::Bind(&ServiceWorkerVersion::OnSetCachedMetadataFinished, |
| weak_factory_.GetWeakPtr(), callback_id)); |
| } |
| |
| void ServiceWorkerVersion::OnSetCachedMetadataFinished(int64 callback_id, |
| int result) { |
| TRACE_EVENT_ASYNC_END1("ServiceWorker", |
| "ServiceWorkerVersion::OnSetCachedMetadata", |
| callback_id, "result", result); |
| FOR_EACH_OBSERVER(Listener, listeners_, OnCachedMetadataUpdated(this)); |
| } |
| |
| void ServiceWorkerVersion::OnClearCachedMetadata(const GURL& url) { |
| int64 callback_id = base::TimeTicks::Now().ToInternalValue(); |
| TRACE_EVENT_ASYNC_BEGIN1("ServiceWorker", |
| "ServiceWorkerVersion::OnClearCachedMetadata", |
| callback_id, "URL", url.spec()); |
| script_cache_map_.ClearMetadata( |
| url, base::Bind(&ServiceWorkerVersion::OnClearCachedMetadataFinished, |
| weak_factory_.GetWeakPtr(), callback_id)); |
| } |
| |
| void ServiceWorkerVersion::OnClearCachedMetadataFinished(int64 callback_id, |
| int result) { |
| TRACE_EVENT_ASYNC_END1("ServiceWorker", |
| "ServiceWorkerVersion::OnClearCachedMetadata", |
| callback_id, "result", result); |
| FOR_EACH_OBSERVER(Listener, listeners_, OnCachedMetadataUpdated(this)); |
| } |
| |
| void ServiceWorkerVersion::OnPostMessageToClient( |
| const std::string& client_uuid, |
| const base::string16& message, |
| const std::vector<TransferredMessagePort>& sent_message_ports) { |
| if (!context_) |
| return; |
| TRACE_EVENT1("ServiceWorker", |
| "ServiceWorkerVersion::OnPostMessageToDocument", |
| "Client id", client_uuid); |
| ServiceWorkerProviderHost* provider_host = |
| context_->GetProviderHostByClientID(client_uuid); |
| if (!provider_host) { |
| // The client may already have been closed, just ignore. |
| return; |
| } |
| if (provider_host->document_url().GetOrigin() != script_url_.GetOrigin()) { |
| // The client does not belong to the same origin as this ServiceWorker, |
| // possibly due to timing issue or bad message. |
| return; |
| } |
| provider_host->PostMessage(message, sent_message_ports); |
| } |
| |
| void ServiceWorkerVersion::OnFocusClient(int request_id, |
| const std::string& client_uuid) { |
| if (!context_) |
| return; |
| TRACE_EVENT2("ServiceWorker", |
| "ServiceWorkerVersion::OnFocusClient", |
| "Request id", request_id, |
| "Client id", client_uuid); |
| ServiceWorkerProviderHost* provider_host = |
| context_->GetProviderHostByClientID(client_uuid); |
| if (!provider_host) { |
| // The client may already have been closed, just ignore. |
| return; |
| } |
| if (provider_host->document_url().GetOrigin() != script_url_.GetOrigin()) { |
| // The client does not belong to the same origin as this ServiceWorker, |
| // possibly due to timing issue or bad message. |
| return; |
| } |
| provider_host->Focus(base::Bind(&ServiceWorkerVersion::OnFocusClientFinished, |
| weak_factory_.GetWeakPtr(), request_id, |
| client_uuid)); |
| } |
| |
| void ServiceWorkerVersion::OnFocusClientFinished( |
| int request_id, |
| const std::string& cliend_uuid, |
| const ServiceWorkerClientInfo& client) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (running_status() != RUNNING) |
| return; |
| |
| ServiceWorkerClientInfo client_info(client); |
| client_info.client_uuid = cliend_uuid; |
| |
| embedded_worker_->SendMessage(ServiceWorkerMsg_FocusClientResponse( |
| request_id, client_info)); |
| } |
| |
| void ServiceWorkerVersion::OnSkipWaiting(int request_id) { |
| skip_waiting_ = true; |
| if (status_ != INSTALLED) |
| return DidSkipWaiting(request_id); |
| |
| if (!context_) |
| return; |
| ServiceWorkerRegistration* registration = |
| context_->GetLiveRegistration(registration_id_); |
| if (!registration) |
| return; |
| pending_skip_waiting_requests_.push_back(request_id); |
| if (pending_skip_waiting_requests_.size() == 1) |
| registration->ActivateWaitingVersionWhenReady(); |
| } |
| |
| void ServiceWorkerVersion::DidSkipWaiting(int request_id) { |
| if (running_status() == STARTING || running_status() == RUNNING) |
| embedded_worker_->SendMessage(ServiceWorkerMsg_DidSkipWaiting(request_id)); |
| } |
| |
| void ServiceWorkerVersion::OnClaimClients(int request_id) { |
| StatusCallback callback = base::Bind(&ServiceWorkerVersion::DidClaimClients, |
| weak_factory_.GetWeakPtr(), request_id); |
| if (status_ != ACTIVATING && status_ != ACTIVATED) { |
| callback.Run(SERVICE_WORKER_ERROR_STATE); |
| return; |
| } |
| if (!context_) { |
| callback.Run(SERVICE_WORKER_ERROR_ABORT); |
| return; |
| } |
| |
| ServiceWorkerRegistration* registration = |
| context_->GetLiveRegistration(registration_id_); |
| if (!registration) { |
| callback.Run(SERVICE_WORKER_ERROR_ABORT); |
| return; |
| } |
| registration->ClaimClients(callback); |
| } |
| |
| void ServiceWorkerVersion::OnPongFromWorker() { |
| ClearTick(&ping_time_); |
| } |
| |
| void ServiceWorkerVersion::DidEnsureLiveRegistrationForStartWorker( |
| bool pause_after_download, |
| const StatusCallback& callback, |
| ServiceWorkerStatusCode status, |
| const scoped_refptr<ServiceWorkerRegistration>& protect) { |
| if (status != SERVICE_WORKER_OK || is_doomed()) { |
| RecordStartWorkerResult(status); |
| RunSoon(base::Bind(callback, SERVICE_WORKER_ERROR_START_WORKER_FAILED)); |
| return; |
| } |
| |
| switch (running_status()) { |
| case RUNNING: |
| RunSoon(base::Bind(callback, SERVICE_WORKER_OK)); |
| return; |
| case STOPPING: |
| case STOPPED: |
| case STARTING: |
| if (start_callbacks_.empty()) { |
| start_callbacks_.push_back( |
| base::Bind(&ServiceWorkerVersion::RecordStartWorkerResult, |
| weak_factory_.GetWeakPtr())); |
| } |
| // Keep the live registration while starting the worker. |
| start_callbacks_.push_back( |
| base::Bind(&RunStartWorkerCallback, callback, protect)); |
| StartWorkerInternal(pause_after_download); |
| return; |
| } |
| } |
| |
| void ServiceWorkerVersion::StartWorkerInternal(bool pause_after_download) { |
| if (!timeout_timer_.IsRunning()) |
| StartTimeoutTimer(); |
| if (running_status() == STOPPED) { |
| embedded_worker_->Start( |
| version_id_, scope_, script_url_, pause_after_download, |
| base::Bind(&ServiceWorkerVersion::OnStartSentAndScriptEvaluated, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void ServiceWorkerVersion::DidClaimClients( |
| int request_id, ServiceWorkerStatusCode status) { |
| if (status == SERVICE_WORKER_ERROR_STATE) { |
| embedded_worker_->SendMessage(ServiceWorkerMsg_ClaimClientsError( |
| request_id, blink::WebServiceWorkerError::ErrorTypeState, |
| base::ASCIIToUTF16(kClaimClientsStateErrorMesage))); |
| return; |
| } |
| if (status == SERVICE_WORKER_ERROR_ABORT) { |
| embedded_worker_->SendMessage(ServiceWorkerMsg_ClaimClientsError( |
| request_id, blink::WebServiceWorkerError::ErrorTypeAbort, |
| base::ASCIIToUTF16(kClaimClientsShutdownErrorMesage))); |
| return; |
| } |
| DCHECK(status == SERVICE_WORKER_OK); |
| embedded_worker_->SendMessage(ServiceWorkerMsg_DidClaimClients(request_id)); |
| } |
| |
| void ServiceWorkerVersion::DidGetClients( |
| int request_id, |
| const std::vector<ServiceWorkerClientInfo>& clients) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (running_status() != RUNNING) |
| return; |
| |
| embedded_worker_->SendMessage( |
| ServiceWorkerMsg_DidGetClients(request_id, clients)); |
| } |
| |
| 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; |
| } |
| |
| ClearTick(&idle_time_); |
| ClearTick(&ping_time_); |
| ping_state_ = NOT_PINGING; |
| |
| timeout_timer_.Start(FROM_HERE, |
| base::TimeDelta::FromSeconds(kTimeoutTimerDelaySeconds), |
| this, &ServiceWorkerVersion::OnTimeoutTimer); |
| } |
| |
| void ServiceWorkerVersion::StopTimeoutTimer() { |
| timeout_timer_.Stop(); |
| } |
| |
| void ServiceWorkerVersion::OnTimeoutTimer() { |
| DCHECK(running_status() == STARTING || running_status() == RUNNING || |
| running_status() == STOPPING) |
| << running_status(); |
| |
| // Starting a worker hasn't finished within a certain period. |
| if (GetTickDuration(start_time_) > |
| base::TimeDelta::FromMinutes(kStartWorkerTimeoutMinutes)) { |
| DCHECK(running_status() == STARTING || running_status() == STOPPING) |
| << running_status(); |
| scoped_refptr<ServiceWorkerVersion> protect(this); |
| RunCallbacks(this, &start_callbacks_, SERVICE_WORKER_ERROR_TIMEOUT); |
| if (running_status() == STARTING) |
| embedded_worker_->Stop(); |
| return; |
| } |
| |
| // This check occurs after the start_time_ timeout check, since in that case |
| // the start callbacks should fail with ERROR_TIMEOUT. In the other timeout |
| // checks, there's nothing more to do as the worker is already stopping. |
| if (running_status() == STOPPING) |
| return; |
| |
| // The worker has been idle for longer than a certain period. |
| if (GetTickDuration(idle_time_) > |
| base::TimeDelta::FromSeconds(kIdleWorkerTimeoutSeconds)) { |
| StopWorkerIfIdle(); |
| return; |
| } |
| |
| // The worker hasn't responded to ping within a certain period. |
| if (GetTickDuration(ping_time_) > |
| base::TimeDelta::FromSeconds(kPingTimeoutSeconds)) { |
| OnPingTimeout(); |
| return; |
| } |
| |
| if (ping_state_ == PINGING && ping_time_.is_null()) |
| PingWorker(); |
| } |
| |
| void ServiceWorkerVersion::PingWorker() { |
| DCHECK(running_status() == STARTING || running_status() == RUNNING); |
| DCHECK_EQ(PINGING, ping_state_); |
| ServiceWorkerStatusCode status = |
| embedded_worker_->SendMessage(ServiceWorkerMsg_Ping()); |
| if (status != SERVICE_WORKER_OK) { |
| // TODO(falken): Maybe try resending Ping a few times first? |
| ping_state_ = PING_TIMED_OUT; |
| StopWorkerIfIdle(); |
| return; |
| } |
| RestartTick(&ping_time_); |
| } |
| |
| void ServiceWorkerVersion::OnPingTimeout() { |
| DCHECK(running_status() == STARTING || running_status() == RUNNING); |
| ping_state_ = PING_TIMED_OUT; |
| // TODO(falken): Show a message to the developer that the SW was stopped due |
| // to timeout (crbug.com/457968). Also, change the error code to |
| // SERVICE_WORKER_ERROR_TIMEOUT. |
| StopWorkerIfIdle(); |
| } |
| |
| void ServiceWorkerVersion::StopWorkerIfIdle() { |
| if (HasInflightRequests() && ping_state_ != PING_TIMED_OUT) |
| return; |
| if (running_status() == STOPPED || running_status() == STOPPING || |
| !stop_callbacks_.empty()) { |
| return; |
| } |
| |
| // TODO(falken): We may need to handle StopIfIdle failure and |
| // forcibly fail pending callbacks so no one is stuck waiting |
| // for the worker. |
| embedded_worker_->StopIfIdle(); |
| } |
| |
| bool ServiceWorkerVersion::HasInflightRequests() const { |
| return |
| !activate_callbacks_.IsEmpty() || |
| !install_callbacks_.IsEmpty() || |
| !fetch_callbacks_.IsEmpty() || |
| !sync_callbacks_.IsEmpty() || |
| !notification_click_callbacks_.IsEmpty() || |
| !push_callbacks_.IsEmpty() || |
| !geofencing_callbacks_.IsEmpty() || |
| !cross_origin_connect_callbacks_.IsEmpty() || |
| !streaming_url_request_jobs_.empty(); |
| } |
| |
| void ServiceWorkerVersion::RecordStartWorkerResult( |
| ServiceWorkerStatusCode status) { |
| base::TimeTicks start_time = start_time_; |
| ClearTick(&start_time_); |
| |
| // Failing to start a doomed worker isn't interesting and very common when |
| // update dooms because the script is byte-to-byte identical. |
| if (is_doomed_) |
| return; |
| |
| UMA_HISTOGRAM_ENUMERATION("ServiceWorker.StartWorker.Status", status, |
| SERVICE_WORKER_ERROR_MAX_VALUE); |
| if (status == SERVICE_WORKER_OK && !start_time.is_null() && |
| !skip_recording_startup_time_) { |
| UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.StartWorker.Time", |
| GetTickDuration(start_time)); |
| } |
| |
| if (status != SERVICE_WORKER_ERROR_TIMEOUT) |
| return; |
| EmbeddedWorkerInstance::StartingPhase phase = |
| EmbeddedWorkerInstance::NOT_STARTING; |
| EmbeddedWorkerInstance::Status 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 != EmbeddedWorkerInstance::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); |
| } |
| |
| void ServiceWorkerVersion::DoomInternal() { |
| DCHECK(is_doomed_); |
| DCHECK(!HasControllee()); |
| SetStatus(REDUNDANT); |
| StopWorkerIfIdle(); |
| if (!context_) |
| return; |
| std::vector<ServiceWorkerDatabase::ResourceRecord> resources; |
| script_cache_map_.GetResources(&resources); |
| context_->storage()->PurgeResources(resources); |
| } |
| |
| template <typename IDMAP> |
| void ServiceWorkerVersion::RemoveCallbackAndStopIfDoomed( |
| IDMAP* callbacks, |
| int request_id) { |
| callbacks->Remove(request_id); |
| if (is_doomed_) { |
| // The stop should be already scheduled, but try to stop immediately, in |
| // order to release worker resources soon. |
| StopWorkerIfIdle(); |
| } |
| } |
| |
| } // namespace content |