Commit d19c6afc authored by mek's avatar mek Committed by Commit bot

Basic implementation of GeofencingManager class.

This class is the single entry point for all Geofencing related API calls, and will be responsible for storing all geofence registrations, and registering them with the underlying platform specific GeofencingProvider.

BUG=383125

Review URL: https://codereview.chromium.org/586163003

Cr-Commit-Position: refs/heads/master@{#299172}
parent 7d5dbab6
...@@ -4,16 +4,20 @@ ...@@ -4,16 +4,20 @@
#include "content/browser/geofencing/geofencing_dispatcher_host.h" #include "content/browser/geofencing/geofencing_dispatcher_host.h"
#include "content/browser/geofencing/geofencing_manager.h"
#include "content/common/geofencing_messages.h" #include "content/common/geofencing_messages.h"
#include "content/common/geofencing_status.h"
#include "third_party/WebKit/public/platform/WebCircularGeofencingRegion.h" #include "third_party/WebKit/public/platform/WebCircularGeofencingRegion.h"
#include "url/gurl.h"
namespace content { namespace content {
static const int kMaxRegionIdLength = 200; static const int kMaxRegionIdLength = 200;
GeofencingDispatcherHost::GeofencingDispatcherHost() GeofencingDispatcherHost::GeofencingDispatcherHost(
: BrowserMessageFilter(GeofencingMsgStart) { BrowserContext* browser_context)
: BrowserMessageFilter(GeofencingMsgStart),
browser_context_(browser_context),
weak_factory_(this) {
} }
GeofencingDispatcherHost::~GeofencingDispatcherHost() { GeofencingDispatcherHost::~GeofencingDispatcherHost() {
...@@ -42,12 +46,17 @@ void GeofencingDispatcherHost::OnRegisterRegion( ...@@ -42,12 +46,17 @@ void GeofencingDispatcherHost::OnRegisterRegion(
thread_id, request_id, GeofencingStatus::GEOFENCING_STATUS_ERROR)); thread_id, request_id, GeofencingStatus::GEOFENCING_STATUS_ERROR));
return; return;
} }
// TODO(mek): Handle registration request. // TODO(mek): Actually pass service worker information to manager.
Send(new GeofencingMsg_RegisterRegionComplete( GeofencingManager::GetInstance()->RegisterRegion(
thread_id, browser_context_,
request_id, 0, /* service_worker_registration_id */
GeofencingStatus:: GURL(), /* service_worker_origin */
GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE)); region_id,
region,
base::Bind(&GeofencingDispatcherHost::RegisterRegionCompleted,
weak_factory_.GetWeakPtr(),
thread_id,
request_id));
} }
void GeofencingDispatcherHost::OnUnregisterRegion( void GeofencingDispatcherHost::OnUnregisterRegion(
...@@ -60,23 +69,45 @@ void GeofencingDispatcherHost::OnUnregisterRegion( ...@@ -60,23 +69,45 @@ void GeofencingDispatcherHost::OnUnregisterRegion(
thread_id, request_id, GeofencingStatus::GEOFENCING_STATUS_ERROR)); thread_id, request_id, GeofencingStatus::GEOFENCING_STATUS_ERROR));
return; return;
} }
// TODO(mek): Handle unregistration request. // TODO(mek): Actually pass service worker information to manager.
Send(new GeofencingMsg_UnregisterRegionComplete( GeofencingManager::GetInstance()->UnregisterRegion(
thread_id, browser_context_,
request_id, 0, /* service_worker_registration_id */
GeofencingStatus:: GURL(), /* service_worker_origin */
GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE)); region_id,
base::Bind(&GeofencingDispatcherHost::UnregisterRegionCompleted,
weak_factory_.GetWeakPtr(),
thread_id,
request_id));
} }
void GeofencingDispatcherHost::OnGetRegisteredRegions(int thread_id, void GeofencingDispatcherHost::OnGetRegisteredRegions(int thread_id,
int request_id) { int request_id) {
GeofencingRegistrations result; GeofencingRegistrations result;
// TODO(mek): Actually pass service worker information to manager.
GeofencingStatus status =
GeofencingManager::GetInstance()->GetRegisteredRegions(
browser_context_,
0, /* service_worker_registration_id */
GURL(), /* service_worker_origin */
&result);
Send(new GeofencingMsg_GetRegisteredRegionsComplete( Send(new GeofencingMsg_GetRegisteredRegionsComplete(
thread_id, thread_id, request_id, status, result));
request_id, }
GeofencingStatus::
GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE, void GeofencingDispatcherHost::RegisterRegionCompleted(
result)); int thread_id,
int request_id,
GeofencingStatus status) {
Send(new GeofencingMsg_RegisterRegionComplete(thread_id, request_id, status));
}
void GeofencingDispatcherHost::UnregisterRegionCompleted(
int thread_id,
int request_id,
GeofencingStatus status) {
Send(new GeofencingMsg_UnregisterRegionComplete(
thread_id, request_id, status));
} }
} // namespace content } // namespace content
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef CONTENT_BROWSER_GEOFENCING_GEOFENCING_DISPATCHER_HOST_H_ #ifndef CONTENT_BROWSER_GEOFENCING_GEOFENCING_DISPATCHER_HOST_H_
#define CONTENT_BROWSER_GEOFENCING_GEOFENCING_DISPATCHER_HOST_H_ #define CONTENT_BROWSER_GEOFENCING_GEOFENCING_DISPATCHER_HOST_H_
#include "content/common/geofencing_status.h"
#include "content/public/browser/browser_message_filter.h" #include "content/public/browser/browser_message_filter.h"
namespace blink { namespace blink {
...@@ -13,9 +14,11 @@ struct WebCircularGeofencingRegion; ...@@ -13,9 +14,11 @@ struct WebCircularGeofencingRegion;
namespace content { namespace content {
class BrowserContext;
class GeofencingDispatcherHost : public BrowserMessageFilter { class GeofencingDispatcherHost : public BrowserMessageFilter {
public: public:
GeofencingDispatcherHost(); explicit GeofencingDispatcherHost(BrowserContext* browser_context);
private: private:
virtual ~GeofencingDispatcherHost(); virtual ~GeofencingDispatcherHost();
...@@ -32,6 +35,16 @@ class GeofencingDispatcherHost : public BrowserMessageFilter { ...@@ -32,6 +35,16 @@ class GeofencingDispatcherHost : public BrowserMessageFilter {
const std::string& region_id); const std::string& region_id);
void OnGetRegisteredRegions(int thread_id, int request_id); void OnGetRegisteredRegions(int thread_id, int request_id);
void RegisterRegionCompleted(int thread_id,
int request_id,
GeofencingStatus result);
void UnregisterRegionCompleted(int thread_id,
int request_id,
GeofencingStatus result);
BrowserContext* browser_context_;
base::WeakPtrFactory<GeofencingDispatcherHost> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(GeofencingDispatcherHost); DISALLOW_COPY_AND_ASSIGN(GeofencingDispatcherHost);
}; };
......
// Copyright 2014 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/geofencing/geofencing_manager.h"
#include <algorithm>
#include "base/callback.h"
#include "base/memory/singleton.h"
#include "content/browser/geofencing/geofencing_provider.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/WebKit/public/platform/WebCircularGeofencingRegion.h"
#include "url/gurl.h"
namespace content {
struct GeofencingManager::RegistrationKey {
RegistrationKey(BrowserContext* browser_context,
int64 service_worker_registration_id,
const GURL& service_worker_origin,
const std::string& region_id);
BrowserContext* browser_context;
int64 service_worker_registration_id;
GURL service_worker_origin;
std::string region_id;
};
GeofencingManager::RegistrationKey::RegistrationKey(
BrowserContext* browser_context,
int64 service_worker_registration_id,
const GURL& service_worker_origin,
const std::string& region_id)
: browser_context(browser_context),
service_worker_registration_id(service_worker_registration_id),
service_worker_origin(service_worker_origin),
region_id(region_id) {
}
struct GeofencingManager::Registration {
Registration();
Registration(const RegistrationKey& key,
const blink::WebCircularGeofencingRegion& region);
RegistrationKey key;
blink::WebCircularGeofencingRegion region;
// Registration id as returned by the GeofencingProvider, set to -1 if not
// currently registered with the provider.
int registration_id;
// Flag to indicate if this registration has completed, and thus should be
// included in calls to GetRegisteredRegions.
bool is_active;
};
GeofencingManager::Registration::Registration() : key(nullptr, -1, GURL(), "") {
}
GeofencingManager::Registration::Registration(
const RegistrationKey& key,
const blink::WebCircularGeofencingRegion& region)
: key(key), region(region), registration_id(-1), is_active(false) {
}
class GeofencingManager::RegistrationMatches {
public:
RegistrationMatches(const RegistrationKey& key) : key_(key) {}
bool operator()(const Registration& registration) {
return registration.key.browser_context == key_.browser_context &&
registration.key.service_worker_registration_id ==
key_.service_worker_registration_id &&
registration.key.service_worker_origin ==
key_.service_worker_origin &&
registration.key.region_id == key_.region_id;
}
private:
const RegistrationKey& key_;
};
GeofencingManager::GeofencingManager() {
}
GeofencingManager::~GeofencingManager() {
}
GeofencingManager* GeofencingManager::GetInstance() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return Singleton<GeofencingManager>::get();
}
void GeofencingManager::RegisterRegion(
BrowserContext* browser_context,
int64 service_worker_registration_id,
const GURL& service_worker_origin,
const std::string& region_id,
const blink::WebCircularGeofencingRegion& region,
const StatusCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// TODO(mek): Validate region_id and region.
if (!provider_.get()) {
callback.Run(GeofencingStatus::
GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE);
return;
}
RegistrationKey key(browser_context,
service_worker_registration_id,
service_worker_origin,
region_id);
if (FindRegistration(key)) {
// Already registered, return an error.
callback.Run(GeofencingStatus::GEOFENCING_STATUS_ERROR);
return;
}
// Add registration, but don't mark it as active yet. This prevents duplicate
// registrations.
AddRegistration(key, region);
// Register with provider.
provider_->RegisterRegion(
region,
base::Bind(&GeofencingManager::RegisterRegionCompleted,
base::Unretained(this),
callback,
key));
}
void GeofencingManager::UnregisterRegion(BrowserContext* browser_context,
int64 service_worker_registration_id,
const GURL& service_worker_origin,
const std::string& region_id,
const StatusCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// TODO(mek): Validate region_id.
if (!provider_.get()) {
callback.Run(GeofencingStatus::
GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE);
return;
}
RegistrationKey key(browser_context,
service_worker_registration_id,
service_worker_origin,
region_id);
Registration* registration = FindRegistration(key);
if (!registration) {
// Not registered, return an error/
callback.Run(GeofencingStatus::GEOFENCING_STATUS_ERROR);
return;
}
if (!registration->is_active) {
// Started registration, but not completed yet, error.
callback.Run(GeofencingStatus::GEOFENCING_STATUS_ERROR);
return;
}
if (registration->registration_id != -1) {
provider_->UnregisterRegion(registration->registration_id);
}
ClearRegistration(key);
callback.Run(GeofencingStatus::GEOFENCING_STATUS_OK);
}
GeofencingStatus GeofencingManager::GetRegisteredRegions(
BrowserContext* browser_context,
int64 service_worker_registration_id,
const GURL& service_worker_origin,
std::map<std::string, blink::WebCircularGeofencingRegion>* result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
CHECK(result);
if (!provider_.get()) {
return GeofencingStatus::
GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE;
}
// Populate result, filtering out inactive registrations.
result->clear();
for (const auto& registration : registrations_) {
if (registration.key.browser_context == browser_context &&
registration.key.service_worker_registration_id ==
service_worker_registration_id &&
registration.key.service_worker_origin == service_worker_origin &&
registration.is_active) {
(*result)[registration.key.region_id] = registration.region;
}
}
return GeofencingStatus::GEOFENCING_STATUS_OK;
}
void GeofencingManager::RegisterRegionCompleted(const StatusCallback& callback,
const RegistrationKey& key,
GeofencingStatus status,
int registration_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (status != GEOFENCING_STATUS_OK) {
ClearRegistration(key);
callback.Run(status);
return;
}
Registration* registration = FindRegistration(key);
DCHECK(registration);
registration->registration_id = registration_id;
registration->is_active = true;
callback.Run(GeofencingStatus::GEOFENCING_STATUS_OK);
}
void GeofencingManager::SetProviderForTests(
scoped_ptr<GeofencingProvider> provider) {
DCHECK(!provider_.get());
provider_ = provider.Pass();
}
GeofencingManager::Registration* GeofencingManager::FindRegistration(
const RegistrationKey& key) {
std::vector<Registration>::iterator it = std::find_if(
registrations_.begin(), registrations_.end(), RegistrationMatches(key));
if (it == registrations_.end())
return nullptr;
return &*it;
}
GeofencingManager::Registration& GeofencingManager::AddRegistration(
const RegistrationKey& key,
const blink::WebCircularGeofencingRegion& region) {
DCHECK(!FindRegistration(key));
registrations_.push_back(Registration(key, region));
return registrations_.back();
}
void GeofencingManager::ClearRegistration(const RegistrationKey& key) {
std::vector<Registration>::iterator it = std::find_if(
registrations_.begin(), registrations_.end(), RegistrationMatches(key));
if (it == registrations_.end())
return;
registrations_.erase(it);
}
} // namespace content
// Copyright 2014 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.
#ifndef CONTENT_BROWSER_GEOFENCING_GEOFENCING_MANAGER_H_
#define CONTENT_BROWSER_GEOFENCING_GEOFENCING_MANAGER_H_
#include <map>
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "content/common/content_export.h"
#include "content/common/geofencing_status.h"
template <typename T>
struct DefaultSingletonTraits;
class GURL;
namespace blink {
struct WebCircularGeofencingRegion;
};
namespace content {
class BrowserContext;
class GeofencingProvider;
// This is the main API to the geofencing subsystem. The application will hold
// a single instance of this class.
// This class is responsible for keeping track of which geofences are currently
// registered by websites/workers, persisting this list of registrations and
// registering a subset of these active registrations with the underlying
// platform specific |GeofencingProvider| instance.
// This class lives on the IO thread, and all public methods of it should only
// ever be called from that same thread.
// FIXME: Does it make more sense for this to live on the UI thread instead of
// the IO thread?
// TODO(mek): Implement some kind of persistence of registrations.
// TODO(mek): Limit the number of geofences that are registered with the
// underlying GeofencingProvider.
class CONTENT_EXPORT GeofencingManager {
public:
typedef base::Callback<void(GeofencingStatus)> StatusCallback;
typedef base::Callback<void(
GeofencingStatus,
const std::map<std::string, blink::WebCircularGeofencingRegion>&)>
RegistrationsCallback;
// Gets a pointer to the singleton instance of the geofencing manager. This
// must only be called on the IO thread so that the GeofencingManager is
// always instantiated on the same thread. Ownership is NOT returned.
static GeofencingManager* GetInstance();
// Initiates registration of a new geofence. StatusCallback is called when
// registration has completed or failed (which could possibly be before
// RegisterRegion returns.
// Attempting to register a region with the same ID as an already registered
// (or in progress of being registered) region will fail.
// TODO(mek): Behavior when using an already used ID might need to be revised
// depending on what the actual spec ends up saying about this.
void RegisterRegion(BrowserContext* browser_context,
int64 service_worker_registration_id,
const GURL& service_worker_origin,
const std::string& region_id,
const blink::WebCircularGeofencingRegion& region,
const StatusCallback& callback);
// Unregister a region that was previously registered by a call to
// RegisterRegion. Any attempt to unregister a region that has not been
// registered, or for which the registration is still in progress
// (RegisterRegion hasn't called its callback yet) will fail.
// TODO(mek): Maybe better behavior would be to allow unregistering still
// in-progress registrations.
void UnregisterRegion(BrowserContext* browser_context,
int64 service_worker_registration_id,
const GURL& service_worker_origin,
const std::string& region_id,
const StatusCallback& callback);
// Returns all currently registered regions. In case of failure (no geofencing
// provider available for example) return an error status, while leaving
// |regions| untouched.
// This only returns regions for which the callback passed to RegisterRegion
// has been called already (so it doesn't include still in progress
// registrations).
GeofencingStatus GetRegisteredRegions(
BrowserContext* browser_context,
int64 service_worker_registration_id,
const GURL& service_worker_origin,
std::map<std::string, blink::WebCircularGeofencingRegion>* regions);
void SetProviderForTests(scoped_ptr<GeofencingProvider> provider);
protected:
friend struct DefaultSingletonTraits<GeofencingManager>;
friend class GeofencingManagerTest;
GeofencingManager();
virtual ~GeofencingManager();
private:
// Internal bookkeeping associated with each registered geofence.
struct RegistrationKey;
struct Registration;
class RegistrationMatches;
// Called by GeofencingProvider when the platform specific provider completes
// registration of a geofence.
void RegisterRegionCompleted(const StatusCallback& callback,
const RegistrationKey& key,
GeofencingStatus status,
int registration_id);
// Looks up a particular geofence registration. Returns nullptr if no
// registration with the given IDs exists.
Registration* FindRegistration(const RegistrationKey& key);
// Registers a new registration, returning a reference to the newly inserted
// object. Assumes no registration with the same IDs currently exists.
Registration& AddRegistration(
const RegistrationKey& key,
const blink::WebCircularGeofencingRegion& region);
// Clears a registration.
void ClearRegistration(const RegistrationKey& key);
// List of all currently registered geofences.
// TODO(mek): Better way of storing these that allows more efficient lookup
// and deletion.
std::vector<Registration> registrations_;
scoped_ptr<GeofencingProvider> provider_;
DISALLOW_COPY_AND_ASSIGN(GeofencingManager);
};
} // namespace content
#endif // CONTENT_BROWSER_GEOFENCING_GEOFENCING_MANAGER_H_
This diff is collapsed.
// Copyright 2014 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.
#ifndef CONTENT_BROWSER_GEOFENCING_GEOFENCING_PROVIDER_H_
#define CONTENT_BROWSER_GEOFENCING_GEOFENCING_PROVIDER_H_
#include "base/callback_forward.h"
#include "content/common/geofencing_status.h"
namespace blink {
struct WebCircularGeofencingRegion;
};
namespace content {
class GeofencingProvider {
public:
// Callback that gets called on completion of registering a new region. The
// status indicates success or failure, and in case of success, an id to use
// to later unregister the region is passed as |registration_id|. If
// registration failed, providers should set |registration_id| to -1.
typedef base::Callback<void(GeofencingStatus, int registration_id)>
RegisterCallback;
virtual ~GeofencingProvider() {}
// Called by |GeofencingManager| to register a new fence. GeofencingManager is
// responsible for handling things like duplicate regions, so platform
// specific implementations shouldn't have to worry about things like that.
// Also GeofencingManager should be making sure the total number of geofences
// that is registered with the platform specific provider does not exceed the
// number of regions supported by the platform, although that isn't
// implemented yet.
virtual void RegisterRegion(const blink::WebCircularGeofencingRegion& region,
const RegisterCallback& callback) = 0;
// Called by |GeofencingManager| to unregister an existing registration. Will
// only be called once for each registration.
virtual void UnregisterRegion(int registration_id) = 0;
};
} // namespace content
#endif // CONTENT_BROWSER_GEOFENCING_GEOFENCING_PROVIDER_H_
...@@ -890,7 +890,7 @@ void RenderProcessHostImpl::CreateMessageFilters() { ...@@ -890,7 +890,7 @@ void RenderProcessHostImpl::CreateMessageFilters() {
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
AddFilter(new ScreenOrientationMessageFilterAndroid()); AddFilter(new ScreenOrientationMessageFilterAndroid());
#endif #endif
AddFilter(new GeofencingDispatcherHost()); AddFilter(new GeofencingDispatcherHost(browser_context_));
} }
int RenderProcessHostImpl::GetNextRoutingID() { int RenderProcessHostImpl::GetNextRoutingID() {
......
...@@ -19,6 +19,9 @@ const char* GeofencingStatusToString(GeofencingStatus status) { ...@@ -19,6 +19,9 @@ const char* GeofencingStatusToString(GeofencingStatus status) {
case GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE: case GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE:
return "Operation failed - geofencing not available"; return "Operation failed - geofencing not available";
case GEOFENCING_STATUS_UNREGISTRATION_FAILED_NOT_REGISTERED:
return "Unregistration failed - no region registered with given ID";
case GEOFENCING_STATUS_ERROR: case GEOFENCING_STATUS_ERROR:
return "Operation has failed (unspecified reason)"; return "Operation has failed (unspecified reason)";
} }
......
...@@ -17,6 +17,9 @@ enum GeofencingStatus { ...@@ -17,6 +17,9 @@ enum GeofencingStatus {
// Operation failed because geofencing is not available. // Operation failed because geofencing is not available.
GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE, GEOFENCING_STATUS_OPERATION_FAILED_SERVICE_NOT_AVAILABLE,
// Unregistering failed because region was not registered.
GEOFENCING_STATUS_UNREGISTRATION_FAILED_NOT_REGISTERED,
// Generic error. // Generic error.
GEOFENCING_STATUS_ERROR, GEOFENCING_STATUS_ERROR,
......
...@@ -674,6 +674,9 @@ ...@@ -674,6 +674,9 @@
'browser/gamepad/xbox_data_fetcher_mac.h', 'browser/gamepad/xbox_data_fetcher_mac.h',
'browser/geofencing/geofencing_dispatcher_host.cc', 'browser/geofencing/geofencing_dispatcher_host.cc',
'browser/geofencing/geofencing_dispatcher_host.h', 'browser/geofencing/geofencing_dispatcher_host.h',
'browser/geofencing/geofencing_manager.cc',
'browser/geofencing/geofencing_manager.h',
'browser/geofencing/geofencing_provider.h',
'browser/geolocation/empty_wifi_data_provider.cc', 'browser/geolocation/empty_wifi_data_provider.cc',
'browser/geolocation/empty_wifi_data_provider.h', 'browser/geolocation/empty_wifi_data_provider.h',
'browser/geolocation/geolocation_dispatcher_host.cc', 'browser/geolocation/geolocation_dispatcher_host.cc',
......
...@@ -482,6 +482,7 @@ ...@@ -482,6 +482,7 @@
'browser/gamepad/gamepad_service_unittest.cc', 'browser/gamepad/gamepad_service_unittest.cc',
'browser/gamepad/gamepad_test_helpers.cc', 'browser/gamepad/gamepad_test_helpers.cc',
'browser/gamepad/gamepad_test_helpers.h', 'browser/gamepad/gamepad_test_helpers.h',
'browser/geofencing/geofencing_manager_unittest.cc',
'browser/geolocation/geolocation_provider_impl_unittest.cc', 'browser/geolocation/geolocation_provider_impl_unittest.cc',
'browser/geolocation/location_arbitrator_impl_unittest.cc', 'browser/geolocation/location_arbitrator_impl_unittest.cc',
'browser/geolocation/network_location_provider_unittest.cc', 'browser/geolocation/network_location_provider_unittest.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment