Commit d4c96c75 authored by Colin Blundell's avatar Colin Blundell Committed by Commit Bot

[iOS] Bring up support for per-user services on iOS

This CL augments the existing ServiceManager embedding on iOS with
support for per-user embedded services. A "per-user" service is one
that has one instance per user, rather than a shared instance for all
users. Concretely, each BrowserState has a unique UserID, and there is
one instance of a per-user service per UserID. Essentially, one can
think of per-user services as the equivalent of profile-keyed services
in the services world.

The support added in this CL closely follows that of //content:

- Public APIs are identical
- Implementation in BrowserState follows that of BrowserContext with minor
  changes:
  - iOS doesn't use File Service at this time
  - No client of //ios/web needs to override how a user ID is generated

This CL adds an Earl Grey test of web_shell that a per-user service can be
embedded and if connected to from a given BrowserState, has its user ID
properly associated with that of the BrowserState.

Finally, this added support requires that all BrowserState subclasses call
BrowserState::Initialize(). This is also done in this CL.

Bug: 731588
Change-Id: Ie3bb79d4f754aecddd9c35c72576262dfc87cb55
Reviewed-on: https://chromium-review.googlesource.com/559529Reviewed-by: default avatarKen Rockot <rockot@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Reviewed-by: default avatarTom Sepez <tsepez@chromium.org>
Reviewed-by: default avatarSylvain Defresne <sdefresne@chromium.org>
Commit-Queue: Colin Blundell <blundell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#485547}
parent 8ddf8272
......@@ -95,6 +95,8 @@ ChromeBrowserStateImpl::ChromeBrowserStateImpl(const base::FilePath& path)
: state_path_(path),
pref_registry_(new user_prefs::PrefRegistrySyncable),
io_data_(new ChromeBrowserStateImplIOData::Handle(this)) {
BrowserState::Initialize(this, state_path_);
otr_state_path_ = state_path_.Append(FILE_PATH_LITERAL("OTR"));
bool directories_created =
......
......@@ -21,6 +21,7 @@ OffTheRecordChromeBrowserStateImpl::OffTheRecordChromeBrowserStateImpl(
original_chrome_browser_state_(original_chrome_browser_state),
prefs_(static_cast<sync_preferences::PrefServiceSyncable*>(
original_chrome_browser_state->GetOffTheRecordPrefs())) {
BrowserState::Initialize(this, otr_state_path_);
user_prefs::UserPrefs::Set(this, GetPrefs());
io_data_.reset(new OffTheRecordChromeBrowserStateIOData::Handle(this));
BrowserStateDependencyManager::GetInstance()->CreateBrowserStateServices(
......
......@@ -176,6 +176,8 @@ void TestChromeBrowserState::Init() {
if (!base::PathExists(state_path_))
base::CreateDirectory(state_path_);
BrowserState::Initialize(this, GetStatePath());
// Normally this would happen during browser startup, but for tests we need to
// trigger creation of BrowserState-related services.
EnsureBrowserStateKeyedServiceFactoriesBuilt();
......
......@@ -4,13 +4,21 @@
#include "ios/web/public/browser_state.h"
#include "base/guid.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/process/process_handle.h"
#include "ios/web/active_state_manager_impl.h"
#include "ios/web/public/certificate_policy_cache.h"
#include "ios/web/public/service_manager_connection.h"
#include "ios/web/public/service_names.mojom.h"
#include "ios/web/public/web_client.h"
#include "ios/web/public/web_thread.h"
#include "ios/web/webui/url_data_manager_ios_backend.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/service_manager/public/interfaces/service.mojom.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
......@@ -18,6 +26,11 @@
namespace web {
namespace {
// Maps service userIds to associated BrowserState instances.
base::LazyInstance<std::map<std::string, BrowserState*>>::DestructorAtExit
g_user_id_to_browser_state = LAZY_INSTANCE_INITIALIZER;
// Private key used for safe conversion of base::SupportsUserData to
// web::BrowserState in web::BrowserState::FromSupportsUserData.
const char kBrowserStateIdentifierKey[] = "BrowserStateIdentifierKey";
......@@ -25,6 +38,9 @@ const char kBrowserStateIdentifierKey[] = "BrowserStateIdentifierKey";
// Data key names.
const char kCertificatePolicyCacheKeyName[] = "cert_policy_cache";
const char kActiveStateManagerKeyName[] = "active_state_manager";
const char kMojoWasInitialized[] = "mojo-was-initialized";
const char kServiceManagerConnection[] = "service-manager-connection";
const char kServiceUserId[] = "service-user-id";
// Wraps a CertificatePolicyCache as a SupportsUserData::Data; this is necessary
// since reference counted objects can't be user data.
......@@ -34,8 +50,58 @@ struct CertificatePolicyCacheHandle : public base::SupportsUserData::Data {
scoped_refptr<CertificatePolicyCache> policy_cache;
};
// Container for a service userId to support association between BrowserStates
// and service UserIds.
class ServiceUserIdHolder : public base::SupportsUserData::Data {
public:
explicit ServiceUserIdHolder(const std::string& user_id)
: user_id_(user_id) {}
~ServiceUserIdHolder() override {}
const std::string& user_id() const { return user_id_; }
private:
std::string user_id_;
DISALLOW_COPY_AND_ASSIGN(ServiceUserIdHolder);
};
// Eliminates the mapping from |browser_state|'s associated userId (if any) to
// |browser_state|.
void RemoveBrowserStateFromUserIdMap(BrowserState* browser_state) {
ServiceUserIdHolder* holder = static_cast<ServiceUserIdHolder*>(
browser_state->GetUserData(kServiceUserId));
if (holder) {
g_user_id_to_browser_state.Get().erase(holder->user_id());
}
}
// Container for a ServiceManagerConnection to support association between
// a BrowserState and the ServiceManagerConnection initiated on behalf of that
// BrowserState.
class BrowserStateServiceManagerConnectionHolder
: public base::SupportsUserData::Data {
public:
explicit BrowserStateServiceManagerConnectionHolder(
service_manager::mojom::ServiceRequest request)
: service_manager_connection_(ServiceManagerConnection::Create(
std::move(request),
WebThread::GetTaskRunnerForThread(WebThread::IO))) {}
~BrowserStateServiceManagerConnectionHolder() override {}
ServiceManagerConnection* service_manager_connection() {
return service_manager_connection_.get();
}
private:
std::unique_ptr<ServiceManagerConnection> service_manager_connection_;
DISALLOW_COPY_AND_ASSIGN(BrowserStateServiceManagerConnectionHolder);
};
} // namespace
// static
scoped_refptr<CertificatePolicyCache> BrowserState::GetCertificatePolicyCache(
BrowserState* browser_state) {
......@@ -85,6 +151,12 @@ BrowserState::BrowserState() : url_data_manager_ios_backend_(nullptr) {
}
BrowserState::~BrowserState() {
CHECK(GetUserData(kMojoWasInitialized))
<< "Attempting to destroy a BrowserState that never called "
<< "Initialize()";
RemoveBrowserStateFromUserIdMap(this);
// Delete the URLDataManagerIOSBackend instance on the IO thread if it has
// been created. Note that while this check can theoretically race with a
// call to |GetURLDataManagerIOSBackendOnIOThread()|, if any clients of this
......@@ -115,4 +187,93 @@ BrowserState* BrowserState::FromSupportsUserData(
}
return static_cast<BrowserState*>(supports_user_data);
}
// static
void BrowserState::Initialize(BrowserState* browser_state,
const base::FilePath& path) {
std::string new_id = base::GenerateGUID();
// Note: If the file service is ever used on iOS, code needs to be added here
// to have the file service associate |path| as the user dir of the user Id
// of |browser_state| (see corresponding code in
// content::BrowserContext::Initialize). crbug.com/739450
RemoveBrowserStateFromUserIdMap(browser_state);
g_user_id_to_browser_state.Get()[new_id] = browser_state;
browser_state->SetUserData(kServiceUserId,
base::MakeUnique<ServiceUserIdHolder>(new_id));
browser_state->SetUserData(kMojoWasInitialized,
base::MakeUnique<base::SupportsUserData::Data>());
ServiceManagerConnection* service_manager_connection =
ServiceManagerConnection::Get();
if (service_manager_connection && base::ThreadTaskRunnerHandle::IsSet()) {
// NOTE: Many unit tests create a TestBrowserState without initializing
// Mojo or the global service manager connection.
// Have the global service manager connection start an instance of the
// web_browser service that is associated with this BrowserState (via
// |new_id|).
service_manager::mojom::ServicePtr service;
auto service_request = mojo::MakeRequest(&service);
service_manager::mojom::PIDReceiverPtr pid_receiver;
service_manager::Identity identity(mojom::kBrowserServiceName, new_id);
service_manager_connection->GetConnector()->StartService(
identity, std::move(service), mojo::MakeRequest(&pid_receiver));
pid_receiver->SetPID(base::GetCurrentProcId());
service_manager_connection->GetConnector()->StartService(identity);
auto connection_holder =
base::MakeUnique<BrowserStateServiceManagerConnectionHolder>(
std::move(service_request));
ServiceManagerConnection* connection =
connection_holder->service_manager_connection();
browser_state->SetUserData(kServiceManagerConnection,
std::move(connection_holder));
// New embedded service factories should be added to |connection| here.
WebClient::StaticServiceMap services;
browser_state->RegisterServices(&services);
for (const auto& entry : services) {
connection->AddEmbeddedService(entry.first, entry.second);
}
connection->Start();
}
}
// static
const std::string& BrowserState::GetServiceUserIdFor(
BrowserState* browser_state) {
CHECK(browser_state->GetUserData(kMojoWasInitialized))
<< "Attempting to get the mojo user id for a BrowserState that was "
<< "never Initialize()ed.";
ServiceUserIdHolder* holder = static_cast<ServiceUserIdHolder*>(
browser_state->GetUserData(kServiceUserId));
return holder->user_id();
}
// static
service_manager::Connector* BrowserState::GetConnectorFor(
BrowserState* browser_state) {
ServiceManagerConnection* connection =
GetServiceManagerConnectionFor(browser_state);
return connection ? connection->GetConnector() : nullptr;
}
// static
ServiceManagerConnection* BrowserState::GetServiceManagerConnectionFor(
BrowserState* browser_state) {
BrowserStateServiceManagerConnectionHolder* connection_holder =
static_cast<BrowserStateServiceManagerConnectionHolder*>(
browser_state->GetUserData(kServiceManagerConnection));
return connection_holder ? connection_holder->service_manager_connection()
: nullptr;
}
} // namespace web
......@@ -4,6 +4,9 @@
"interface_provider_specs": {
"service_manager:connector": {
"provides": {
"service_manager:service_factory": [
"service_manager::mojom::ServiceFactory"
]
},
"requires": {
"*": [ "app" ],
......
......@@ -6,6 +6,7 @@
#define IOS_WEB_PUBLIC_BROWSER_STATE_H_
#include "base/supports_user_data.h"
#include "services/service_manager/embedder/embedded_service_info.h"
namespace base {
class FilePath;
......@@ -15,9 +16,14 @@ namespace net {
class URLRequestContextGetter;
}
namespace service_manager {
class Connector;
}
namespace web {
class ActiveStateManager;
class CertificatePolicyCache;
class ServiceManagerConnection;
class URLDataManagerIOS;
class URLDataManagerIOSBackend;
class URLRequestChromeJob;
......@@ -60,9 +66,37 @@ class BrowserState : public base::SupportsUserData {
static BrowserState* FromSupportsUserData(
base::SupportsUserData* supports_user_data);
// Returns a Service User ID associated with this BrowserState. This ID is
// not persistent across runs. See
// services/service_manager/public/interfaces/connector.mojom. By default,
// this user id is randomly generated when Initialize() is called.
static const std::string& GetServiceUserIdFor(BrowserState* browser_state);
// Returns a Connector associated with this BrowserState, which can be used
// to connect to service instances bound as this user.
static service_manager::Connector* GetConnectorFor(
BrowserState* browser_state);
// Returns a ServiceManagerConnection associated with this BrowserState,
// which can be used to connect to service instances bound as this user.
static ServiceManagerConnection* GetServiceManagerConnectionFor(
BrowserState* browser_state);
using StaticServiceMap =
std::map<std::string, service_manager::EmbeddedServiceInfo>;
// Registers per-browser-state services to be loaded by the Service Manager.
virtual void RegisterServices(StaticServiceMap* services) {}
protected:
BrowserState();
// Makes the Service Manager aware of this BrowserState, and assigns a user
// ID number to it. Must be called for each BrowserState created. |path|
// should be the same path that would be returned by GetStatePath().
static void Initialize(BrowserState* browser_state,
const base::FilePath& path);
private:
friend class URLDataManagerIOS;
friend class URLRequestChromeJob;
......
......@@ -43,7 +43,9 @@ class TestContextURLRequestContextGetter : public net::URLRequestContextGetter {
} // namespace
TestBrowserState::TestBrowserState() : is_off_the_record_(false) {}
TestBrowserState::TestBrowserState() : is_off_the_record_(false) {
BrowserState::Initialize(this, GetStatePath());
}
TestBrowserState::~TestBrowserState() {}
......
......@@ -268,7 +268,11 @@ void ServiceManagerConnection::Set(
// static
ServiceManagerConnection* ServiceManagerConnection::Get() {
DCHECK_CURRENTLY_ON(WebThread::UI);
// WebThreads are not initialized in many unit tests. These tests also by
// definition are not setting the global ServiceManagerConnection (since
// otherwise the DCHECK in the above method would fire).
DCHECK(!web::WebThread::IsThreadInitialized(web::WebThread::UI) ||
web::WebThread::CurrentlyOn(web::WebThread::UI));
return g_connection_for_process.Get().get();
}
......
......@@ -44,6 +44,7 @@ service_manifest("shell_packaged_services_manifest_overlay") {
service_manifest("shell_browser_manifest_overlay") {
source = "web_shell_browser_manifest_overlay.json"
packaged_services = [ "//services/test/user_id:manifest" ]
}
grit("resources") {
......@@ -112,6 +113,8 @@ source_set("shell") {
"//net:extras",
"//services/test/echo:lib",
"//services/test/echo/public/interfaces",
"//services/test/user_id:lib",
"//services/test/user_id/public/interfaces",
"//ui/base",
]
......
......@@ -4,5 +4,6 @@ include_rules = [
"+ios/web/public",
"+ios/web/shell",
"+services/test/echo",
"+services/test/user_id",
]
......@@ -25,6 +25,7 @@ class ShellBrowserState : public BrowserState {
bool IsOffTheRecord() const override;
base::FilePath GetStatePath() const override;
net::URLRequestContextGetter* GetRequestContext() override;
void RegisterServices(StaticServiceMap* services) override;
private:
base::FilePath path_;
......
......@@ -11,6 +11,7 @@
#include "base/threading/thread_restrictions.h"
#include "ios/web/public/web_thread.h"
#include "ios/web/shell/shell_url_request_context_getter.h"
#include "services/test/user_id/user_id_service.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
......@@ -26,6 +27,8 @@ ShellBrowserState::ShellBrowserState() : BrowserState() {
web::WebThread::GetTaskRunnerForThread(web::WebThread::IO),
web::WebThread::GetTaskRunnerForThread(web::WebThread::FILE),
web::WebThread::GetTaskRunnerForThread(web::WebThread::CACHE));
BrowserState::Initialize(this, path_);
}
ShellBrowserState::~ShellBrowserState() {
......@@ -43,4 +46,11 @@ net::URLRequestContextGetter* ShellBrowserState::GetRequestContext() {
return request_context_getter_.get();
}
void ShellBrowserState::RegisterServices(StaticServiceMap* services) {
service_manager::EmbeddedServiceInfo user_id_info;
user_id_info.factory = base::Bind(&user_id::CreateUserIdService);
user_id_info.task_runner = base::ThreadTaskRunnerHandle::Get();
services->insert(std::make_pair("user_id", user_id_info));
}
} // namespace web
......@@ -40,6 +40,7 @@ ios_eg_test("ios_web_shell_egtests") {
"//net",
"//services/service_manager/public/cpp",
"//services/test/echo/public/interfaces",
"//services/test/user_id/public/interfaces",
"//url",
# All shared libraries must have the sanitizer deps to properly link in
......
......@@ -7,11 +7,14 @@
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#import "ios/testing/wait_util.h"
#include "ios/web/public/browser_state.h"
#include "ios/web/public/service_manager_connection.h"
#import "ios/web/shell/test/app/web_shell_test_util.h"
#import "ios/web/shell/test/earl_grey/shell_earl_grey.h"
#import "ios/web/shell/test/earl_grey/web_shell_test_case.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/test/echo/public/interfaces/echo.mojom.h"
#include "services/test/user_id/public/interfaces/user_id.mojom.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
......@@ -34,16 +37,33 @@ void OnEchoString(echo::mojom::EchoPtr echo,
*echo_callback_called_flag = true;
}
// Waits until the callback to echo::mojom::Echo::EchoString() is invoked (as
// signalled by that callback setting |echo_callback_called_flag| to true).
void WaitForEchoStringCallback(bool* echo_callback_called_flag) {
// Callback passed to user_id::mojom::UserId::GetUserId(). Verifies that the
// passed-back user ID has the expected value and sets
// |user_id_callback_called_flag| to true to indicate that the callback was
// invoked. |user_id| is passed simply to ensure that our connection to the
// UserId implementation remains alive long enough for the callback to reach
// us.
void OnGotUserId(user_id::mojom::UserIdPtr user_id,
bool* user_id_callback_called_flag,
const std::string& expected_user_id,
const std::string& received_user_id) {
GREYAssert(expected_user_id == received_user_id,
@"Unexpected User ID passed to user_id callback: %s",
received_user_id.c_str());
*user_id_callback_called_flag = true;
}
// Waits until a given callback is invoked (as signalled by that callback
// setting |callback_called_flag| to true).
void WaitForCallback(const std::string& callback_name,
bool* callback_called_flag) {
GREYCondition* condition =
[GREYCondition conditionWithName:@"Wait for echo string callback"
[GREYCondition conditionWithName:@"Wait for callback"
block:^BOOL {
return *echo_callback_called_flag;
return *callback_called_flag;
}];
GREYAssert([condition waitWithTimeout:testing::kWaitForUIElementTimeout],
@"Failed waiting for echo callback");
@"Failed waiting for %s callback", callback_name.c_str());
}
}
......@@ -53,9 +73,9 @@ void WaitForEchoStringCallback(bool* echo_callback_called_flag) {
@implementation ServiceManagerTestCase
// Tests that it is possible to connect to an embedded service that was
// registered by web_shell.
- (void)testConnectionToEmbeddedService {
// Tests that it is possible to connect to an all-users embedded service that
// was registered by web_shell.
- (void)testConnectionToAllUsersEmbeddedService {
// Connect to the echo service and bind an Echo instance.
echo::mojom::EchoPtr echo;
web::ServiceManagerConnection* connection =
......@@ -65,13 +85,33 @@ void WaitForEchoStringCallback(bool* echo_callback_called_flag) {
// Call EchoString, making sure to keep our end of the connection alive
// until the callback is received.
echo::mojom::Echo* raw_echo = echo.get();
bool echo_callback_called = false;
raw_echo->EchoString(kTestInput,
base::BindOnce(&OnEchoString, base::Passed(&echo),
&echo_callback_called));
echo::mojom::Echo* rawEcho = echo.get();
bool echoCallbackCalled = false;
rawEcho->EchoString(
kTestInput,
base::BindOnce(&OnEchoString, base::Passed(&echo), &echoCallbackCalled));
WaitForCallback("EchoString", &echoCallbackCalled);
}
// Tests that it is possible to connect to a per-user embedded service that
// was registered by web_shell.
- (void)testConnectionToPerUserEmbeddedService {
// Connect to the user ID service and bind a UserId instance.
user_id::mojom::UserIdPtr userID;
web::WebState* webState = web::shell_test_util::GetCurrentWebState();
web::BrowserState::GetConnectorFor(webState->GetBrowserState())
->BindInterface("user_id", mojo::MakeRequest(&userID));
// Call GetUserId(), making sure to keep our end of the connection alive
// until the callback is received.
user_id::mojom::UserId* rawUserID = userID.get();
bool userIDCallbackCalled = false;
rawUserID->GetUserId(base::BindOnce(
&OnGotUserId, base::Passed(&userID), &userIDCallbackCalled,
web::BrowserState::GetServiceUserIdFor(webState->GetBrowserState())));
WaitForEchoStringCallback(&echo_callback_called);
WaitForCallback("GetUserId", &userIDCallbackCalled);
}
@end
......@@ -3,7 +3,8 @@
"interface_provider_specs": {
"service_manager:connector": {
"requires": {
"echo": [ "echo" ]
"echo": [ "echo" ],
"user_id": [ "user_id" ]
}
}
}
......
......@@ -50,6 +50,8 @@ WebViewBrowserState::WebViewBrowserState(bool off_the_record)
web::WebThread::GetTaskRunnerForThread(web::WebThread::FILE),
web::WebThread::GetTaskRunnerForThread(web::WebThread::CACHE));
BrowserState::Initialize(this, path_);
// Initialize prefs.
scoped_refptr<user_prefs::PrefRegistrySyncable> pref_registry =
new user_prefs::PrefRegistrySyncable;
......
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