Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / geofencing / geofencing_manager.cc
blob96f55ecd3d0ae70fa75bdc430b3df8b9bccbf005
1 // Copyright 2014 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.
5 #include "content/browser/geofencing/geofencing_manager.h"
7 #include <algorithm>
9 #include "base/callback.h"
10 #include "content/browser/geofencing/geofencing_service.h"
11 #include "content/browser/geofencing/mock_geofencing_service.h"
12 #include "content/browser/service_worker/service_worker_context_wrapper.h"
13 #include "content/public/browser/browser_context.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "content/public/browser/storage_partition.h"
16 #include "third_party/WebKit/public/platform/WebCircularGeofencingRegion.h"
17 #include "third_party/WebKit/public/platform/WebGeofencingEventType.h"
18 #include "url/gurl.h"
20 namespace content {
22 struct GeofencingManager::Registration {
23 Registration(int64 service_worker_registration_id,
24 const GURL& service_worker_origin,
25 const std::string& region_id,
26 const blink::WebCircularGeofencingRegion& region,
27 const StatusCallback& callback,
28 int64 geofencing_registration_id);
29 int64 service_worker_registration_id;
30 GURL service_worker_origin;
31 std::string region_id;
32 blink::WebCircularGeofencingRegion region;
34 // Registration ID as returned by the |GeofencingService|.
35 int64 geofencing_registration_id;
37 // Callback to call when registration is completed. This field is reset when
38 // registration is complete.
39 StatusCallback registration_callback;
41 // Returns true if registration has been completed, and thus should be
42 // included in calls to GetRegisteredRegions.
43 bool is_active() const { return registration_callback.is_null(); }
46 GeofencingManager::Registration::Registration(
47 int64 service_worker_registration_id,
48 const GURL& service_worker_origin,
49 const std::string& region_id,
50 const blink::WebCircularGeofencingRegion& region,
51 const GeofencingManager::StatusCallback& callback,
52 int64 geofencing_registration_id)
53 : service_worker_registration_id(service_worker_registration_id),
54 service_worker_origin(service_worker_origin),
55 region_id(region_id),
56 region(region),
57 geofencing_registration_id(geofencing_registration_id),
58 registration_callback(callback) {
61 GeofencingManager::GeofencingManager(
62 const scoped_refptr<ServiceWorkerContextWrapper>& service_worker_context)
63 : service_(nullptr), service_worker_context_(service_worker_context) {
64 DCHECK_CURRENTLY_ON(BrowserThread::UI);
67 GeofencingManager::~GeofencingManager() {
70 void GeofencingManager::Init() {
71 DCHECK_CURRENTLY_ON(BrowserThread::UI);
72 BrowserThread::PostTask(BrowserThread::IO,
73 FROM_HERE,
74 base::Bind(&GeofencingManager::InitOnIO, this));
77 void GeofencingManager::Shutdown() {
78 DCHECK_CURRENTLY_ON(BrowserThread::UI);
79 BrowserThread::PostTask(BrowserThread::IO,
80 FROM_HERE,
81 base::Bind(&GeofencingManager::ShutdownOnIO, this));
84 void GeofencingManager::InitOnIO() {
85 DCHECK_CURRENTLY_ON(BrowserThread::IO);
86 service_worker_context_->AddObserver(this);
87 if (!service_)
88 service_ = GeofencingServiceImpl::GetInstance();
91 void GeofencingManager::ShutdownOnIO() {
92 DCHECK_CURRENTLY_ON(BrowserThread::IO);
93 service_worker_context_->RemoveObserver(this);
94 // Clean up all registrations with the |GeofencingService|.
95 // TODO(mek): This will need to change to support geofence registrations that
96 // outlive the browser, although removing the references to this
97 // |GeofencingManager| from the |GeofencingService| will still be needed.
98 for (const auto& registration : registrations_by_id_)
99 service_->UnregisterRegion(registration.first);
102 void GeofencingManager::RegisterRegion(
103 int64 service_worker_registration_id,
104 const std::string& region_id,
105 const blink::WebCircularGeofencingRegion& region,
106 const StatusCallback& callback) {
107 DCHECK_CURRENTLY_ON(BrowserThread::IO);
109 // TODO(mek): Validate region_id and region.
111 // Look up service worker.
112 ServiceWorkerRegistration* service_worker_registration =
113 service_worker_context_->context()->GetLiveRegistration(
114 service_worker_registration_id);
115 if (!service_worker_registration) {
116 callback.Run(GEOFENCING_STATUS_OPERATION_FAILED_NO_SERVICE_WORKER);
117 return;
120 GURL service_worker_origin =
121 service_worker_registration->pattern().GetOrigin();
123 if (!service_->IsServiceAvailable()) {
124 callback.Run(GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE);
125 return;
128 if (FindRegistration(service_worker_registration_id, region_id)) {
129 // Already registered, return an error.
130 // TODO(mek): Use a more specific error code.
131 callback.Run(GEOFENCING_STATUS_ERROR);
132 return;
135 AddRegistration(service_worker_registration_id, service_worker_origin,
136 region_id, region, callback,
137 service_->RegisterRegion(region, this));
140 void GeofencingManager::UnregisterRegion(int64 service_worker_registration_id,
141 const std::string& region_id,
142 const StatusCallback& callback) {
143 DCHECK_CURRENTLY_ON(BrowserThread::IO);
145 // TODO(mek): Validate region_id.
147 // Look up service worker.
148 ServiceWorkerRegistration* service_worker_registration =
149 service_worker_context_->context()->GetLiveRegistration(
150 service_worker_registration_id);
151 if (!service_worker_registration) {
152 callback.Run(GEOFENCING_STATUS_OPERATION_FAILED_NO_SERVICE_WORKER);
153 return;
156 if (!service_->IsServiceAvailable()) {
157 callback.Run(GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE);
158 return;
161 Registration* registration =
162 FindRegistration(service_worker_registration_id, region_id);
163 if (!registration) {
164 // Not registered, return an error.
165 callback.Run(GEOFENCING_STATUS_UNREGISTRATION_FAILED_NOT_REGISTERED);
166 return;
169 if (!registration->is_active()) {
170 // Started registration, but not completed yet, error.
171 callback.Run(GEOFENCING_STATUS_UNREGISTRATION_FAILED_NOT_REGISTERED);
172 return;
175 service_->UnregisterRegion(registration->geofencing_registration_id);
176 ClearRegistration(registration);
177 callback.Run(GEOFENCING_STATUS_OK);
180 GeofencingStatus GeofencingManager::GetRegisteredRegions(
181 int64 service_worker_registration_id,
182 std::map<std::string, blink::WebCircularGeofencingRegion>* result) {
183 DCHECK_CURRENTLY_ON(BrowserThread::IO);
184 CHECK(result);
186 // Look up service worker.
187 ServiceWorkerRegistration* service_worker_registration =
188 service_worker_context_->context()->GetLiveRegistration(
189 service_worker_registration_id);
190 if (!service_worker_registration) {
191 return GEOFENCING_STATUS_OPERATION_FAILED_NO_SERVICE_WORKER;
194 if (!service_->IsServiceAvailable()) {
195 return GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE;
198 // Populate result, filtering out inactive registrations.
199 result->clear();
200 ServiceWorkerRegistrationsMap::iterator registrations =
201 registrations_.find(service_worker_registration_id);
202 if (registrations == registrations_.end())
203 return GEOFENCING_STATUS_OK;
204 for (const auto& registration : registrations->second) {
205 if (registration.second.is_active())
206 (*result)[registration.first] = registration.second.region;
208 return GEOFENCING_STATUS_OK;
211 void GeofencingManager::SetMockProvider(GeofencingMockState mock_state) {
212 DCHECK_CURRENTLY_ON(BrowserThread::IO);
214 // TODO(mek): It would be nice if enabling the mock geofencing service
215 // wouldn't completely delete all existing registrations but instead just
216 // somehow keep them around but inactive.
217 // For now mocking is only used in layout tests, so just clearing all
218 // registrations works good enough.
219 for (const auto& registration : registrations_by_id_)
220 service_->UnregisterRegion(registration.first);
221 registrations_by_id_.clear();
222 registrations_.clear();
224 // Then set or reset the mock service for the service worker.
225 if (mock_state == GeofencingMockState::NONE) {
226 service_ = GeofencingServiceImpl::GetInstance();
227 mock_service_.reset();
228 } else {
229 mock_service_.reset(new MockGeofencingService(
230 mock_state != GeofencingMockState::SERVICE_UNAVAILABLE));
231 service_ = mock_service_.get();
235 void GeofencingManager::SetMockPosition(double latitude, double longitude) {
236 DCHECK_CURRENTLY_ON(BrowserThread::IO);
237 if (!mock_service_)
238 return;
239 mock_service_->SetMockPosition(latitude, longitude);
242 void GeofencingManager::OnRegistrationDeleted(
243 int64 service_worker_registration_id,
244 const GURL& pattern) {
245 DCHECK_CURRENTLY_ON(BrowserThread::IO);
246 CleanUpForServiceWorker(service_worker_registration_id);
249 void GeofencingManager::RegistrationFinished(int64 geofencing_registration_id,
250 GeofencingStatus status) {
251 DCHECK_CURRENTLY_ON(BrowserThread::IO);
252 Registration* registration = FindRegistrationById(geofencing_registration_id);
253 DCHECK(registration);
254 DCHECK(!registration->is_active());
255 registration->registration_callback.Run(status);
256 registration->registration_callback.Reset();
258 // If the registration wasn't succesful, remove it from our storage.
259 if (status != GEOFENCING_STATUS_OK)
260 ClearRegistration(registration);
263 void GeofencingManager::RegionEntered(int64 geofencing_registration_id) {
264 DispatchGeofencingEvent(blink::WebGeofencingEventTypeEnter,
265 geofencing_registration_id);
268 void GeofencingManager::RegionExited(int64 geofencing_registration_id) {
269 DispatchGeofencingEvent(blink::WebGeofencingEventTypeLeave,
270 geofencing_registration_id);
273 GeofencingManager::Registration* GeofencingManager::FindRegistration(
274 int64 service_worker_registration_id,
275 const std::string& region_id) {
276 DCHECK_CURRENTLY_ON(BrowserThread::IO);
277 ServiceWorkerRegistrationsMap::iterator registrations_iterator =
278 registrations_.find(service_worker_registration_id);
279 if (registrations_iterator == registrations_.end())
280 return nullptr;
281 RegionIdRegistrationMap::iterator registration =
282 registrations_iterator->second.find(region_id);
283 if (registration == registrations_iterator->second.end())
284 return nullptr;
285 return &registration->second;
288 GeofencingManager::Registration* GeofencingManager::FindRegistrationById(
289 int64 geofencing_registration_id) {
290 DCHECK_CURRENTLY_ON(BrowserThread::IO);
291 RegistrationIdRegistrationMap::iterator registration_iterator =
292 registrations_by_id_.find(geofencing_registration_id);
293 if (registration_iterator == registrations_by_id_.end())
294 return nullptr;
295 return &registration_iterator->second->second;
298 GeofencingManager::Registration& GeofencingManager::AddRegistration(
299 int64 service_worker_registration_id,
300 const GURL& service_worker_origin,
301 const std::string& region_id,
302 const blink::WebCircularGeofencingRegion& region,
303 const StatusCallback& callback,
304 int64 geofencing_registration_id) {
305 DCHECK_CURRENTLY_ON(BrowserThread::IO);
306 DCHECK(!FindRegistration(service_worker_registration_id, region_id));
307 RegionIdRegistrationMap::iterator registration =
308 registrations_[service_worker_registration_id]
309 .insert(std::make_pair(region_id,
310 Registration(service_worker_registration_id,
311 service_worker_origin,
312 region_id,
313 region,
314 callback,
315 geofencing_registration_id)))
316 .first;
317 registrations_by_id_[geofencing_registration_id] = registration;
318 return registration->second;
321 void GeofencingManager::ClearRegistration(Registration* registration) {
322 DCHECK_CURRENTLY_ON(BrowserThread::IO);
323 registrations_by_id_.erase(registration->geofencing_registration_id);
324 ServiceWorkerRegistrationsMap::iterator registrations_iterator =
325 registrations_.find(registration->service_worker_registration_id);
326 DCHECK(registrations_iterator != registrations_.end());
327 registrations_iterator->second.erase(registration->region_id);
328 if (registrations_iterator->second.empty())
329 registrations_.erase(registrations_iterator);
332 void GeofencingManager::CleanUpForServiceWorker(
333 int64 service_worker_registration_id) {
334 DCHECK_CURRENTLY_ON(BrowserThread::IO);
335 ServiceWorkerRegistrationsMap::iterator registrations_iterator =
336 registrations_.find(service_worker_registration_id);
337 if (registrations_iterator == registrations_.end())
338 return;
340 for (const auto& registration : registrations_iterator->second) {
341 int geofencing_registration_id =
342 registration.second.geofencing_registration_id;
343 service_->UnregisterRegion(geofencing_registration_id);
344 registrations_by_id_.erase(geofencing_registration_id);
346 registrations_.erase(service_worker_registration_id);
349 void GeofencingManager::DispatchGeofencingEvent(
350 blink::WebGeofencingEventType event_type,
351 int64 geofencing_registration_id) {
352 DCHECK_CURRENTLY_ON(BrowserThread::IO);
353 Registration* registration = FindRegistrationById(geofencing_registration_id);
354 if (!registration ||
355 registration->service_worker_registration_id ==
356 kInvalidServiceWorkerRegistrationId) {
357 // TODO(mek): Log/track these failures.
358 return;
361 service_worker_context_->context()->storage()->FindRegistrationForId(
362 registration->service_worker_registration_id,
363 registration->service_worker_origin,
364 base::Bind(&GeofencingManager::DeliverGeofencingEvent,
365 this,
366 event_type,
367 geofencing_registration_id));
370 void GeofencingManager::DeliverGeofencingEvent(
371 blink::WebGeofencingEventType event_type,
372 int64 geofencing_registration_id,
373 ServiceWorkerStatusCode service_worker_status,
374 const scoped_refptr<ServiceWorkerRegistration>&
375 service_worker_registration) {
376 DCHECK_CURRENTLY_ON(BrowserThread::IO);
377 Registration* registration = FindRegistrationById(geofencing_registration_id);
378 if (!registration) {
379 // Geofence got unregistered in the meantime, no longer need to deliver
380 // event.
381 return;
384 if (service_worker_status != SERVICE_WORKER_OK) {
385 // TODO(mek): SW no longer exists, somehow handle this.
386 return;
389 ServiceWorkerVersion* active_version =
390 service_worker_registration->active_version();
391 if (!active_version) {
392 // TODO(mek): No active version, potentially because one is still being
393 // installed. Handle this somehow.
394 return;
397 // Hold on to the service worker registration in the callback to keep it alive
398 // until the callback dies. Otherwise the registration could be released when
399 // this method returns - before the event is delivered to the service worker.
400 base::Callback<void(ServiceWorkerStatusCode)> dispatch_event_callback =
401 base::Bind(&GeofencingManager::DeliverGeofencingEventEnd,
402 this,
403 service_worker_registration);
404 active_version->DispatchGeofencingEvent(dispatch_event_callback,
405 event_type,
406 registration->region_id,
407 registration->region);
410 void GeofencingManager::DeliverGeofencingEventEnd(
411 const scoped_refptr<ServiceWorkerRegistration>& service_worker_registration,
412 ServiceWorkerStatusCode service_worker_status) {
413 DCHECK_CURRENTLY_ON(BrowserThread::IO);
414 // TODO(mek): log/check result.
417 } // namespace content