Commit 32c803d0 authored by Christopher Cameron's avatar Christopher Cameron Committed by Commit Bot

MacPWAs: Open all last-active profiles at app launch

Restructure how shims connect to be as follows
* (the shim process connects and creates an AppShimHostBootstrap)
* OnShimProcessConnected is called with the bootstrap, and then split
  to two paths
  * OnShimProcessConnectedForLaunch if we should open a new window
    * (e.g, user clicked on the icon)
    * Use the AppShimRegistry to create a list of windows to open
    * Potentially-asynchronously load the profiles, and then
    * OnShimProcessConnectedForLaunch will go through those profiles
      and create new app windows for them
      * If the app shim is an "old" shim that specified a profile, then
        only open the specified profile
  * OnShimProcessConnectedForRegisterOnly if not
    * (e.g, Chrome created an app window on its own and we're starting
      the shim to display it in its own process)
    * Search for an existing AppShimHost to connect to
    * Create one if needed only because tests want this (that cleanup
      can come later)
* OnShimProcessConnectedAndAllLaunchesDone is then called by both of
  the above branches. This function will
  * If the above-branch found or created an AppShimHost, then connect
    the bootstrap to it
  * Otherwise, tell the bootstrap that it should quit.

The big difference in structure here is how we load profiles. Prior to
this change, we
- Had OnShimProcessConnected asynchronously load the list of profiles
  for which the app is installed
- Then selected one profile, send it to LoadProfileAndApp, and only
  attached the shim to that one profile.
By contrast, we now
- Read profiles from the AppShimRegistry
- Load all profiles we may want to launch via a chain of calls to
  LoadProfileAndApp

TBR=dominickn

Bug: 1001213
Change-Id: I4b4355cc79606fc33a6de926af00bce48c086100
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1928016Reviewed-by: default avatarccameron <ccameron@chromium.org>
Commit-Queue: ccameron <ccameron@chromium.org>
Cr-Commit-Position: refs/heads/master@{#719201}
parent e3aeec40
...@@ -235,12 +235,19 @@ struct ExtensionAppShimHandler::ProfileState { ...@@ -235,12 +235,19 @@ struct ExtensionAppShimHandler::ProfileState {
// The state for an individual app. This includes the state for all // The state for an individual app. This includes the state for all
// profiles that are using the app. // profiles that are using the app.
struct ExtensionAppShimHandler::AppState { struct ExtensionAppShimHandler::AppState {
AppState(std::unique_ptr<AppShimHost> in_multi_profile_host) AppState(const std::string& app_id,
: multi_profile_host(std::move(in_multi_profile_host)) {} std::unique_ptr<AppShimHost> multi_profile_host)
: app_id(app_id), multi_profile_host(std::move(multi_profile_host)) {}
~AppState() = default; ~AppState() = default;
bool IsMultiProfile() const; bool IsMultiProfile() const;
// Mark the last-active profiles in AppShimRegistry, so that they will re-open
// when the app is started next.
void SaveLastActiveProfiles() const;
const std::string app_id;
// Multi-profile apps share the same shim process across multiple profiles. // Multi-profile apps share the same shim process across multiple profiles.
const std::unique_ptr<AppShimHost> multi_profile_host; const std::unique_ptr<AppShimHost> multi_profile_host;
...@@ -274,6 +281,17 @@ bool ExtensionAppShimHandler::AppState::IsMultiProfile() const { ...@@ -274,6 +281,17 @@ bool ExtensionAppShimHandler::AppState::IsMultiProfile() const {
return multi_profile_host.get(); return multi_profile_host.get();
} }
void ExtensionAppShimHandler::AppState::SaveLastActiveProfiles() const {
if (!IsMultiProfile())
return;
std::set<base::FilePath> last_active_profile_paths;
for (auto iter_profile = profiles.begin(); iter_profile != profiles.end();
++iter_profile) {
last_active_profile_paths.insert(iter_profile->first->GetPath());
}
AppShimRegistry::Get()->OnAppQuit(app_id, last_active_profile_paths);
}
Profile* ExtensionAppShimHandler::Delegate::ProfileForPath( Profile* ExtensionAppShimHandler::Delegate::ProfileForPath(
const base::FilePath& full_path) { const base::FilePath& full_path) {
ProfileManager* profile_manager = g_browser_process->profile_manager(); ProfileManager* profile_manager = g_browser_process->profile_manager();
...@@ -520,14 +538,227 @@ void ExtensionAppShimHandler::OnShimLaunchRequested( ...@@ -520,14 +538,227 @@ void ExtensionAppShimHandler::OnShimLaunchRequested(
void ExtensionAppShimHandler::OnShimProcessConnected( void ExtensionAppShimHandler::OnShimProcessConnected(
std::unique_ptr<AppShimHostBootstrap> bootstrap) { std::unique_ptr<AppShimHostBootstrap> bootstrap) {
switch (bootstrap->GetLaunchType()) {
case chrome::mojom::AppShimLaunchType::kNormal:
OnShimProcessConnectedForLaunch(std::move(bootstrap));
break;
case chrome::mojom::AppShimLaunchType::kRegisterOnly:
OnShimProcessConnectedForRegisterOnly(std::move(bootstrap));
break;
}
}
void ExtensionAppShimHandler::OnShimProcessConnectedForRegisterOnly(
std::unique_ptr<AppShimHostBootstrap> bootstrap) {
const std::string& app_id = bootstrap->GetAppId(); const std::string& app_id = bootstrap->GetAppId();
DCHECK(crx_file::id_util::IdIsValid(app_id)); DCHECK(crx_file::id_util::IdIsValid(app_id));
DCHECK_EQ(bootstrap->GetLaunchType(),
chrome::mojom::AppShimLaunchType::kRegisterOnly);
GetProfilesForAppAsync( // Create a ProfileState the specified profile (if there is one). We should
app_id, // not do this (if there exists no ProfileState, then the shim should just
base::BindOnce( // exit), but many tests assume this behavior, and need to be updated.
&ExtensionAppShimHandler::OnShimProcessConnectedAndProfilesRetrieved, Profile* profile = delegate_->ProfileForPath(bootstrap->GetProfilePath());
weak_factory_.GetWeakPtr(), std::move(bootstrap))); const Extension* extension = delegate_->MaybeGetAppExtension(profile, app_id);
if (profile && extension && delegate_->AllowShimToConnect(profile, extension))
GetOrCreateProfileState(profile, extension);
// Because this was a register-only launch, it must have been launched by
// Chrome, and so there should probably still exist the ProfileState through
// which the launch was originally done.
ProfileState* profile_state = nullptr;
auto found_app = apps_.find(app_id);
if (found_app != apps_.end()) {
AppState* app_state = found_app->second.get();
if (app_state->IsMultiProfile()) {
DCHECK(!app_state->profiles.empty());
profile_state = app_state->profiles.begin()->second.get();
} else {
auto found_profile = app_state->profiles.find(profile);
if (found_profile != app_state->profiles.end()) {
profile_state = found_profile->second.get();
}
}
}
OnShimProcessConnectedAndAllLaunchesDone(
profile_state,
profile_state ? chrome::mojom::AppShimLaunchResult::kSuccess
: chrome::mojom::AppShimLaunchResult::kNoHost,
std::move(bootstrap));
}
void ExtensionAppShimHandler::OnShimProcessConnectedForLaunch(
std::unique_ptr<AppShimHostBootstrap> bootstrap) {
const std::string& app_id = bootstrap->GetAppId();
DCHECK(crx_file::id_util::IdIsValid(app_id));
DCHECK_EQ(bootstrap->GetLaunchType(),
chrome::mojom::AppShimLaunchType::kNormal);
// Retrieve the list of last-active profiles. If there are no last-active
// profiles (which is rare -- e.g, when the last-active profiles were
// removed), then use all profiles for which the app is installed.
std::set<base::FilePath> last_active_profile_paths =
AppShimRegistry::Get()->GetLastActiveProfilesForApp(app_id);
if (last_active_profile_paths.empty()) {
last_active_profile_paths =
AppShimRegistry::Get()->GetInstalledProfilesForApp(app_id);
}
// Construct |profile_paths_to_launch| to be the list of all profiles to
// attempt to launch, starting with the profile specified in |bootstrap|,
// at the front of the list.
std::vector<base::FilePath> profile_paths_to_launch = {
bootstrap->GetProfilePath()};
for (const auto& profile_path : last_active_profile_paths)
profile_paths_to_launch.push_back(profile_path);
// Attempt load all of the profiles in |profile_paths_to_launch|, and once
// they're loaded (or have failed to load), call
// OnShimProcessConnectedAndProfilesToLaunchLoaded.
base::OnceClosure callback = base::BindOnce(
&ExtensionAppShimHandler::OnShimProcessConnectedAndProfilesToLaunchLoaded,
weak_factory_.GetWeakPtr(), std::move(bootstrap),
profile_paths_to_launch);
{
// This will update |callback| to be a chain of callbacks that load the
// profiles in |profile_paths_to_load|, one by one, using
// LoadProfileAndApp, and then finally call the initial |callback|. This
// may end up being async (if some profiles aren't loaded), or may be
// synchronous (if all profiles happen to already be loaded).
for (const auto& profile_path : profile_paths_to_launch) {
if (profile_path.empty())
continue;
LoadProfileAppCallback callback_wrapped = base::BindOnce(
[](base::OnceClosure callback_to_wrap, Profile*,
const extensions::Extension*) {
std::move(callback_to_wrap).Run();
},
std::move(callback));
callback = base::BindOnce(&ExtensionAppShimHandler::LoadProfileAndApp,
weak_factory_.GetWeakPtr(), profile_path,
app_id, std::move(callback_wrapped));
}
}
std::move(callback).Run();
}
void ExtensionAppShimHandler::OnShimProcessConnectedAndProfilesToLaunchLoaded(
std::unique_ptr<AppShimHostBootstrap> bootstrap,
const std::vector<base::FilePath>& profile_paths_to_launch) {
// The the profile specified in |bootstrap| (even if it's empty) should be the
// first profile listed in |profile_paths_to_launch|.
DCHECK_EQ(profile_paths_to_launch[0], bootstrap->GetProfilePath());
const auto& app_id = bootstrap->GetAppId();
auto launch_files = bootstrap->GetLaunchFiles();
// Launch all of the profiles in |profile_paths_to_launch|. Record the most
// profile successfully launched in |profile_state|, and the most recent
// reason for a failure (if any) in |launch_result|.
ProfileState* profile_state = nullptr;
auto launch_result = chrome::mojom::AppShimLaunchResult::kNoHost;
for (size_t i = 0; i < profile_paths_to_launch.size(); ++i) {
const base::FilePath& profile_path = profile_paths_to_launch[i];
if (profile_path.empty()) {
launch_result = chrome::mojom::AppShimLaunchResult::kProfileNotFound;
continue;
}
if (delegate_->IsProfileLockedForPath(profile_path)) {
launch_result = chrome::mojom::AppShimLaunchResult::kProfileLocked;
continue;
}
Profile* profile = delegate_->ProfileForPath(profile_path);
if (!profile) {
launch_result = chrome::mojom::AppShimLaunchResult::kProfileNotFound;
continue;
}
const Extension* extension =
delegate_->MaybeGetAppExtension(profile, app_id);
if (!extension) {
launch_result = chrome::mojom::AppShimLaunchResult::kAppNotFound;
continue;
}
// Create a ProfileState for this app. We will connect |bootstrap| to it
// after all profiles have been launched.
if (delegate_->AllowShimToConnect(profile, extension))
profile_state = GetOrCreateProfileState(profile, extension);
// Launch the app (that is, open a browser window for it). Only pass
// |launch_files| to the first profile opened.
delegate_->LaunchApp(profile, extension, launch_files);
launch_files.clear();
// If this was the first profile in |profile_paths_to_launch|, then this
// was the profile specified in the bootstrap, so stop here.
if (i == 0)
break;
}
// If we launched any profile, report success.
if (profile_state)
launch_result = chrome::mojom::AppShimLaunchResult::kSuccess;
OnShimProcessConnectedAndAllLaunchesDone(profile_state, launch_result,
std::move(bootstrap));
}
void ExtensionAppShimHandler::OnShimProcessConnectedAndAllLaunchesDone(
ProfileState* profile_state,
chrome::mojom::AppShimLaunchResult result,
std::unique_ptr<AppShimHostBootstrap> bootstrap) {
// If we failed because the profile was locked, launch the profile manager.
if (result == chrome::mojom::AppShimLaunchResult::kProfileLocked)
delegate_->LaunchUserManager();
// If we failed to launch the app at all, report that.
if (result != chrome::mojom::AppShimLaunchResult::kSuccess) {
bootstrap->OnFailedToConnectToHost(result);
return;
}
// Find an AppShimHost that |bootstrap| can connect to. This may be because no
// host was created (e.g, open-in-a-tab bookmarks) or because the app was
// closed were closed between callbacks.
if (!profile_state) {
bootstrap->OnFailedToConnectToHost(
chrome::mojom::AppShimLaunchResult::kNoHost);
return;
}
AppShimHost* host = profile_state->GetHost();
DCHECK(host);
// If we already have a host attached (e.g, due to multiple launches racing),
// close down the app shim that didn't win the race.
if (host->HasBootstrapConnected()) {
// If another app shim process has already connected to this (profile,
// app_id) pair, then focus the windows for the existing process. Note
// that this only does anything for non-RemoveCocoa apps.
OnShimFocus(
profile_state->GetHost(),
bootstrap->GetLaunchType() == chrome::mojom::AppShimLaunchType::kNormal
? chrome::mojom::AppShimFocusType::kReopen
: chrome::mojom::AppShimFocusType::kNormal,
bootstrap->GetLaunchFiles());
bootstrap->OnFailedToConnectToHost(
chrome::mojom::AppShimLaunchResult::kDuplicateHost);
return;
}
// If the connecting shim process doesn't have an acceptable code
// signature, reject the connection and re-launch the shim. The internal
// re-launch will likely fail, whereupon the shim will be recreated.
if (!IsAcceptablyCodeSigned(bootstrap->GetAppShimPid())) {
LOG(ERROR) << "The attaching app shim's code signature is invalid.";
bootstrap->OnFailedToConnectToHost(
chrome::mojom::AppShimLaunchResult::kFailedValidation);
host->LaunchShim();
return;
}
host->OnBootstrapConnected(std::move(bootstrap));
} }
// static // static
...@@ -558,9 +789,15 @@ void ExtensionAppShimHandler::CloseShimForApp(Profile* profile, ...@@ -558,9 +789,15 @@ void ExtensionAppShimHandler::CloseShimForApp(Profile* profile,
if (found_app == apps_.end()) if (found_app == apps_.end())
return; return;
AppState* app_state = found_app->second.get(); AppState* app_state = found_app->second.get();
app_state->profiles.erase(profile); auto found_profile = app_state->profiles.find(profile);
if (app_state->profiles.empty()) if (found_profile == app_state->profiles.end())
return;
if (app_state->profiles.size() == 1) {
app_state->SaveLastActiveProfiles();
apps_.erase(found_app); apps_.erase(found_app);
} else {
app_state->profiles.erase(found_profile);
}
} }
void ExtensionAppShimHandler::LoadProfileAndApp( void ExtensionAppShimHandler::LoadProfileAndApp(
...@@ -620,142 +857,6 @@ void ExtensionAppShimHandler::OnAppEnabled(const base::FilePath& profile_path, ...@@ -620,142 +857,6 @@ void ExtensionAppShimHandler::OnAppEnabled(const base::FilePath& profile_path,
std::move(callback).Run(profile, extension); std::move(callback).Run(profile, extension);
} }
base::FilePath ExtensionAppShimHandler::SelectProfileForApp(
const std::string& app_id,
const base::FilePath& specified_profile_path,
const std::vector<base::FilePath>& installed_profile_paths) const {
DCHECK(!installed_profile_paths.empty());
// If the specified profile path is valid, and the app is installed for that
// profile, then use the specified profile.
if (!specified_profile_path.empty()) {
if (base::Contains(installed_profile_paths, specified_profile_path))
return specified_profile_path;
}
// If the app is active for a profile, use the profile for which the app
// is active.
auto found_app = apps_.find(app_id);
if (found_app != apps_.end()) {
AppState* app_state = found_app->second.get();
DCHECK(app_state);
if (!app_state->profiles.empty()) {
Profile* active_profile = app_state->profiles.begin()->first;
return active_profile->GetPath();
}
}
// See if there is a registered last-active profile.
{
std::set<base::FilePath> last_active_paths =
AppShimRegistry::Get()->GetLastActiveProfilesForApp(app_id);
if (!last_active_paths.empty()) {
// TODO(https://crbug.com/1001213): Allow opening multiple profiles at
// once.
return *last_active_paths.begin();
}
}
// Otherwise, return an arbitrary profile from |installed_profile_paths|.
return installed_profile_paths.front();
}
void ExtensionAppShimHandler::OnShimProcessConnectedAndProfilesRetrieved(
std::unique_ptr<AppShimHostBootstrap> bootstrap,
const std::vector<base::FilePath>& profile_paths) {
// If the app is installed for no profiles, quit.
if (profile_paths.empty()) {
LOG(ERROR) << "App " << bootstrap->GetAppId()
<< " installed for no profiles.";
bootstrap->OnFailedToConnectToHost(
chrome::mojom::AppShimLaunchResult::kProfileNotFound);
return;
}
std::string app_id = bootstrap->GetAppId();
base::FilePath profile_path =
SelectProfileForApp(app_id, bootstrap->GetProfilePath(), profile_paths);
if (delegate_->IsProfileLockedForPath(profile_path)) {
LOG(WARNING) << "Requested profile is locked. Showing User Manager.";
bootstrap->OnFailedToConnectToHost(
chrome::mojom::AppShimLaunchResult::kProfileLocked);
delegate_->LaunchUserManager();
return;
}
LoadProfileAndApp(
profile_path, app_id,
base::BindOnce(
&ExtensionAppShimHandler::OnShimProcessConnectedAndAppLoaded,
weak_factory_.GetWeakPtr(), std::move(bootstrap)));
}
void ExtensionAppShimHandler::OnShimProcessConnectedAndAppLoaded(
std::unique_ptr<AppShimHostBootstrap> bootstrap,
Profile* profile,
const extensions::Extension* extension) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Early-out if the profile or extension failed to load.
if (!profile) {
bootstrap->OnFailedToConnectToHost(
chrome::mojom::AppShimLaunchResult::kProfileNotFound);
return;
}
if (!extension) {
bootstrap->OnFailedToConnectToHost(
chrome::mojom::AppShimLaunchResult::kAppNotFound);
return;
}
std::string app_id = bootstrap->GetAppId();
chrome::mojom::AppShimLaunchType launch_type = bootstrap->GetLaunchType();
std::vector<base::FilePath> files = bootstrap->GetLaunchFiles();
ProfileState* profile_state =
delegate_->AllowShimToConnect(profile, extension)
? GetOrCreateProfileState(profile, extension)
: nullptr;
if (profile_state) {
DCHECK_EQ(profile_state->app_state->IsMultiProfile(),
bootstrap->IsMultiProfile());
AppShimHost* host = profile_state->GetHost();
if (host->HasBootstrapConnected()) {
// If another app shim process has already connected to this (profile,
// app_id) pair, then focus the windows for the existing process, and
// close the new process.
OnShimFocus(host,
launch_type == chrome::mojom::AppShimLaunchType::kNormal
? chrome::mojom::AppShimFocusType::kReopen
: chrome::mojom::AppShimFocusType::kNormal,
files);
bootstrap->OnFailedToConnectToHost(
chrome::mojom::AppShimLaunchResult::kDuplicateHost);
return;
}
if (IsAcceptablyCodeSigned(bootstrap->GetAppShimPid())) {
host->OnBootstrapConnected(std::move(bootstrap));
} else {
// If the connecting shim process doesn't have an acceptable code
// signature, reject the connection and re-launch the shim. The internal
// re-launch will likely fail, whereupon the shim will be recreated.
LOG(ERROR) << "The attaching app shim's code signature is invalid.";
bootstrap->OnFailedToConnectToHost(
chrome::mojom::AppShimLaunchResult::kFailedValidation);
host->LaunchShim();
}
} else {
// If it's an app that has a shim to launch it but shouldn't use a host
// (e.g, a hosted app that opens in a tab), terminate the shim, but still
// launch the app (that is, open the relevant browser tabs).
bootstrap->OnFailedToConnectToHost(
chrome::mojom::AppShimLaunchResult::kDuplicateHost);
}
// If this is not a register-only launch, then launch the app (that is, open
// a browser window for it).
if (launch_type == chrome::mojom::AppShimLaunchType::kNormal)
delegate_->LaunchApp(profile, extension, files);
}
bool ExtensionAppShimHandler::IsAcceptablyCodeSigned(pid_t pid) const { bool ExtensionAppShimHandler::IsAcceptablyCodeSigned(pid_t pid) const {
return IsAcceptablyCodeSignedInternal(pid); return IsAcceptablyCodeSignedInternal(pid);
} }
...@@ -771,13 +872,12 @@ void ExtensionAppShimHandler::OnShimProcessDisconnected(AppShimHost* host) { ...@@ -771,13 +872,12 @@ void ExtensionAppShimHandler::OnShimProcessDisconnected(AppShimHost* host) {
// For multi-profile apps, just delete the AppState, which will take down // For multi-profile apps, just delete the AppState, which will take down
// |host| and all profiles' state. // |host| and all profiles' state.
if (app_state->IsMultiProfile()) { if (app_state->IsMultiProfile()) {
app_state->SaveLastActiveProfiles();
DCHECK_EQ(host, app_state->multi_profile_host.get()); DCHECK_EQ(host, app_state->multi_profile_host.get());
apps_.erase(found_app); apps_.erase(found_app);
return; return;
} }
Profile* profile = delegate_->ProfileForPath(host->GetProfilePath());
// For non-RemoteCocoa apps, close all of the windows only if the the shim // For non-RemoteCocoa apps, close all of the windows only if the the shim
// process has successfully connected (if it never connected, then let the // process has successfully connected (if it never connected, then let the
// app run as normal). // app run as normal).
...@@ -785,6 +885,7 @@ void ExtensionAppShimHandler::OnShimProcessDisconnected(AppShimHost* host) { ...@@ -785,6 +885,7 @@ void ExtensionAppShimHandler::OnShimProcessDisconnected(AppShimHost* host) {
!host->UsesRemoteViews() && host->HasBootstrapConnected(); !host->UsesRemoteViews() && host->HasBootstrapConnected();
// Erase the ProfileState, which will delete |host|. // Erase the ProfileState, which will delete |host|.
Profile* profile = delegate_->ProfileForPath(host->GetProfilePath());
auto found_profile = app_state->profiles.find(profile); auto found_profile = app_state->profiles.find(profile);
DCHECK(found_profile != app_state->profiles.end()); DCHECK(found_profile != app_state->profiles.end());
ProfileState* profile_state = found_profile->second.get(); ProfileState* profile_state = found_profile->second.get();
...@@ -1088,7 +1189,7 @@ ExtensionAppShimHandler::GetOrCreateProfileState( ...@@ -1088,7 +1189,7 @@ ExtensionAppShimHandler::GetOrCreateProfileState(
delegate_->CreateHost(this, profile_path, app_id, use_remote_cocoa); delegate_->CreateHost(this, profile_path, app_id, use_remote_cocoa);
} }
auto new_app_state = auto new_app_state =
std::make_unique<AppState>(std::move(multi_profile_host)); std::make_unique<AppState>(app_id, std::move(multi_profile_host));
found_app = found_app =
apps_.insert(std::make_pair(app_id, std::move(new_app_state))).first; apps_.insert(std::make_pair(app_id, std::move(new_app_state))).first;
} }
......
...@@ -218,26 +218,31 @@ class ExtensionAppShimHandler : public AppShimHostBootstrap::Client, ...@@ -218,26 +218,31 @@ class ExtensionAppShimHandler : public AppShimHostBootstrap::Client,
// Close one specified app. // Close one specified app.
void CloseShimForApp(Profile* profile, const std::string& app_id); void CloseShimForApp(Profile* profile, const std::string& app_id);
// Return the profile that should be opened for |app_id|, preferring // This is called by OnShimProcessConnected if the app shim was launched by
// |specified_profile_path| if is valid, otherwise prefering the most recently // Chrome, and should connect to an already-existing AppShimHost.
// used of |profile_paths|. void OnShimProcessConnectedForRegisterOnly(
base::FilePath SelectProfileForApp( std::unique_ptr<AppShimHostBootstrap> bootstrap);
const std::string& app_id,
const base::FilePath& specified_profile_path, // This is called by OnShimProcessConnected when the shim was was launched
const std::vector<base::FilePath>& profile_paths) const; // not by Chrome, and needs to launch the app (that is, open a new app
// window).
// Continuation of OnShimProcessConnected, once the query for all profiles void OnShimProcessConnectedForLaunch(
// with the app installed has returned.profiles std::unique_ptr<AppShimHostBootstrap> bootstrap);
void OnShimProcessConnectedAndProfilesRetrieved(
// Continuation of OnShimProcessConnectedForLaunch, once all of the profiles
// to use have been loaded. The list of profiles to launch is in
// |profile_paths_to_launch|. The first entry corresponds to the bootstrap-
// specified profile, and may be a blank path.
void OnShimProcessConnectedAndProfilesToLaunchLoaded(
std::unique_ptr<AppShimHostBootstrap> bootstrap, std::unique_ptr<AppShimHostBootstrap> bootstrap,
const std::vector<base::FilePath>& profiles); const std::vector<base::FilePath>& profile_paths_to_launch);
// Continuation of OnShimProcessConnectedAndProfilesRetrieved, once the // The final step of both paths for OnShimProcessConnected. This will connect
// decided profile has loaded. // |bootstrap| to |profile_state|'s AppShimHost, if possible.
void OnShimProcessConnectedAndAppLoaded( void OnShimProcessConnectedAndAllLaunchesDone(
std::unique_ptr<AppShimHostBootstrap> bootstrap, ProfileState* profile_state,
Profile* profile, chrome::mojom::AppShimLaunchResult result,
const extensions::Extension* extension); std::unique_ptr<AppShimHostBootstrap> bootstrap);
// Continuation of OnShimSelectedProfile, once the profile has loaded. // Continuation of OnShimSelectedProfile, once the profile has loaded.
void OnShimSelectedProfileAndAppLoaded( void OnShimSelectedProfileAndAppLoaded(
......
...@@ -49,7 +49,7 @@ using ::testing::WithArgs; ...@@ -49,7 +49,7 @@ using ::testing::WithArgs;
class MockDelegate : public ExtensionAppShimHandler::Delegate { class MockDelegate : public ExtensionAppShimHandler::Delegate {
public: public:
virtual ~MockDelegate() {} virtual ~MockDelegate() { DCHECK(load_profile_callbacks_.empty()); }
void GetProfilesForAppAsync( void GetProfilesForAppAsync(
const std::string& app_id, const std::string& app_id,
...@@ -131,14 +131,14 @@ class MockDelegate : public ExtensionAppShimHandler::Delegate { ...@@ -131,14 +131,14 @@ class MockDelegate : public ExtensionAppShimHandler::Delegate {
void CaptureLoadProfileCallback(const base::FilePath& path, void CaptureLoadProfileCallback(const base::FilePath& path,
base::OnceCallback<void(Profile*)> callback) { base::OnceCallback<void(Profile*)> callback) {
callbacks_[path] = std::move(callback); load_profile_callbacks_[path] = std::move(callback);
} }
bool RunLoadProfileCallback( bool RunLoadProfileCallback(
const base::FilePath& path, const base::FilePath& path,
Profile* profile) { Profile* profile) {
std::move(callbacks_[path]).Run(profile); std::move(load_profile_callbacks_[path]).Run(profile);
return callbacks_.erase(path); return load_profile_callbacks_.erase(path);
} }
bool RunGetProfilesForAppCallback() { bool RunGetProfilesForAppCallback() {
...@@ -152,7 +152,8 @@ class MockDelegate : public ExtensionAppShimHandler::Delegate { ...@@ -152,7 +152,8 @@ class MockDelegate : public ExtensionAppShimHandler::Delegate {
private: private:
ShimLaunchedCallback* launch_shim_callback_capture_ = nullptr; ShimLaunchedCallback* launch_shim_callback_capture_ = nullptr;
ShimTerminatedCallback* terminated_shim_callback_capture_ = nullptr; ShimTerminatedCallback* terminated_shim_callback_capture_ = nullptr;
std::map<base::FilePath, base::OnceCallback<void(Profile*)>> callbacks_; std::map<base::FilePath, base::OnceCallback<void(Profile*)>>
load_profile_callbacks_;
std::unique_ptr<AppShimHost> host_for_create_ = nullptr; std::unique_ptr<AppShimHost> host_for_create_ = nullptr;
std::list<base::OnceClosure> get_profiles_for_app_callbacks_; std::list<base::OnceClosure> get_profiles_for_app_callbacks_;
...@@ -323,15 +324,18 @@ class ExtensionAppShimHandlerTestBase : public testing::Test { ...@@ -323,15 +324,18 @@ class ExtensionAppShimHandlerTestBase : public testing::Test {
~ExtensionAppShimHandlerTestBase() override {} ~ExtensionAppShimHandlerTestBase() override {}
void SetUp() override { void SetUp() override {
profile_path_a_ = profile_a_.GetPath();
profile_path_b_ = profile_b_.GetPath();
profile_path_c_ = profile_c_.GetPath();
const base::FilePath user_data_dir = profile_path_a_.DirName();
local_state_ = std::make_unique<TestingPrefServiceSimple>(); local_state_ = std::make_unique<TestingPrefServiceSimple>();
AppShimRegistry::Get()->RegisterLocalPrefs(local_state_->registry()); AppShimRegistry::Get()->RegisterLocalPrefs(local_state_->registry());
AppShimRegistry::Get()->SetPrefServiceAndUserDataDirForTesting( AppShimRegistry::Get()->SetPrefServiceAndUserDataDirForTesting(
local_state_.get(), base::FilePath("/User/Data/Dir/")); local_state_.get(), user_data_dir);
delegate_ = new MockDelegate; delegate_ = new MockDelegate;
handler_.reset(new TestingExtensionAppShimHandler(delegate_)); handler_.reset(new TestingExtensionAppShimHandler(delegate_));
profile_path_a_ = base::FilePath("/User/Data/Dir/Profile A");
profile_path_b_ = base::FilePath("/User/Data/Dir/Profile B");
AppShimHostBootstrap::SetClient(handler_.get()); AppShimHostBootstrap::SetClient(handler_.get());
bootstrap_aa_ = (new TestingAppShimHostBootstrap( bootstrap_aa_ = (new TestingAppShimHostBootstrap(
profile_path_a_, kTestAppIdA, profile_path_a_, kTestAppIdA,
...@@ -341,6 +345,10 @@ class ExtensionAppShimHandlerTestBase : public testing::Test { ...@@ -341,6 +345,10 @@ class ExtensionAppShimHandlerTestBase : public testing::Test {
profile_path_b_, kTestAppIdA, profile_path_b_, kTestAppIdA,
true /* is_from_bookmark */, &bootstrap_ba_result_)) true /* is_from_bookmark */, &bootstrap_ba_result_))
->GetWeakPtr(); ->GetWeakPtr();
bootstrap_ca_ = (new TestingAppShimHostBootstrap(
profile_path_c_, kTestAppIdA,
true /* is_from_bookmark */, &bootstrap_ca_result_))
->GetWeakPtr();
bootstrap_xa_ = (new TestingAppShimHostBootstrap( bootstrap_xa_ = (new TestingAppShimHostBootstrap(
base::FilePath(), kTestAppIdA, base::FilePath(), kTestAppIdA,
true /* is_from_bookmark */, &bootstrap_xa_result_)) true /* is_from_bookmark */, &bootstrap_xa_result_))
...@@ -408,15 +416,24 @@ class ExtensionAppShimHandlerTestBase : public testing::Test { ...@@ -408,15 +416,24 @@ class ExtensionAppShimHandlerTestBase : public testing::Test {
handler_->SetProfileMenuItems(std::move(items)); handler_->SetProfileMenuItems(std::move(items));
} }
printf("Adding A: %s\n", profile_path_a_.value().c_str());
EXPECT_CALL(*delegate_, IsProfileLockedForPath(profile_path_a_)) EXPECT_CALL(*delegate_, IsProfileLockedForPath(profile_path_a_))
.WillRepeatedly(Return(false)); .WillRepeatedly(Return(false));
EXPECT_CALL(*delegate_, ProfileForPath(profile_path_a_)) EXPECT_CALL(*delegate_, ProfileForPath(profile_path_a_))
.WillRepeatedly(Return(&profile_a_)); .WillRepeatedly(Return(&profile_a_));
printf("Adding B: %s\n", profile_path_b_.value().c_str());
EXPECT_CALL(*delegate_, IsProfileLockedForPath(profile_path_b_)) EXPECT_CALL(*delegate_, IsProfileLockedForPath(profile_path_b_))
.WillRepeatedly(Return(false)); .WillRepeatedly(Return(false));
EXPECT_CALL(*delegate_, ProfileForPath(profile_path_b_)) EXPECT_CALL(*delegate_, ProfileForPath(profile_path_b_))
.WillRepeatedly(Return(&profile_b_)); .WillRepeatedly(Return(&profile_b_));
printf("Adding C: %s\n", profile_path_c_.value().c_str());
EXPECT_CALL(*delegate_, IsProfileLockedForPath(profile_path_c_))
.WillRepeatedly(Return(false));
EXPECT_CALL(*delegate_, ProfileForPath(profile_path_c_))
.WillRepeatedly(Return(&profile_c_));
// In most tests, we don't care about the result of GetWindows, it just // In most tests, we don't care about the result of GetWindows, it just
// needs to be non-empty. // needs to be non-empty.
AppWindowList app_window_list; AppWindowList app_window_list;
...@@ -424,8 +441,12 @@ class ExtensionAppShimHandlerTestBase : public testing::Test { ...@@ -424,8 +441,12 @@ class ExtensionAppShimHandlerTestBase : public testing::Test {
EXPECT_CALL(*delegate_, GetWindows(_, _)) EXPECT_CALL(*delegate_, GetWindows(_, _))
.WillRepeatedly(Return(app_window_list)); .WillRepeatedly(Return(app_window_list));
EXPECT_CALL(*delegate_, MaybeGetAppExtension(_, kTestAppIdA)) EXPECT_CALL(*delegate_, MaybeGetAppExtension(&profile_a_, kTestAppIdA))
.WillRepeatedly(Return(extension_a_.get())); .WillRepeatedly(Return(extension_a_.get()));
EXPECT_CALL(*delegate_, MaybeGetAppExtension(&profile_b_, kTestAppIdA))
.WillRepeatedly(Return(extension_a_.get()));
EXPECT_CALL(*delegate_, MaybeGetAppExtension(&profile_c_, kTestAppIdA))
.WillRepeatedly(Return(nullptr));
EXPECT_CALL(*delegate_, MaybeGetAppExtension(_, kTestAppIdB)) EXPECT_CALL(*delegate_, MaybeGetAppExtension(_, kTestAppIdB))
.WillRepeatedly(Return(extension_b_.get())); .WillRepeatedly(Return(extension_b_.get()));
EXPECT_CALL(*delegate_, LaunchApp(_, _, _)) EXPECT_CALL(*delegate_, LaunchApp(_, _, _))
...@@ -445,6 +466,7 @@ class ExtensionAppShimHandlerTestBase : public testing::Test { ...@@ -445,6 +466,7 @@ class ExtensionAppShimHandlerTestBase : public testing::Test {
// have been destroyed (because they may now own the bootstraps). // have been destroyed (because they may now own the bootstraps).
delete bootstrap_aa_.get(); delete bootstrap_aa_.get();
delete bootstrap_ba_.get(); delete bootstrap_ba_.get();
delete bootstrap_ca_.get();
delete bootstrap_xa_.get(); delete bootstrap_xa_.get();
delete bootstrap_ab_.get(); delete bootstrap_ab_.get();
delete bootstrap_bb_.get(); delete bootstrap_bb_.get();
...@@ -464,7 +486,6 @@ class ExtensionAppShimHandlerTestBase : public testing::Test { ...@@ -464,7 +486,6 @@ class ExtensionAppShimHandlerTestBase : public testing::Test {
if (host) if (host)
delegate_->SetHostForCreate(std::move(host)); delegate_->SetHostForCreate(std::move(host));
bootstrap->DoTestLaunch(launch_type, files); bootstrap->DoTestLaunch(launch_type, files);
EXPECT_TRUE(delegate_->RunGetProfilesForAppCallback());
} }
void NormalLaunch(base::WeakPtr<TestingAppShimHostBootstrap> bootstrap, void NormalLaunch(base::WeakPtr<TestingAppShimHostBootstrap> bootstrap,
...@@ -512,11 +533,14 @@ class ExtensionAppShimHandlerTestBase : public testing::Test { ...@@ -512,11 +533,14 @@ class ExtensionAppShimHandlerTestBase : public testing::Test {
std::unique_ptr<TestingExtensionAppShimHandler> handler_; std::unique_ptr<TestingExtensionAppShimHandler> handler_;
base::FilePath profile_path_a_; base::FilePath profile_path_a_;
base::FilePath profile_path_b_; base::FilePath profile_path_b_;
base::FilePath profile_path_c_;
TestingProfile profile_a_; TestingProfile profile_a_;
TestingProfile profile_b_; TestingProfile profile_b_;
TestingProfile profile_c_;
base::WeakPtr<TestingAppShimHostBootstrap> bootstrap_aa_; base::WeakPtr<TestingAppShimHostBootstrap> bootstrap_aa_;
base::WeakPtr<TestingAppShimHostBootstrap> bootstrap_ba_; base::WeakPtr<TestingAppShimHostBootstrap> bootstrap_ba_;
base::WeakPtr<TestingAppShimHostBootstrap> bootstrap_ca_;
base::WeakPtr<TestingAppShimHostBootstrap> bootstrap_xa_; base::WeakPtr<TestingAppShimHostBootstrap> bootstrap_xa_;
base::WeakPtr<TestingAppShimHostBootstrap> bootstrap_ab_; base::WeakPtr<TestingAppShimHostBootstrap> bootstrap_ab_;
base::WeakPtr<TestingAppShimHostBootstrap> bootstrap_bb_; base::WeakPtr<TestingAppShimHostBootstrap> bootstrap_bb_;
...@@ -525,6 +549,7 @@ class ExtensionAppShimHandlerTestBase : public testing::Test { ...@@ -525,6 +549,7 @@ class ExtensionAppShimHandlerTestBase : public testing::Test {
base::Optional<chrome::mojom::AppShimLaunchResult> bootstrap_aa_result_; base::Optional<chrome::mojom::AppShimLaunchResult> bootstrap_aa_result_;
base::Optional<chrome::mojom::AppShimLaunchResult> bootstrap_ba_result_; base::Optional<chrome::mojom::AppShimLaunchResult> bootstrap_ba_result_;
base::Optional<chrome::mojom::AppShimLaunchResult> bootstrap_ca_result_;
base::Optional<chrome::mojom::AppShimLaunchResult> bootstrap_xa_result_; base::Optional<chrome::mojom::AppShimLaunchResult> bootstrap_xa_result_;
base::Optional<chrome::mojom::AppShimLaunchResult> bootstrap_ab_result_; base::Optional<chrome::mojom::AppShimLaunchResult> bootstrap_ab_result_;
base::Optional<chrome::mojom::AppShimLaunchResult> bootstrap_bb_result_; base::Optional<chrome::mojom::AppShimLaunchResult> bootstrap_bb_result_;
...@@ -861,8 +886,7 @@ TEST_F(ExtensionAppShimHandlerTest, DontCreateHost) { ...@@ -861,8 +886,7 @@ TEST_F(ExtensionAppShimHandlerTest, DontCreateHost) {
EXPECT_CALL(*delegate_, LaunchApp(_, _, _)).Times(1); EXPECT_CALL(*delegate_, LaunchApp(_, _, _)).Times(1);
NormalLaunch(bootstrap_ab_, std::move(host_ab_unique_)); NormalLaunch(bootstrap_ab_, std::move(host_ab_unique_));
// But the bootstrap should be closed. // But the bootstrap should be closed.
EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kDuplicateHost, EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kNoHost, *bootstrap_ab_result_);
*bootstrap_ab_result_);
// And we should create no host. // And we should create no host.
EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdB)); EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdB));
} }
...@@ -1103,40 +1127,35 @@ TEST_F(ExtensionAppShimHandlerTestMultiProfile, ProfileMenuOneProfile) { ...@@ -1103,40 +1127,35 @@ TEST_F(ExtensionAppShimHandlerTestMultiProfile, ProfileMenuOneProfile) {
} }
TEST_F(ExtensionAppShimHandlerTestMultiProfile, FindProfileFromBadProfile) { TEST_F(ExtensionAppShimHandlerTestMultiProfile, FindProfileFromBadProfile) {
// Set this app to be installed for profile A. // Set this app to be installed for profile A and B.
{ AppShimRegistry::Get()->OnAppInstalledForProfile(kTestAppIdA,
auto item_a = chrome::mojom::ProfileMenuItem::New(); profile_path_a_);
item_a->profile_path = profile_path_a_; AppShimRegistry::Get()->OnAppInstalledForProfile(kTestAppIdA,
item_a->menu_index = 999; profile_path_b_);
std::vector<chrome::mojom::ProfileMenuItemPtr> items; // Set the app to be last-active on profile A.
items.push_back(std::move(item_a)); std::set<base::FilePath> last_active_profile_paths;
handler_->SetProfileMenuItems(std::move(items)); last_active_profile_paths.insert(profile_path_a_);
} AppShimRegistry::Get()->OnAppQuit(kTestAppIdA, last_active_profile_paths);
// Launch the shim requesting profile B. // Launch the shim requesting profile C.
delegate_->SetHostForCreate(std::move(host_aa_unique_)); delegate_->SetHostForCreate(std::move(host_aa_unique_));
EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, extension_a_.get(), _)) EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, extension_a_.get(), _))
.Times(1); .Times(1);
EXPECT_CALL(*delegate_, LaunchApp(&profile_b_, extension_a_.get(), _)) EXPECT_CALL(*delegate_, LaunchApp(&profile_b_, extension_a_.get(), _))
.Times(0); .Times(0);
NormalLaunch(bootstrap_ba_, nullptr); EXPECT_CALL(*delegate_, DoEnableExtension(&profile_c_, kTestAppIdA, _))
.WillOnce(RunOnceCallback<2>());
NormalLaunch(bootstrap_ca_, nullptr);
EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kSuccess, EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kSuccess,
*bootstrap_ba_result_); *bootstrap_ca_result_);
EXPECT_EQ(host_aa_.get(), handler_->FindHost(&profile_a_, kTestAppIdA)); EXPECT_EQ(host_aa_.get(), handler_->FindHost(&profile_a_, kTestAppIdA));
} }
TEST_F(ExtensionAppShimHandlerTestMultiProfile, FindProfileFromNoProfile) { TEST_F(ExtensionAppShimHandlerTestMultiProfile, FindProfileFromNoProfile) {
// Set this app to be installed for profile A. // Set this app to be installed for profile A.
{ AppShimRegistry::Get()->OnAppInstalledForProfile(kTestAppIdA,
auto item_a = chrome::mojom::ProfileMenuItem::New(); profile_path_a_);
item_a->profile_path = profile_path_a_;
item_a->menu_index = 999;
std::vector<chrome::mojom::ProfileMenuItemPtr> items;
items.push_back(std::move(item_a));
handler_->SetProfileMenuItems(std::move(items));
}
// Launch the shim without specifying a profile. // Launch the shim without specifying a profile.
delegate_->SetHostForCreate(std::move(host_aa_unique_)); delegate_->SetHostForCreate(std::move(host_aa_unique_));
......
...@@ -21,6 +21,9 @@ enum AppShimLaunchType { ...@@ -21,6 +21,9 @@ enum AppShimLaunchType {
enum AppShimLaunchResult { enum AppShimLaunchResult {
// App launched successfully. // App launched successfully.
kSuccess, kSuccess,
// The app launched successfully, but is not using a host (e.g, is an
// open-in-a-tab bookmark app), or the host has closed.
kNoHost,
// There is already a host registered for this app. // There is already a host registered for this app.
kDuplicateHost, kDuplicateHost,
// The profile was not found. // The profile was not found.
......
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