service_worker_storage.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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_storage.h"
6
7#include <string>
8#include "base/message_loop/message_loop.h"
9#include "content/browser/service_worker/service_worker_context_core.h"
10#include "content/browser/service_worker/service_worker_info.h"
11#include "content/browser/service_worker/service_worker_registration.h"
12#include "content/browser/service_worker/service_worker_utils.h"
13#include "content/browser/service_worker/service_worker_version.h"
14#include "content/public/browser/browser_thread.h"
15#include "webkit/browser/quota/quota_manager_proxy.h"
16
17namespace content {
18
19namespace {
20
21void RunSoon(const base::Closure& closure) {
22  base::MessageLoop::current()->PostTask(FROM_HERE, closure);
23}
24
25void CompleteFindNow(
26    const scoped_refptr<ServiceWorkerRegistration>& registration,
27    ServiceWorkerStatusCode status,
28    const ServiceWorkerStorage::FindRegistrationCallback& callback) {
29  callback.Run(status, registration);
30}
31
32void CompleteFindSoon(
33    const scoped_refptr<ServiceWorkerRegistration>& registration,
34    ServiceWorkerStatusCode status,
35    const ServiceWorkerStorage::FindRegistrationCallback& callback) {
36  RunSoon(base::Bind(callback, status, registration));
37}
38
39const base::FilePath::CharType kServiceWorkerDirectory[] =
40    FILE_PATH_LITERAL("ServiceWorker");
41
42}  // namespace
43
44ServiceWorkerStorage::ServiceWorkerStorage(
45    const base::FilePath& path,
46    base::WeakPtr<ServiceWorkerContextCore> context,
47    quota::QuotaManagerProxy* quota_manager_proxy)
48    : last_registration_id_(0),
49      last_version_id_(0),
50      last_resource_id_(0),
51      simulated_lazy_initted_(false),
52      context_(context),
53      quota_manager_proxy_(quota_manager_proxy) {
54  if (!path.empty())
55    path_ = path.Append(kServiceWorkerDirectory);
56}
57
58ServiceWorkerStorage::~ServiceWorkerStorage() {
59}
60
61void ServiceWorkerStorage::FindRegistrationForPattern(
62    const GURL& scope,
63    const FindRegistrationCallback& callback) {
64  simulated_lazy_initted_ = true;
65  scoped_refptr<ServiceWorkerRegistration> null_registration;
66  if (!context_) {
67    CompleteFindSoon(null_registration, SERVICE_WORKER_ERROR_FAILED, callback);
68    return;
69  }
70
71  scoped_refptr<ServiceWorkerRegistration> installing_registration =
72      FindInstallingRegistrationForPattern(scope);
73  if (installing_registration) {
74    CompleteFindSoon(installing_registration, SERVICE_WORKER_OK, callback);
75    return;
76  }
77
78  // See if there are any registrations for the origin.
79  OriginRegistrationsMap::const_iterator
80      found = stored_registrations_.find(scope.GetOrigin());
81  if (found == stored_registrations_.end()) {
82    CompleteFindSoon(null_registration, SERVICE_WORKER_ERROR_NOT_FOUND,
83                     callback);
84    return;
85  }
86
87  // Find one with a matching scope.
88  for (RegistrationsMap::const_iterator it = found->second.begin();
89       it != found->second.end(); ++it) {
90    if (scope == it->second.scope) {
91      const ServiceWorkerDatabase::RegistrationData* data = &(it->second);
92      scoped_refptr<ServiceWorkerRegistration> registration =
93          context_->GetLiveRegistration(data->registration_id);
94      if (registration) {
95        CompleteFindSoon(registration, SERVICE_WORKER_OK, callback);
96        return;
97      }
98
99      registration = CreateRegistration(data);
100      CompleteFindSoon(registration, SERVICE_WORKER_OK, callback);
101      return;
102    }
103  }
104
105  CompleteFindSoon(null_registration, SERVICE_WORKER_ERROR_NOT_FOUND, callback);
106}
107
108void ServiceWorkerStorage::FindRegistrationForDocument(
109    const GURL& document_url,
110    const FindRegistrationCallback& callback) {
111  simulated_lazy_initted_ = true;
112  scoped_refptr<ServiceWorkerRegistration> null_registration;
113  if (!context_) {
114    CompleteFindNow(null_registration, SERVICE_WORKER_ERROR_FAILED, callback);
115    return;
116  }
117
118  // See if there are any registrations for the origin.
119  OriginRegistrationsMap::const_iterator
120      found = stored_registrations_.find(document_url.GetOrigin());
121  if (found == stored_registrations_.end()) {
122    // Look for something currently being installed.
123    scoped_refptr<ServiceWorkerRegistration> installing_registration =
124        FindInstallingRegistrationForDocument(document_url);
125    if (installing_registration) {
126      CompleteFindNow(installing_registration, SERVICE_WORKER_OK, callback);
127      return;
128    }
129
130    // Return syncly to simulate this class having an in memory map of
131    // origins with registrations.
132    CompleteFindNow(null_registration, SERVICE_WORKER_ERROR_NOT_FOUND,
133                    callback);
134    return;
135  }
136
137  // Find one with a pattern match.
138  for (RegistrationsMap::const_iterator it = found->second.begin();
139       it != found->second.end(); ++it) {
140    // TODO(michaeln): if there are multiple matches the one with
141    // the longest scope should win.
142    if (ServiceWorkerUtils::ScopeMatches(it->second.scope, document_url)) {
143      const ServiceWorkerDatabase::RegistrationData* data = &(it->second);
144
145      // If its in the live map, return syncly to simulate this class having
146      // iterated over the values in that map instead of reading the db.
147      scoped_refptr<ServiceWorkerRegistration> registration =
148          context_->GetLiveRegistration(data->registration_id);
149      if (registration) {
150        CompleteFindNow(registration, SERVICE_WORKER_OK, callback);
151        return;
152      }
153
154      // If we have to create a new instance, return it asyncly to simulate
155      // having had to retreive the RegistrationData from the db.
156      registration = CreateRegistration(data);
157      CompleteFindSoon(registration, SERVICE_WORKER_OK, callback);
158      return;
159    }
160  }
161
162  // Look for something currently being installed.
163  // TODO(michaeln): Should be mixed in with the stored registrations
164  // for this test.
165  scoped_refptr<ServiceWorkerRegistration> installing_registration =
166      FindInstallingRegistrationForDocument(document_url);
167  if (installing_registration) {
168    CompleteFindSoon(installing_registration, SERVICE_WORKER_OK, callback);
169    return;
170  }
171
172  // Return asyncly to simulate having had to look in the db since this
173  // origin does have some registations.
174  CompleteFindSoon(installing_registration, SERVICE_WORKER_OK, callback);
175}
176
177void ServiceWorkerStorage::FindRegistrationForId(
178    int64 registration_id,
179    const FindRegistrationCallback& callback) {
180  simulated_lazy_initted_ = true;
181  scoped_refptr<ServiceWorkerRegistration> null_registration;
182  if (!context_) {
183    CompleteFindNow(null_registration, SERVICE_WORKER_ERROR_FAILED, callback);
184    return;
185  }
186  scoped_refptr<ServiceWorkerRegistration> installing_registration =
187      FindInstallingRegistrationForId(registration_id);
188  if (installing_registration) {
189    CompleteFindNow(installing_registration, SERVICE_WORKER_OK, callback);
190    return;
191  }
192  RegistrationPtrMap::const_iterator found =
193      registrations_by_id_.find(registration_id);
194  if (found == registrations_by_id_.end()) {
195    CompleteFindNow(null_registration, SERVICE_WORKER_ERROR_NOT_FOUND,
196                    callback);
197    return;
198  }
199  scoped_refptr<ServiceWorkerRegistration> registration =
200      context_->GetLiveRegistration(registration_id);
201  if (registration) {
202    CompleteFindNow(registration, SERVICE_WORKER_OK, callback);
203    return;
204  }
205  registration = CreateRegistration(found->second);
206  CompleteFindSoon(registration, SERVICE_WORKER_OK, callback);
207}
208
209void ServiceWorkerStorage::GetAllRegistrations(
210    const GetAllRegistrationInfosCallback& callback) {
211  simulated_lazy_initted_ = true;
212  std::vector<ServiceWorkerRegistrationInfo> registrations;
213  if (!context_) {
214    RunSoon(base::Bind(callback, registrations));
215    return;
216  }
217
218  // Add all stored registrations.
219  for (RegistrationPtrMap::const_iterator it = registrations_by_id_.begin();
220       it != registrations_by_id_.end(); ++it) {
221    ServiceWorkerRegistration* registration =
222        context_->GetLiveRegistration(it->first);
223    if (registration) {
224      registrations.push_back(registration->GetInfo());
225      continue;
226    }
227    ServiceWorkerRegistrationInfo info;
228    info.pattern = it->second->scope;
229    info.script_url = it->second->script;
230    info.active_version.is_null = false;
231    if (it->second->is_active)
232      info.active_version.status = ServiceWorkerVersion::ACTIVE;
233    else
234      info.active_version.status = ServiceWorkerVersion::INSTALLED;
235    registrations.push_back(info);
236  }
237
238  // Add unstored registrations that are being installed.
239  for (RegistrationRefsById::const_iterator it =
240           installing_registrations_.begin();
241       it != installing_registrations_.end(); ++it) {
242    if (registrations_by_id_.find(it->first) == registrations_by_id_.end())
243      registrations.push_back(it->second->GetInfo());
244  }
245
246  RunSoon(base::Bind(callback, registrations));
247}
248
249void ServiceWorkerStorage::StoreRegistration(
250    ServiceWorkerRegistration* registration,
251    ServiceWorkerVersion* version,
252    const StatusCallback& callback) {
253  DCHECK(registration);
254  DCHECK(version);
255  DCHECK(simulated_lazy_initted_);
256  if (!context_) {
257    RunSoon(base::Bind(callback, SERVICE_WORKER_ERROR_FAILED));
258    return;
259  }
260
261  // Keep a database struct in the storage map.
262  RegistrationsMap& storage_map =
263      stored_registrations_[registration->script_url().GetOrigin()];
264  ServiceWorkerDatabase::RegistrationData& data =
265      storage_map[registration->id()];
266  data.registration_id = registration->id();
267  data.scope = registration->pattern();
268  data.script = registration->script_url();
269  data.has_fetch_handler = true;
270  data.version_id = version->version_id();
271  data.last_update_check = base::Time::Now();
272  data.is_active = false;  // initially stored in the waiting state
273
274  // Keep a seperate map of ptrs keyed by id only.
275  registrations_by_id_[registration->id()] = &storage_map[registration->id()];
276
277  RunSoon(base::Bind(callback, SERVICE_WORKER_OK));
278}
279
280 void ServiceWorkerStorage::UpdateToActiveState(
281      ServiceWorkerRegistration* registration,
282      const StatusCallback& callback) {
283  DCHECK(simulated_lazy_initted_);
284  if (!context_) {
285    RunSoon(base::Bind(callback, SERVICE_WORKER_ERROR_FAILED));
286    return;
287  }
288
289  RegistrationPtrMap::const_iterator
290       found = registrations_by_id_.find(registration->id());
291  if (found == registrations_by_id_.end()) {
292    RunSoon(base::Bind(callback, SERVICE_WORKER_ERROR_NOT_FOUND));
293    return;
294  }
295  DCHECK(!found->second->is_active);
296  found->second->is_active = true;
297  RunSoon(base::Bind(callback, SERVICE_WORKER_OK));
298}
299
300void ServiceWorkerStorage::DeleteRegistration(
301    int64 registration_id,
302    const StatusCallback& callback) {
303  DCHECK(simulated_lazy_initted_);
304  RegistrationPtrMap::iterator
305      found = registrations_by_id_.find(registration_id);
306  if (found == registrations_by_id_.end()) {
307    RunSoon(base::Bind(callback, SERVICE_WORKER_ERROR_NOT_FOUND));
308    return;
309  }
310
311  GURL origin = found->second->script.GetOrigin();
312  stored_registrations_[origin].erase(registration_id);
313  if (stored_registrations_[origin].empty())
314    stored_registrations_.erase(origin);
315
316  registrations_by_id_.erase(found);
317
318  RunSoon(base::Bind(callback, SERVICE_WORKER_OK));
319  // TODO(michaeln): Either its instance should also be
320  // removed from liveregistrations map or the live object
321  // should marked as deleted in some way and not 'findable'
322  // thereafter.
323}
324
325int64 ServiceWorkerStorage::NewRegistrationId() {
326  DCHECK(simulated_lazy_initted_);
327  return ++last_registration_id_;
328}
329
330int64 ServiceWorkerStorage::NewVersionId() {
331  DCHECK(simulated_lazy_initted_);
332  return ++last_version_id_;
333}
334
335int64 ServiceWorkerStorage::NewResourceId() {
336  DCHECK(simulated_lazy_initted_);
337  return ++last_resource_id_;
338}
339
340void ServiceWorkerStorage::NotifyInstallingRegistration(
341      ServiceWorkerRegistration* registration) {
342  installing_registrations_[registration->id()] = registration;
343}
344
345void ServiceWorkerStorage::NotifyDoneInstallingRegistration(
346      ServiceWorkerRegistration* registration) {
347  installing_registrations_.erase(registration->id());
348}
349
350scoped_refptr<ServiceWorkerRegistration>
351ServiceWorkerStorage::CreateRegistration(
352    const ServiceWorkerDatabase::RegistrationData* data) {
353  scoped_refptr<ServiceWorkerRegistration> registration(
354      new ServiceWorkerRegistration(
355          data->scope, data->script, data->registration_id, context_));
356
357  scoped_refptr<ServiceWorkerVersion> version =
358      context_->GetLiveVersion(data->version_id);
359  if (!version) {
360    version = new ServiceWorkerVersion(
361        registration, data->version_id, context_);
362    version->SetStatus(data->GetVersionStatus());
363  }
364
365  if (version->status() == ServiceWorkerVersion::ACTIVE)
366    registration->set_active_version(version);
367  else if (version->status() == ServiceWorkerVersion::INSTALLED)
368    registration->set_pending_version(version);
369  else
370    NOTREACHED();
371  // TODO(michaeln): Hmmm, what if DeleteReg was invoked after
372  // the Find result we're returning here? NOTREACHED condition?
373
374  return registration;
375}
376
377ServiceWorkerRegistration*
378ServiceWorkerStorage::FindInstallingRegistrationForDocument(
379    const GURL& document_url) {
380  // TODO(michaeln): if there are multiple matches the one with
381  // the longest scope should win, and these should on equal footing
382  // with the stored registrations in FindRegistrationForDocument().
383  for (RegistrationRefsById::const_iterator it =
384           installing_registrations_.begin();
385       it != installing_registrations_.end(); ++it) {
386    if (ServiceWorkerUtils::ScopeMatches(
387            it->second->pattern(), document_url)) {
388      return it->second;
389    }
390  }
391  return NULL;
392}
393
394ServiceWorkerRegistration*
395ServiceWorkerStorage::FindInstallingRegistrationForPattern(
396    const GURL& scope) {
397  for (RegistrationRefsById::const_iterator it =
398           installing_registrations_.begin();
399       it != installing_registrations_.end(); ++it) {
400    if (it->second->pattern() == scope)
401      return it->second;
402  }
403  return NULL;
404}
405
406ServiceWorkerRegistration*
407ServiceWorkerStorage::FindInstallingRegistrationForId(
408    int64 registration_id) {
409  RegistrationRefsById::const_iterator found =
410      installing_registrations_.find(registration_id);
411  if (found == installing_registrations_.end())
412    return NULL;
413  return found->second;
414}
415
416}  // namespace content
417