service_worker_version.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/service_worker/service_worker_version.h"
6
7#include "base/command_line.h"
8#include "base/stl_util.h"
9#include "base/strings/string16.h"
10#include "content/browser/service_worker/embedded_worker_instance.h"
11#include "content/browser/service_worker/embedded_worker_registry.h"
12#include "content/browser/service_worker/service_worker_context_core.h"
13#include "content/browser/service_worker/service_worker_registration.h"
14#include "content/browser/service_worker/service_worker_utils.h"
15#include "content/common/service_worker/service_worker_messages.h"
16#include "content/public/browser/browser_thread.h"
17#include "content/public/common/content_switches.h"
18
19namespace content {
20
21typedef ServiceWorkerVersion::StatusCallback StatusCallback;
22typedef ServiceWorkerVersion::MessageCallback MessageCallback;
23
24namespace {
25
26// Default delay to stop the worker context after all documents that
27// are associated to the worker are closed.
28// (Note that if all references to the version is dropped the worker
29// is also stopped without delay)
30const int64 kStopWorkerDelay = 30;  // 30 secs.
31
32void RunSoon(const base::Closure& callback) {
33  if (!callback.is_null())
34    base::MessageLoop::current()->PostTask(FROM_HERE, callback);
35}
36
37template <typename CallbackArray, typename Arg>
38void RunCallbacks(ServiceWorkerVersion* version,
39                  CallbackArray* callbacks_ptr,
40                  const Arg& arg) {
41  CallbackArray callbacks;
42  callbacks.swap(*callbacks_ptr);
43  scoped_refptr<ServiceWorkerVersion> protect(version);
44  for (typename CallbackArray::const_iterator i = callbacks.begin();
45       i != callbacks.end(); ++i)
46    (*i).Run(arg);
47}
48
49template <typename IDMAP, typename Method, typename Params>
50void RunIDMapCallbacks(IDMAP* callbacks, Method method, const Params& params) {
51  typename IDMAP::iterator iter(callbacks);
52  while (!iter.IsAtEnd()) {
53    DispatchToMethod(iter.GetCurrentValue(), method, params);
54    iter.Advance();
55  }
56  callbacks->Clear();
57}
58
59// A callback adapter to start a |task| after StartWorker.
60void RunTaskAfterStartWorker(
61    base::WeakPtr<ServiceWorkerVersion> version,
62    const StatusCallback& error_callback,
63    const base::Closure& task,
64    ServiceWorkerStatusCode status) {
65  if (status != SERVICE_WORKER_OK) {
66    if (!error_callback.is_null())
67      error_callback.Run(status);
68    return;
69  }
70  if (version->running_status() != ServiceWorkerVersion::RUNNING) {
71    // We've tried to start the worker (and it has succeeded), but
72    // it looks it's not running yet.
73    NOTREACHED() << "The worker's not running after successful StartWorker";
74    if (!error_callback.is_null())
75      error_callback.Run(SERVICE_WORKER_ERROR_START_WORKER_FAILED);
76    return;
77  }
78  task.Run();
79}
80
81void RunErrorFetchCallback(const ServiceWorkerVersion::FetchCallback& callback,
82                           ServiceWorkerStatusCode status) {
83  callback.Run(status,
84               SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK,
85               ServiceWorkerResponse());
86}
87
88}  // namespace
89
90ServiceWorkerVersion::ServiceWorkerVersion(
91    ServiceWorkerRegistration* registration,
92    int64 version_id,
93    base::WeakPtr<ServiceWorkerContextCore> context)
94    : version_id_(version_id),
95      registration_id_(kInvalidServiceWorkerVersionId),
96      status_(NEW),
97      context_(context),
98      script_cache_map_(this, context),
99      weak_factory_(this) {
100  DCHECK(context_);
101  DCHECK(registration);
102  if (registration) {
103    registration_id_ = registration->id();
104    script_url_ = registration->script_url();
105    scope_ = registration->pattern();
106  }
107  context_->AddLiveVersion(this);
108  embedded_worker_ = context_->embedded_worker_registry()->CreateWorker();
109  embedded_worker_->AddListener(this);
110}
111
112ServiceWorkerVersion::~ServiceWorkerVersion() {
113  embedded_worker_->RemoveListener(this);
114  if (context_)
115    context_->RemoveLiveVersion(version_id_);
116  // EmbeddedWorker's dtor sends StopWorker if it's still running.
117}
118
119void ServiceWorkerVersion::SetStatus(Status status) {
120  if (status_ == status)
121    return;
122
123  status_ = status;
124
125  std::vector<base::Closure> callbacks;
126  callbacks.swap(status_change_callbacks_);
127  for (std::vector<base::Closure>::const_iterator i = callbacks.begin();
128       i != callbacks.end(); ++i) {
129    (*i).Run();
130  }
131
132  FOR_EACH_OBSERVER(Listener, listeners_, OnVersionStateChanged(this));
133}
134
135void ServiceWorkerVersion::RegisterStatusChangeCallback(
136    const base::Closure& callback) {
137  status_change_callbacks_.push_back(callback);
138}
139
140ServiceWorkerVersionInfo ServiceWorkerVersion::GetInfo() {
141  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
142  return ServiceWorkerVersionInfo(
143      running_status(),
144      status(),
145      version_id(),
146      embedded_worker()->process_id(),
147      embedded_worker()->thread_id(),
148      embedded_worker()->worker_devtools_agent_route_id());
149}
150
151void ServiceWorkerVersion::StartWorker(const StatusCallback& callback) {
152  StartWorkerWithCandidateProcesses(std::vector<int>(), callback);
153}
154
155void ServiceWorkerVersion::StartWorkerWithCandidateProcesses(
156    const std::vector<int>& possible_process_ids,
157    const StatusCallback& callback) {
158  switch (running_status()) {
159    case RUNNING:
160      RunSoon(base::Bind(callback, SERVICE_WORKER_OK));
161      return;
162    case STOPPING:
163      RunSoon(base::Bind(callback, SERVICE_WORKER_ERROR_START_WORKER_FAILED));
164      return;
165    case STOPPED:
166    case STARTING:
167      start_callbacks_.push_back(callback);
168      if (running_status() == STOPPED) {
169        embedded_worker_->Start(
170            version_id_,
171            scope_,
172            script_url_,
173            possible_process_ids,
174            base::Bind(&ServiceWorkerVersion::RunStartWorkerCallbacksOnError,
175                       weak_factory_.GetWeakPtr()));
176      }
177      return;
178  }
179}
180
181void ServiceWorkerVersion::StopWorker(const StatusCallback& callback) {
182  if (running_status() == STOPPED) {
183    RunSoon(base::Bind(callback, SERVICE_WORKER_OK));
184    return;
185  }
186  if (stop_callbacks_.empty()) {
187    ServiceWorkerStatusCode status = embedded_worker_->Stop();
188    if (status != SERVICE_WORKER_OK) {
189      RunSoon(base::Bind(callback, status));
190      return;
191    }
192  }
193  stop_callbacks_.push_back(callback);
194}
195
196void ServiceWorkerVersion::SendMessage(
197    const IPC::Message& message, const StatusCallback& callback) {
198  if (running_status() != RUNNING) {
199    // Schedule calling this method after starting the worker.
200    StartWorker(base::Bind(&RunTaskAfterStartWorker,
201                           weak_factory_.GetWeakPtr(), callback,
202                           base::Bind(&self::SendMessage,
203                                      weak_factory_.GetWeakPtr(),
204                                      message, callback)));
205    return;
206  }
207
208  ServiceWorkerStatusCode status = embedded_worker_->SendMessage(message);
209  RunSoon(base::Bind(callback, status));
210}
211
212void ServiceWorkerVersion::DispatchInstallEvent(
213    int active_version_id,
214    const StatusCallback& callback) {
215  DCHECK_EQ(NEW, status()) << status();
216  SetStatus(INSTALLING);
217
218  if (running_status() != RUNNING) {
219    // Schedule calling this method after starting the worker.
220    StartWorker(
221        base::Bind(&RunTaskAfterStartWorker,
222                   weak_factory_.GetWeakPtr(),
223                   callback,
224                   base::Bind(&self::DispatchInstallEventAfterStartWorker,
225                              weak_factory_.GetWeakPtr(),
226                              active_version_id,
227                              callback)));
228  } else {
229    DispatchInstallEventAfterStartWorker(active_version_id, callback);
230  }
231}
232
233void ServiceWorkerVersion::DispatchActivateEvent(
234    const StatusCallback& callback) {
235  DCHECK_EQ(INSTALLED, status()) << status();
236  SetStatus(ACTIVATING);
237
238  if (running_status() != RUNNING) {
239    // Schedule calling this method after starting the worker.
240    StartWorker(
241        base::Bind(&RunTaskAfterStartWorker,
242                   weak_factory_.GetWeakPtr(),
243                   callback,
244                   base::Bind(&self::DispatchActivateEventAfterStartWorker,
245                              weak_factory_.GetWeakPtr(),
246                              callback)));
247  } else {
248    DispatchActivateEventAfterStartWorker(callback);
249  }
250}
251
252void ServiceWorkerVersion::DispatchFetchEvent(
253    const ServiceWorkerFetchRequest& request,
254    const FetchCallback& callback) {
255  DCHECK_EQ(ACTIVE, status()) << status();
256
257  if (running_status() != RUNNING) {
258    // Schedule calling this method after starting the worker.
259    StartWorker(base::Bind(&RunTaskAfterStartWorker,
260                           weak_factory_.GetWeakPtr(),
261                           base::Bind(&RunErrorFetchCallback, callback),
262                           base::Bind(&self::DispatchFetchEvent,
263                                      weak_factory_.GetWeakPtr(),
264                                      request, callback)));
265    return;
266  }
267
268  int request_id = fetch_callbacks_.Add(new FetchCallback(callback));
269  ServiceWorkerStatusCode status = embedded_worker_->SendMessage(
270      ServiceWorkerMsg_FetchEvent(request_id, request));
271  if (status != SERVICE_WORKER_OK) {
272    fetch_callbacks_.Remove(request_id);
273    RunSoon(base::Bind(&RunErrorFetchCallback,
274                       callback,
275                       SERVICE_WORKER_ERROR_FAILED));
276  }
277}
278
279void ServiceWorkerVersion::DispatchSyncEvent(const StatusCallback& callback) {
280  DCHECK_EQ(ACTIVE, status()) << status();
281
282  if (!CommandLine::ForCurrentProcess()->HasSwitch(
283          switches::kEnableServiceWorkerSync)) {
284    callback.Run(SERVICE_WORKER_ERROR_ABORT);
285    return;
286  }
287
288  if (running_status() != RUNNING) {
289    // Schedule calling this method after starting the worker.
290    StartWorker(base::Bind(&RunTaskAfterStartWorker,
291                           weak_factory_.GetWeakPtr(), callback,
292                           base::Bind(&self::DispatchSyncEvent,
293                                      weak_factory_.GetWeakPtr(),
294                                      callback)));
295    return;
296  }
297
298  int request_id = sync_callbacks_.Add(new StatusCallback(callback));
299  ServiceWorkerStatusCode status = embedded_worker_->SendMessage(
300      ServiceWorkerMsg_SyncEvent(request_id));
301  if (status != SERVICE_WORKER_OK) {
302    sync_callbacks_.Remove(request_id);
303    RunSoon(base::Bind(callback, status));
304  }
305}
306
307void ServiceWorkerVersion::DispatchPushEvent(const StatusCallback& callback,
308                                             const std::string& data) {
309  DCHECK_EQ(ACTIVE, status()) << status();
310
311  if (!CommandLine::ForCurrentProcess()->HasSwitch(
312          switches::kEnableExperimentalWebPlatformFeatures)) {
313    callback.Run(SERVICE_WORKER_ERROR_ABORT);
314    return;
315  }
316
317  if (running_status() != RUNNING) {
318    // Schedule calling this method after starting the worker.
319    StartWorker(base::Bind(&RunTaskAfterStartWorker,
320                           weak_factory_.GetWeakPtr(), callback,
321                           base::Bind(&self::DispatchPushEvent,
322                                      weak_factory_.GetWeakPtr(),
323                                      callback, data)));
324    return;
325  }
326
327  int request_id = push_callbacks_.Add(new StatusCallback(callback));
328  ServiceWorkerStatusCode status = embedded_worker_->SendMessage(
329      ServiceWorkerMsg_PushEvent(request_id, data));
330  if (status != SERVICE_WORKER_OK) {
331    push_callbacks_.Remove(request_id);
332    RunSoon(base::Bind(callback, status));
333  }
334}
335
336void ServiceWorkerVersion::AddProcessToWorker(int process_id) {
337  embedded_worker_->AddProcessReference(process_id);
338}
339
340void ServiceWorkerVersion::RemoveProcessFromWorker(int process_id) {
341  embedded_worker_->ReleaseProcessReference(process_id);
342}
343
344bool ServiceWorkerVersion::HasProcessToRun() const {
345  return embedded_worker_->HasProcessToRun();
346}
347
348void ServiceWorkerVersion::AddControllee(
349    ServiceWorkerProviderHost* provider_host) {
350  DCHECK(!ContainsKey(controllee_map_, provider_host));
351  int controllee_id = controllee_by_id_.Add(provider_host);
352  controllee_map_[provider_host] = controllee_id;
353  AddProcessToWorker(provider_host->process_id());
354  if (stop_worker_timer_.IsRunning())
355    stop_worker_timer_.Stop();
356}
357
358void ServiceWorkerVersion::RemoveControllee(
359    ServiceWorkerProviderHost* provider_host) {
360  ControlleeMap::iterator found = controllee_map_.find(provider_host);
361  DCHECK(found != controllee_map_.end());
362  controllee_by_id_.Remove(found->second);
363  controllee_map_.erase(found);
364  RemoveProcessFromWorker(provider_host->process_id());
365  if (!HasControllee())
366    ScheduleStopWorker();
367  // TODO(kinuko): Fire NoControllees notification when the # of controllees
368  // reaches 0, so that a new pending version can be activated (which will
369  // deactivate this version).
370  // TODO(michaeln): On no controllees call storage DeleteVersionResources
371  // if this version has been deactivated. Probably storage can listen for
372  // NoControllees for versions that have been deleted.
373}
374
375void ServiceWorkerVersion::AddWaitingControllee(
376    ServiceWorkerProviderHost* provider_host) {
377  AddProcessToWorker(provider_host->process_id());
378}
379
380void ServiceWorkerVersion::RemoveWaitingControllee(
381    ServiceWorkerProviderHost* provider_host) {
382  RemoveProcessFromWorker(provider_host->process_id());
383}
384
385void ServiceWorkerVersion::AddListener(Listener* listener) {
386  listeners_.AddObserver(listener);
387}
388
389void ServiceWorkerVersion::RemoveListener(Listener* listener) {
390  listeners_.RemoveObserver(listener);
391}
392
393void ServiceWorkerVersion::OnStarted() {
394  DCHECK_EQ(RUNNING, running_status());
395  // Fire all start callbacks.
396  RunCallbacks(this, &start_callbacks_, SERVICE_WORKER_OK);
397  FOR_EACH_OBSERVER(Listener, listeners_, OnWorkerStarted(this));
398}
399
400void ServiceWorkerVersion::OnStopped() {
401  DCHECK_EQ(STOPPED, running_status());
402  scoped_refptr<ServiceWorkerVersion> protect(this);
403
404  // Fire all stop callbacks.
405  RunCallbacks(this, &stop_callbacks_, SERVICE_WORKER_OK);
406
407  // Let all start callbacks fail.
408  RunCallbacks(
409      this, &start_callbacks_, SERVICE_WORKER_ERROR_START_WORKER_FAILED);
410
411  // Let all message callbacks fail (this will also fire and clear all
412  // callbacks for events).
413  // TODO(kinuko): Consider if we want to add queue+resend mechanism here.
414  RunIDMapCallbacks(&activate_callbacks_,
415                    &StatusCallback::Run,
416                    MakeTuple(SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED));
417  RunIDMapCallbacks(&install_callbacks_,
418                    &StatusCallback::Run,
419                    MakeTuple(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED));
420  RunIDMapCallbacks(&fetch_callbacks_,
421                    &FetchCallback::Run,
422                    MakeTuple(SERVICE_WORKER_ERROR_FAILED,
423                              SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK,
424                              ServiceWorkerResponse()));
425  RunIDMapCallbacks(&sync_callbacks_,
426                    &StatusCallback::Run,
427                    MakeTuple(SERVICE_WORKER_ERROR_FAILED));
428  RunIDMapCallbacks(&push_callbacks_,
429                    &StatusCallback::Run,
430                    MakeTuple(SERVICE_WORKER_ERROR_FAILED));
431
432  FOR_EACH_OBSERVER(Listener, listeners_, OnWorkerStopped(this));
433}
434
435void ServiceWorkerVersion::OnReportException(
436    const base::string16& error_message,
437    int line_number,
438    int column_number,
439    const GURL& source_url) {
440  FOR_EACH_OBSERVER(
441      Listener,
442      listeners_,
443      OnErrorReported(
444          this, error_message, line_number, column_number, source_url));
445}
446
447void ServiceWorkerVersion::OnReportConsoleMessage(int source_identifier,
448                                                  int message_level,
449                                                  const base::string16& message,
450                                                  int line_number,
451                                                  const GURL& source_url) {
452  FOR_EACH_OBSERVER(Listener,
453                    listeners_,
454                    OnReportConsoleMessage(this,
455                                           source_identifier,
456                                           message_level,
457                                           message,
458                                           line_number,
459                                           source_url));
460}
461
462bool ServiceWorkerVersion::OnMessageReceived(const IPC::Message& message) {
463  bool handled = true;
464  IPC_BEGIN_MESSAGE_MAP(ServiceWorkerVersion, message)
465    IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_GetClientDocuments,
466                        OnGetClientDocuments)
467    IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_ActivateEventFinished,
468                        OnActivateEventFinished)
469    IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_InstallEventFinished,
470                        OnInstallEventFinished)
471    IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FetchEventFinished,
472                        OnFetchEventFinished)
473    IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SyncEventFinished,
474                        OnSyncEventFinished)
475    IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PushEventFinished,
476                        OnPushEventFinished)
477    IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PostMessageToDocument,
478                        OnPostMessageToDocument)
479    IPC_MESSAGE_UNHANDLED(handled = false)
480  IPC_END_MESSAGE_MAP()
481  return handled;
482}
483
484void ServiceWorkerVersion::RunStartWorkerCallbacksOnError(
485    ServiceWorkerStatusCode status) {
486  if (status != SERVICE_WORKER_OK)
487    RunCallbacks(this, &start_callbacks_, status);
488}
489
490void ServiceWorkerVersion::DispatchInstallEventAfterStartWorker(
491    int active_version_id,
492    const StatusCallback& callback) {
493  DCHECK_EQ(RUNNING, running_status())
494      << "Worker stopped too soon after it was started.";
495  int request_id = install_callbacks_.Add(new StatusCallback(callback));
496  ServiceWorkerStatusCode status = embedded_worker_->SendMessage(
497      ServiceWorkerMsg_InstallEvent(request_id, active_version_id));
498  if (status != SERVICE_WORKER_OK) {
499    install_callbacks_.Remove(request_id);
500    RunSoon(base::Bind(callback, status));
501  }
502}
503
504void ServiceWorkerVersion::DispatchActivateEventAfterStartWorker(
505    const StatusCallback& callback) {
506  DCHECK_EQ(RUNNING, running_status())
507      << "Worker stopped too soon after it was started.";
508  int request_id = activate_callbacks_.Add(new StatusCallback(callback));
509  ServiceWorkerStatusCode status =
510      embedded_worker_->SendMessage(ServiceWorkerMsg_ActivateEvent(request_id));
511  if (status != SERVICE_WORKER_OK) {
512    activate_callbacks_.Remove(request_id);
513    RunSoon(base::Bind(callback, status));
514  }
515}
516
517void ServiceWorkerVersion::OnGetClientDocuments(int request_id) {
518  std::vector<int> client_ids;
519  ControlleeByIDMap::iterator it(&controllee_by_id_);
520  while (!it.IsAtEnd()) {
521    client_ids.push_back(it.GetCurrentKey());
522    it.Advance();
523  }
524  // Don't bother if it's no longer running.
525  if (running_status() == RUNNING) {
526    embedded_worker_->SendMessage(
527        ServiceWorkerMsg_DidGetClientDocuments(request_id, client_ids));
528  }
529}
530
531void ServiceWorkerVersion::OnActivateEventFinished(
532    int request_id,
533    blink::WebServiceWorkerEventResult result) {
534  StatusCallback* callback = activate_callbacks_.Lookup(request_id);
535  if (!callback) {
536    NOTREACHED() << "Got unexpected message: " << request_id;
537    return;
538  }
539  ServiceWorkerStatusCode status = SERVICE_WORKER_OK;
540  if (result == blink::WebServiceWorkerEventResultRejected)
541    status = SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED;
542  else
543    SetStatus(ACTIVE);
544
545  scoped_refptr<ServiceWorkerVersion> protect(this);
546  callback->Run(status);
547  activate_callbacks_.Remove(request_id);
548}
549
550void ServiceWorkerVersion::OnInstallEventFinished(
551    int request_id,
552    blink::WebServiceWorkerEventResult result) {
553  StatusCallback* callback = install_callbacks_.Lookup(request_id);
554  if (!callback) {
555    NOTREACHED() << "Got unexpected message: " << request_id;
556    return;
557  }
558  ServiceWorkerStatusCode status = SERVICE_WORKER_OK;
559  if (result == blink::WebServiceWorkerEventResultRejected)
560    status = SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED;
561  else
562    SetStatus(INSTALLED);
563
564  scoped_refptr<ServiceWorkerVersion> protect(this);
565  callback->Run(status);
566  install_callbacks_.Remove(request_id);
567}
568
569void ServiceWorkerVersion::OnFetchEventFinished(
570    int request_id,
571    ServiceWorkerFetchEventResult result,
572    const ServiceWorkerResponse& response) {
573  FetchCallback* callback = fetch_callbacks_.Lookup(request_id);
574  if (!callback) {
575    NOTREACHED() << "Got unexpected message: " << request_id;
576    return;
577  }
578
579  scoped_refptr<ServiceWorkerVersion> protect(this);
580  callback->Run(SERVICE_WORKER_OK, result, response);
581  fetch_callbacks_.Remove(request_id);
582}
583
584void ServiceWorkerVersion::OnSyncEventFinished(
585    int request_id) {
586  StatusCallback* callback = sync_callbacks_.Lookup(request_id);
587  if (!callback) {
588    NOTREACHED() << "Got unexpected message: " << request_id;
589    return;
590  }
591
592  scoped_refptr<ServiceWorkerVersion> protect(this);
593  callback->Run(SERVICE_WORKER_OK);
594  sync_callbacks_.Remove(request_id);
595}
596
597void ServiceWorkerVersion::OnPushEventFinished(
598    int request_id) {
599  StatusCallback* callback = push_callbacks_.Lookup(request_id);
600  if (!callback) {
601    NOTREACHED() << "Got unexpected message: " << request_id;
602    return;
603  }
604
605  scoped_refptr<ServiceWorkerVersion> protect(this);
606  callback->Run(SERVICE_WORKER_OK);
607  push_callbacks_.Remove(request_id);
608}
609
610void ServiceWorkerVersion::OnPostMessageToDocument(
611    int client_id,
612    const base::string16& message,
613    const std::vector<int>& sent_message_port_ids) {
614  ServiceWorkerProviderHost* provider_host =
615      controllee_by_id_.Lookup(client_id);
616  if (!provider_host) {
617    // The client may already have been closed, just ignore.
618    return;
619  }
620  provider_host->PostMessage(message, sent_message_port_ids);
621}
622
623void ServiceWorkerVersion::ScheduleStopWorker() {
624  if (running_status() != RUNNING)
625    return;
626  if (stop_worker_timer_.IsRunning()) {
627    stop_worker_timer_.Reset();
628    return;
629  }
630  stop_worker_timer_.Start(
631      FROM_HERE, base::TimeDelta::FromSeconds(kStopWorkerDelay),
632      base::Bind(&ServiceWorkerVersion::StopWorker,
633                 weak_factory_.GetWeakPtr(),
634                 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)));
635}
636
637}  // namespace content
638