Commit 4be24bf9 authored by Christopher Cameron's avatar Christopher Cameron Committed by Commit Bot

Only show profiles that have app installed in PWA profile menu

In order to do this, we will need to know whether or not a PWA is
installed for a given profile, without actually loading that profile.
The suggested mechanism for this is to check the for the presence of
a Extensions/app_id subdirectory of the profile path.

Add a profile_menu_items_ member to ExtensionAppShimHandler that
lists all profiles in the AvatarMenu.

Add a installed_profiles member to ExtensionAppShimHandler::AppState
that lists all profiles for which a given app is installed (this is
a subset of the profiles in ExtensionAppShimHandler::
profile_menu_items_).

Add a ExtensionAppShimHandler::Delegate::GetProfilesForAppAsync
method to do the aforementioned path check.

Update ExtensionAppShimHandler::UpdateAppProfileMenu to sort menu
items by |menu_index| and to send an empty list if just one profile
would have appeared in the menu.

Add tests. Restructure tests such that ExtensionAppShimHandler
takes its delegate as a constructor argument (instead of having it
be set-able).

R=dominickn
TBR=skuhne

Bug: 1001215
Change-Id: I10bb7128c6f775759f1d1f381000eb7b33960eb3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1864098Reviewed-by: default avatarccameron <ccameron@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Commit-Queue: ccameron <ccameron@chromium.org>
Cr-Commit-Position: refs/heads/master@{#708792}
parent 8bb3ff85
...@@ -415,7 +415,8 @@ void AppShimController::UpdateProfileMenu( ...@@ -415,7 +415,8 @@ void AppShimController::UpdateProfileMenu(
// Note that this code to create menu items is nearly identical to the code // Note that this code to create menu items is nearly identical to the code
// in ProfileMenuController in the browser process. // in ProfileMenuController in the browser process.
for (const auto& mojo_item : profile_menu_items_) { for (size_t i = 0; i < profile_menu_items_.size(); ++i) {
const auto& mojo_item = profile_menu_items_[i];
NSString* name = base::SysUTF16ToNSString(mojo_item->name); NSString* name = base::SysUTF16ToNSString(mojo_item->name);
NSMenuItem* item = NSMenuItem* item =
[[[NSMenuItem alloc] initWithTitle:name [[[NSMenuItem alloc] initWithTitle:name
...@@ -426,7 +427,7 @@ void AppShimController::UpdateProfileMenu( ...@@ -426,7 +427,7 @@ void AppShimController::UpdateProfileMenu(
[item setTarget:profile_menu_target_.get()]; [item setTarget:profile_menu_target_.get()];
gfx::Image icon(mojo_item->icon); gfx::Image icon(mojo_item->icon);
[item setImage:icon.ToNSImage()]; [item setImage:icon.ToNSImage()];
[menu insertItem:item atIndex:mojo_item->menu_index]; [menu insertItem:item atIndex:i];
} }
} }
......
...@@ -91,8 +91,8 @@ class AppShimHost : public chrome::mojom::AppShimHost { ...@@ -91,8 +91,8 @@ class AppShimHost : public chrome::mojom::AppShimHost {
// Return the factory to use to create new widgets in the same process. // Return the factory to use to create new widgets in the same process.
remote_cocoa::ApplicationHost* GetRemoteCocoaApplicationHost() const; remote_cocoa::ApplicationHost* GetRemoteCocoaApplicationHost() const;
// Return the app shim interface. // Return the app shim interface. Virtual for tests.
chrome::mojom::AppShim* GetAppShim() const; virtual chrome::mojom::AppShim* GetAppShim() const;
protected: protected:
void ChannelError(uint32_t custom_reason, const std::string& description); void ChannelError(uint32_t custom_reason, const std::string& description);
......
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
#include <Security/Security.h> #include <Security/Security.h>
#include <algorithm>
#include <set>
#include <utility> #include <utility>
#include "apps/app_lifetime_monitor_factory.h" #include "apps/app_lifetime_monitor_factory.h"
...@@ -45,6 +47,7 @@ ...@@ -45,6 +47,7 @@
#include "chrome/browser/web_applications/components/web_app_helpers.h" #include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/browser/web_applications/components/web_app_shortcut_mac.h" #include "chrome/browser/web_applications/components/web_app_shortcut_mac.h"
#include "chrome/browser/web_applications/extensions/web_app_extension_shortcut.h" #include "chrome/browser/web_applications/extensions/web_app_extension_shortcut.h"
#include "chrome/browser/web_applications/extensions/web_app_extension_shortcut_mac.h"
#include "chrome/common/chrome_features.h" #include "chrome/common/chrome_features.h"
#include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_metrics.h" #include "chrome/common/extensions/extension_metrics.h"
...@@ -197,6 +200,11 @@ bool UsesRemoteViews(const extensions::Extension* extension) { ...@@ -197,6 +200,11 @@ bool UsesRemoteViews(const extensions::Extension* extension) {
return extension->is_hosted_app() && extension->from_bookmark(); return extension->is_hosted_app() && extension->from_bookmark();
} }
bool ProfileMenuItemComparator(const chrome::mojom::ProfileMenuItemPtr& a,
const chrome::mojom::ProfileMenuItemPtr& b) {
return a->menu_index < b->menu_index;
}
} // namespace } // namespace
namespace apps { namespace apps {
...@@ -235,8 +243,12 @@ struct ExtensionAppShimHandler::AppState { ...@@ -235,8 +243,12 @@ struct ExtensionAppShimHandler::AppState {
// 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;
// The profile state for the profiles currently running this app.
std::map<Profile*, std::unique_ptr<ProfileState>> profiles; std::map<Profile*, std::unique_ptr<ProfileState>> profiles;
// The set of profiles for which this app is installed.
std::set<base::FilePath> installed_profiles;
private: private:
DISALLOW_COPY_AND_ASSIGN(AppState); DISALLOW_COPY_AND_ASSIGN(AppState);
}; };
...@@ -261,13 +273,6 @@ bool ExtensionAppShimHandler::AppState::IsMultiProfile() const { ...@@ -261,13 +273,6 @@ bool ExtensionAppShimHandler::AppState::IsMultiProfile() const {
return multi_profile_host.get(); return multi_profile_host.get();
} }
std::unique_ptr<AvatarMenu> ExtensionAppShimHandler::Delegate::CreateAvatarMenu(
AvatarMenuObserver* observer) {
ProfileManager* profile_manager = g_browser_process->profile_manager();
return std::make_unique<AvatarMenu>(
&profile_manager->GetProfileAttributesStorage(), observer, nullptr);
}
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();
...@@ -277,6 +282,14 @@ Profile* ExtensionAppShimHandler::Delegate::ProfileForPath( ...@@ -277,6 +282,14 @@ Profile* ExtensionAppShimHandler::Delegate::ProfileForPath(
return profile && profile_manager->IsValidProfile(profile) ? profile : NULL; return profile && profile_manager->IsValidProfile(profile) ? profile : NULL;
} }
void ExtensionAppShimHandler::Delegate::GetProfilesForAppAsync(
const std::string& app_id,
const std::vector<base::FilePath>& profile_paths_to_check,
base::OnceCallback<void(const std::vector<base::FilePath>&)> callback) {
web_app::GetProfilesForAppShim(app_id, profile_paths_to_check,
std::move(callback));
}
void ExtensionAppShimHandler::Delegate::LoadProfileAsync( void ExtensionAppShimHandler::Delegate::LoadProfileAsync(
const base::FilePath& full_path, const base::FilePath& full_path,
base::OnceCallback<void(Profile*)> callback) { base::OnceCallback<void(Profile*)> callback) {
...@@ -631,6 +644,7 @@ void ExtensionAppShimHandler::OnShimProcessConnectedAndAppLoaded( ...@@ -631,6 +644,7 @@ void ExtensionAppShimHandler::OnShimProcessConnectedAndAppLoaded(
bootstrap->OnFailedToConnectToHost(APP_SHIM_LAUNCH_APP_NOT_FOUND); bootstrap->OnFailedToConnectToHost(APP_SHIM_LAUNCH_APP_NOT_FOUND);
return; return;
} }
std::string app_id = bootstrap->GetAppId();
AppShimLaunchType launch_type = bootstrap->GetLaunchType(); AppShimLaunchType launch_type = bootstrap->GetLaunchType();
std::vector<base::FilePath> files = bootstrap->GetLaunchFiles(); std::vector<base::FilePath> files = bootstrap->GetLaunchFiles();
...@@ -884,60 +898,103 @@ void ExtensionAppShimHandler::OnBrowserRemoved(Browser* browser) { ...@@ -884,60 +898,103 @@ void ExtensionAppShimHandler::OnBrowserRemoved(Browser* browser) {
} }
void ExtensionAppShimHandler::OnBrowserSetLastActive(Browser* browser) { void ExtensionAppShimHandler::OnBrowserSetLastActive(Browser* browser) {
// Defer creation of the avatar menu until the active browser window changes, // Rebuild the profile menu items (to ensure that the checkmark in the menu
// to allow tests to override |delegate_|. // is next to the new-active item).
if (!avatar_menu_) { if (avatar_menu_)
avatar_menu_ = delegate_->CreateAvatarMenu(this); avatar_menu_->ActiveBrowserChanged(browser);
return; UpdateProfileMenuItems();
}
if (!avatar_menu_)
return;
avatar_menu_->ActiveBrowserChanged(browser);
avatar_menu_->RebuildMenu();
for (auto& iter_app : apps_) {
AppState* app_state = iter_app.second.get();
if (app_state->IsMultiProfile())
UpdateHostProfileMenu(app_state->multi_profile_host.get());
}
}
void ExtensionAppShimHandler::OnAvatarMenuChanged(AvatarMenu* menu) { // Update all multi-profile apps' profile menus.
if (!avatar_menu_)
return;
for (auto& iter_app : apps_) { for (auto& iter_app : apps_) {
AppState* app_state = iter_app.second.get(); AppState* app_state = iter_app.second.get();
if (app_state->IsMultiProfile()) if (app_state->IsMultiProfile())
UpdateHostProfileMenu(app_state->multi_profile_host.get()); UpdateAppProfileMenu(app_state);
} }
} }
void ExtensionAppShimHandler::UpdateHostProfileMenu(AppShimHost* host) { void ExtensionAppShimHandler::UpdateProfileMenuItems() {
std::vector<chrome::mojom::ProfileMenuItemPtr> mojo_items; if (!avatar_menu_) {
ProfileManager* profile_manager = g_browser_process->profile_manager();
avatar_menu_ = std::make_unique<AvatarMenu>(
&profile_manager->GetProfileAttributesStorage(), this, nullptr);
}
avatar_menu_->RebuildMenu();
profile_menu_items_.clear();
for (size_t i = 0; i < avatar_menu_->GetNumberOfItems(); ++i) { for (size_t i = 0; i < avatar_menu_->GetNumberOfItems(); ++i) {
// TODO(https://crbug.com/982024): Skip profiles for which this extension
// is not installed.
auto mojo_item = chrome::mojom::ProfileMenuItem::New(); auto mojo_item = chrome::mojom::ProfileMenuItem::New();
const AvatarMenu::Item& item = avatar_menu_->GetItemAt(i); const AvatarMenu::Item& item = avatar_menu_->GetItemAt(i);
mojo_item->name = item.name; mojo_item->name = item.name;
mojo_item->menu_index = item.menu_index; mojo_item->menu_index = item.menu_index;
mojo_item->active = item.active; mojo_item->active = item.active;
mojo_item->profile_path = item.profile_path; mojo_item->profile_path = item.profile_path;
{ mojo_item->icon =
// Scale the icon as needed (see ProfileMenuController). profiles::GetAvatarIconForNSMenu(item.profile_path).ToImageSkia()[0];
gfx::Image itemIcon; profile_menu_items_.push_back(std::move(mojo_item));
AvatarMenu::GetImageForMenuButton(item.profile_path, &itemIcon); }
static constexpr int kMenuAvatarIconSize = 38; }
if (itemIcon.Width() > kMenuAvatarIconSize ||
itemIcon.Height() > kMenuAvatarIconSize) { void ExtensionAppShimHandler::OnAvatarMenuChanged(AvatarMenu* menu) {
itemIcon = profiles::GetSizedAvatarIcon(itemIcon, /*is_rectangle=*/true, // Rebuild the profile menu to reflect changes (e.g, added or removed
kMenuAvatarIconSize, // profiles).
kMenuAvatarIconSize); DCHECK_EQ(avatar_menu_.get(), menu);
} UpdateProfileMenuItems();
mojo_item->icon = *itemIcon.ToImageSkia();
} // Because profiles may have been added or removed, for each app, update the
mojo_items.push_back(std::move(mojo_item)); // list of profiles for which that app is installed.
for (auto& iter_app : apps_) {
const std::string& app_id = iter_app.first;
AppState* app_state = iter_app.second.get();
if (!app_state->IsMultiProfile())
continue;
GetProfilesForAppAsync(
iter_app.first,
base::BindOnce(&ExtensionAppShimHandler::OnGotProfilesForApp,
weak_factory_.GetWeakPtr(), app_id));
}
}
void ExtensionAppShimHandler::GetProfilesForAppAsync(
const std::string& app_id,
base::OnceCallback<void(const std::vector<base::FilePath>&)> callback) {
std::vector<base::FilePath> profile_paths_to_check;
for (const auto& item : profile_menu_items_)
profile_paths_to_check.push_back(item->profile_path);
delegate_->GetProfilesForAppAsync(app_id, profile_paths_to_check,
std::move(callback));
}
void ExtensionAppShimHandler::OnGotProfilesForApp(
const std::string& app_id,
const std::vector<base::FilePath>& profiles) {
auto found_app = apps_.find(app_id);
if (found_app == apps_.end())
return;
AppState* app_state = found_app->second.get();
app_state->installed_profiles.clear();
for (const auto& profile_path : profiles)
app_state->installed_profiles.insert(profile_path);
UpdateAppProfileMenu(app_state);
}
void ExtensionAppShimHandler::UpdateAppProfileMenu(AppState* app_state) {
DCHECK(app_state->IsMultiProfile());
// Include in |items| the profiles from |profile_menu_items_| for which this
// app is installed, sorted by |menu_index|.
std::vector<chrome::mojom::ProfileMenuItemPtr> items;
for (const auto& item : profile_menu_items_) {
if (app_state->installed_profiles.count(item->profile_path))
items.push_back(item->Clone());
} }
host->GetAppShim()->UpdateProfileMenu(std::move(mojo_items)); std::sort(items.begin(), items.end(), ProfileMenuItemComparator);
// Do not show a profile menu unless it has at least 2 entries (that is, the
// app is available for at least 2 profiles).
if (items.size() < 2)
items.clear();
// Send the profile menu to the app shim process.
app_state->multi_profile_host->GetAppShim()->UpdateProfileMenu(
std::move(items));
} }
ExtensionAppShimHandler::ProfileState* ExtensionAppShimHandler::ProfileState*
...@@ -982,6 +1039,14 @@ ExtensionAppShimHandler::GetOrCreateProfileState( ...@@ -982,6 +1039,14 @@ ExtensionAppShimHandler::GetOrCreateProfileState(
app_state->profiles app_state->profiles
.insert(std::make_pair(profile, std::move(new_profile_state))) .insert(std::make_pair(profile, std::move(new_profile_state)))
.first; .first;
// Update the profile menu as each new profile uses this app. Note that
// ideally this should be done when when |multi_profile_host| is created
// above, and should be updated when apps are installed or uninstalled
// (but do not).
UpdateProfileMenuItems();
GetProfilesForAppAsync(
app_id, base::BindOnce(&ExtensionAppShimHandler::OnGotProfilesForApp,
weak_factory_.GetWeakPtr(), app_id));
} }
return found_profile->second.get(); return found_profile->second.get();
} }
......
...@@ -54,13 +54,16 @@ class ExtensionAppShimHandler : public AppShimHostBootstrap::Client, ...@@ -54,13 +54,16 @@ class ExtensionAppShimHandler : public AppShimHostBootstrap::Client,
public: public:
virtual ~Delegate() {} virtual ~Delegate() {}
// Create an avatar menu (disable-able for tests).
virtual std::unique_ptr<AvatarMenu> CreateAvatarMenu(
AvatarMenuObserver* observer);
// Return the profile for |path|, only if it is already loaded. // Return the profile for |path|, only if it is already loaded.
virtual Profile* ProfileForPath(const base::FilePath& path); virtual Profile* ProfileForPath(const base::FilePath& path);
// Call |callback| with the list of profiles for which this app is
// installed.
virtual void GetProfilesForAppAsync(
const std::string& app_id,
const std::vector<base::FilePath>& profile_paths_to_check,
base::OnceCallback<void(const std::vector<base::FilePath>&)>);
// Load a profile and call |callback| when completed or failed. // Load a profile and call |callback| when completed or failed.
virtual void LoadProfileAsync(const base::FilePath& path, virtual void LoadProfileAsync(const base::FilePath& path,
base::OnceCallback<void(Profile*)> callback); base::OnceCallback<void(Profile*)> callback);
...@@ -197,6 +200,12 @@ class ExtensionAppShimHandler : public AppShimHostBootstrap::Client, ...@@ -197,6 +200,12 @@ class ExtensionAppShimHandler : public AppShimHostBootstrap::Client,
void set_delegate(Delegate* delegate); void set_delegate(Delegate* delegate);
content::NotificationRegistrar& registrar() { return registrar_; } content::NotificationRegistrar& registrar() { return registrar_; }
// Update |profile_menu_items_| from |avatar_menu_|. Virtual for tests.
virtual void UpdateProfileMenuItems();
// The list of all profiles that might appear in the menu.
std::vector<chrome::mojom::ProfileMenuItemPtr> profile_menu_items_;
private: private:
// The state for an individual app, and for the profile-scoped app info. // The state for an individual app, and for the profile-scoped app info.
struct ProfileState; struct ProfileState;
...@@ -234,8 +243,19 @@ class ExtensionAppShimHandler : public AppShimHostBootstrap::Client, ...@@ -234,8 +243,19 @@ class ExtensionAppShimHandler : public AppShimHostBootstrap::Client,
const std::string& app_id, const std::string& app_id,
LoadProfileAppCallback callback); LoadProfileAppCallback callback);
// Check to see for which profile paths in |profile_menu_items_| the app with
// |app_id| is installed, and return them as the argument to |callback|.
void GetProfilesForAppAsync(
const std::string& app_id,
base::OnceCallback<void(const std::vector<base::FilePath>&)> callback);
// Callback for the call to asynchronously query which profiles have an app
// installed.
void OnGotProfilesForApp(const std::string& app_id,
const std::vector<base::FilePath>& profiles);
// Update the profiles menu for the specified host. // Update the profiles menu for the specified host.
void UpdateHostProfileMenu(AppShimHost* host); void UpdateAppProfileMenu(AppState* app_state);
std::unique_ptr<Delegate> delegate_; std::unique_ptr<Delegate> delegate_;
......
...@@ -48,13 +48,15 @@ class MockDelegate : public ExtensionAppShimHandler::Delegate { ...@@ -48,13 +48,15 @@ class MockDelegate : public ExtensionAppShimHandler::Delegate {
public: public:
virtual ~MockDelegate() {} virtual ~MockDelegate() {}
std::unique_ptr<AvatarMenu> CreateAvatarMenu( void GetProfilesForAppAsync(
AvatarMenuObserver* observer) override { const std::string& app_id,
return nullptr; const std::vector<base::FilePath>& profile_paths_to_check,
} base::OnceCallback<void(const std::vector<base::FilePath>&)> callback)
base::FilePath GetFullProfilePath(const base::FilePath& relative_path) { override {
return relative_path; get_profiles_for_app_callbacks_.push_back(
base::BindOnce(std::move(callback), profile_paths_to_check));
} }
MOCK_METHOD1(ProfileForPath, Profile*(const base::FilePath&)); MOCK_METHOD1(ProfileForPath, Profile*(const base::FilePath&));
void LoadProfileAsync(const base::FilePath& path, void LoadProfileAsync(const base::FilePath& path,
base::OnceCallback<void(Profile*)> callback) override { base::OnceCallback<void(Profile*)> callback) override {
...@@ -136,11 +138,21 @@ class MockDelegate : public ExtensionAppShimHandler::Delegate { ...@@ -136,11 +138,21 @@ class MockDelegate : public ExtensionAppShimHandler::Delegate {
return callbacks_.erase(path); return callbacks_.erase(path);
} }
bool RunGetProfilesForAppCallback() {
if (get_profiles_for_app_callbacks_.empty())
return false;
std::move(get_profiles_for_app_callbacks_.front()).Run();
get_profiles_for_app_callbacks_.pop_front();
return true;
}
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*)>> 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_;
bool allow_shim_to_connect_ = true; bool allow_shim_to_connect_ = true;
}; };
...@@ -162,6 +174,16 @@ class TestingExtensionAppShimHandler : public ExtensionAppShimHandler { ...@@ -162,6 +174,16 @@ class TestingExtensionAppShimHandler : public ExtensionAppShimHandler {
ExtensionAppShimHandler::OnShimFocus(host, focus_type, files); ExtensionAppShimHandler::OnShimFocus(host, focus_type, files);
} }
void SetProfileMenuItems(
std::vector<chrome::mojom::ProfileMenuItemPtr> new_profile_menu_items) {
new_profile_menu_items_ = std::move(new_profile_menu_items);
}
void UpdateProfileMenuItems() override {
profile_menu_items_.clear();
for (const auto& item : new_profile_menu_items_)
profile_menu_items_.push_back(item.Clone());
}
void SetAcceptablyCodeSigned(bool is_acceptable_code_signed) { void SetAcceptablyCodeSigned(bool is_acceptable_code_signed) {
is_acceptably_code_signed_ = is_acceptable_code_signed; is_acceptably_code_signed_ = is_acceptable_code_signed;
} }
...@@ -172,6 +194,7 @@ class TestingExtensionAppShimHandler : public ExtensionAppShimHandler { ...@@ -172,6 +194,7 @@ class TestingExtensionAppShimHandler : public ExtensionAppShimHandler {
content::NotificationRegistrar& GetRegistrar() { return registrar(); } content::NotificationRegistrar& GetRegistrar() { return registrar(); }
private: private:
std::vector<chrome::mojom::ProfileMenuItemPtr> new_profile_menu_items_;
bool is_acceptably_code_signed_ = true; bool is_acceptably_code_signed_ = true;
DISALLOW_COPY_AND_ASSIGN(TestingExtensionAppShimHandler); DISALLOW_COPY_AND_ASSIGN(TestingExtensionAppShimHandler);
}; };
...@@ -231,6 +254,22 @@ class TestingAppShimHostBootstrap : public AppShimHostBootstrap { ...@@ -231,6 +254,22 @@ class TestingAppShimHostBootstrap : public AppShimHostBootstrap {
const char kTestAppIdA[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const char kTestAppIdA[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const char kTestAppIdB[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; const char kTestAppIdB[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
class TestAppShim : public chrome::mojom::AppShim {
public:
// chrome::mojom::AppShim:
void CreateRemoteCocoaApplication(
remote_cocoa::mojom::ApplicationAssociatedRequest request) override {}
void CreateCommandDispatcherForWidget(uint64_t widget_id) override {}
void SetBadgeLabel(const std::string& badge_label) override {}
void SetUserAttention(apps::AppShimAttentionType attention_type) override {}
void UpdateProfileMenu(std::vector<chrome::mojom::ProfileMenuItemPtr>
profile_menu_items) override {
profile_menu_items_ = std::move(profile_menu_items);
}
std::vector<chrome::mojom::ProfileMenuItemPtr> profile_menu_items_;
};
class TestHost : public AppShimHost { class TestHost : public AppShimHost {
public: public:
TestHost(const base::FilePath& profile_path, TestHost(const base::FilePath& profile_path,
...@@ -240,9 +279,14 @@ class TestHost : public AppShimHost { ...@@ -240,9 +279,14 @@ class TestHost : public AppShimHost {
app_id, app_id,
profile_path, profile_path,
false /* uses_remote_views */), false /* uses_remote_views */),
test_app_shim_(new TestAppShim),
test_weak_factory_(this) {} test_weak_factory_(this) {}
~TestHost() override {} ~TestHost() override {}
chrome::mojom::AppShim* GetAppShim() const override {
return test_app_shim_.get();
}
// Record whether or not OnBootstrapConnected has been called. // Record whether or not OnBootstrapConnected has been called.
void OnBootstrapConnected( void OnBootstrapConnected(
std::unique_ptr<AppShimHostBootstrap> bootstrap) override { std::unique_ptr<AppShimHostBootstrap> bootstrap) override {
...@@ -258,6 +302,8 @@ class TestHost : public AppShimHost { ...@@ -258,6 +302,8 @@ class TestHost : public AppShimHost {
using AppShimHost::ProfileSelectedFromMenu; using AppShimHost::ProfileSelectedFromMenu;
std::unique_ptr<TestAppShim> test_app_shim_;
private: private:
bool did_connect_to_host_ = false; bool did_connect_to_host_ = false;
...@@ -903,4 +949,73 @@ TEST_F(ExtensionAppShimHandlerTest, MultiProfileSelectMenu) { ...@@ -903,4 +949,73 @@ TEST_F(ExtensionAppShimHandlerTest, MultiProfileSelectMenu) {
host_aa_->ProfileSelectedFromMenu(profile_path_b_); host_aa_->ProfileSelectedFromMenu(profile_path_b_);
} }
TEST_F(ExtensionAppShimHandlerTest, ProfileMenuOneProfile) {
base::test::ScopedFeatureList scoped_features;
scoped_features.InitWithFeatures(
/*enabled_features=*/{features::kAppShimMultiProfile},
/*disabled_features=*/{});
// Set this app to be installed for profile A.
{
auto item_a = chrome::mojom::ProfileMenuItem::New();
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));
}
// When the app activates, a host is created. This will trigger building
// the avatar menu.
delegate_->SetHostForCreate(std::move(host_aa_unique_));
EXPECT_CALL(*delegate_, DoLaunchShim(&profile_a_, extension_a_.get(), false));
handler_->OnAppActivated(&profile_a_, kTestAppIdA);
EXPECT_EQ(host_aa_.get(), handler_->FindHost(&profile_a_, kTestAppIdA));
// Launch the shim.
EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, extension_a_.get(), _))
.Times(0);
RegisterOnlyLaunch(bootstrap_aa_, nullptr);
EXPECT_EQ(APP_SHIM_LAUNCH_SUCCESS, *bootstrap_aa_result_);
EXPECT_EQ(host_aa_.get(), handler_->FindHost(&profile_a_, kTestAppIdA));
const auto& menu_items = host_aa_->test_app_shim_->profile_menu_items_;
// We should have no menu items, because there is only one installed profile.
EXPECT_TRUE(delegate_->RunGetProfilesForAppCallback());
EXPECT_FALSE(delegate_->RunGetProfilesForAppCallback());
EXPECT_TRUE(menu_items.empty());
// Add profile B to the avatar menu and call the avatar menu observer update
// method.
{
auto item_a = chrome::mojom::ProfileMenuItem::New();
item_a->profile_path = profile_path_a_;
item_a->menu_index = 999;
auto item_b = chrome::mojom::ProfileMenuItem::New();
item_b->profile_path = profile_path_b_;
item_b->menu_index = 111;
std::vector<chrome::mojom::ProfileMenuItemPtr> items;
items.push_back(std::move(item_a));
items.push_back(std::move(item_b));
handler_->SetProfileMenuItems(std::move(items));
}
handler_->OnAvatarMenuChanged(nullptr);
EXPECT_TRUE(delegate_->RunGetProfilesForAppCallback());
EXPECT_FALSE(delegate_->RunGetProfilesForAppCallback());
// We should now have 2 menu items. They should be sorted by menu_index,
// making b be before a.
EXPECT_EQ(menu_items.size(), 2u);
EXPECT_EQ(menu_items[0]->profile_path, profile_path_b_);
EXPECT_EQ(menu_items[1]->profile_path, profile_path_a_);
// Activate profile B. This should trigger re-building the avatar menu.
handler_->OnAppActivated(&profile_b_, kTestAppIdA);
EXPECT_TRUE(delegate_->RunGetProfilesForAppCallback());
EXPECT_FALSE(delegate_->RunGetProfilesForAppCallback());
}
} // namespace apps } // namespace apps
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "cc/paint/paint_flags.h" #include "cc/paint/paint_flags.h"
#include "chrome/app/vector_icons/vector_icons.h" #include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/avatar_menu.h"
#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths.h"
#include "chrome/grit/generated_resources.h" #include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h" #include "chrome/grit/theme_resources.h"
...@@ -392,6 +393,21 @@ gfx::Image GetAvatarIconForTitleBar(const gfx::Image& image, ...@@ -392,6 +393,21 @@ gfx::Image GetAvatarIconForTitleBar(const gfx::Image& image,
return gfx::Image(gfx::ImageSkia(std::move(source), dst_size)); return gfx::Image(gfx::ImageSkia(std::move(source), dst_size));
} }
#if defined(OS_MACOSX)
gfx::Image GetAvatarIconForNSMenu(const base::FilePath& profile_path) {
// Always use the low-res, small default avatars in the menu.
gfx::Image icon;
AvatarMenu::GetImageForMenuButton(profile_path, &icon);
// Shape the avatar icon into a circle for consistency with other avatar
// UI elements.
constexpr int kMenuAvatarIconSize = 38;
return profiles::GetSizedAvatarIcon(icon, /*is_rectangle=*/true,
kMenuAvatarIconSize, kMenuAvatarIconSize,
profiles::SHAPE_CIRCLE);
}
#endif
SkBitmap GetAvatarIconAsSquare(const SkBitmap& source_bitmap, SkBitmap GetAvatarIconAsSquare(const SkBitmap& source_bitmap,
int scale_factor) { int scale_factor) {
SkBitmap square_bitmap; SkBitmap square_bitmap;
......
...@@ -84,6 +84,12 @@ gfx::Image GetAvatarIconForTitleBar(const gfx::Image& image, ...@@ -84,6 +84,12 @@ gfx::Image GetAvatarIconForTitleBar(const gfx::Image& image,
int dst_width, int dst_width,
int dst_height); int dst_height);
#if defined(OS_MACOSX)
// Returns the image for the profile at |profile_path| that is suitable for use
// in the macOS menu bar.
gfx::Image GetAvatarIconForNSMenu(const base::FilePath& profile_path);
#endif
// Returns a bitmap with a couple of columns shaved off so it is more square, // Returns a bitmap with a couple of columns shaved off so it is more square,
// so that when resized to a square aspect ratio it looks pretty. // so that when resized to a square aspect ratio it looks pretty.
SkBitmap GetAvatarIconAsSquare(const SkBitmap& source_bitmap, int scale_factor); SkBitmap GetAvatarIconAsSquare(const SkBitmap& source_bitmap, int scale_factor);
......
...@@ -29,8 +29,6 @@ ...@@ -29,8 +29,6 @@
namespace { namespace {
constexpr int kMenuAvatarIconSize = 38;
// Used in UMA histogram macros, shouldn't be reordered or renumbered // Used in UMA histogram macros, shouldn't be reordered or renumbered
enum ValidateMenuItemSelector { enum ValidateMenuItemSelector {
UNKNOWN_SELECTOR = 0, UNKNOWN_SELECTOR = 0,
...@@ -159,16 +157,8 @@ class Observer : public BrowserListObserver, public AvatarMenuObserver { ...@@ -159,16 +157,8 @@ class Observer : public BrowserListObserver, public AvatarMenuObserver {
if (dock) { if (dock) {
[item setIndentationLevel:1]; [item setIndentationLevel:1];
} else { } else {
gfx::Image itemIcon; gfx::Image itemIcon =
// Always use the low-res, small default avatars in the menu. profiles::GetAvatarIconForNSMenu(itemData.profile_path);
AvatarMenu::GetImageForMenuButton(itemData.profile_path, &itemIcon);
// Shapes the avatar icon into a circle for consistency with other avatar
// UI elements.
itemIcon = profiles::GetSizedAvatarIcon(
itemIcon, /*is_rectangle=*/true, kMenuAvatarIconSize,
kMenuAvatarIconSize, profiles::SHAPE_CIRCLE);
[item setImage:itemIcon.ToNSImage()]; [item setImage:itemIcon.ToNSImage()];
[item setState:itemData.active ? NSOnState : NSOffState]; [item setState:itemData.active ? NSOnState : NSOffState];
} }
......
...@@ -5,6 +5,12 @@ ...@@ -5,6 +5,12 @@
#ifndef CHROME_BROWSER_WEB_APPLICATIONS_EXTENSIONS_WEB_APP_EXTENSION_SHORTCUT_MAC_H_ #ifndef CHROME_BROWSER_WEB_APPLICATIONS_EXTENSIONS_WEB_APP_EXTENSION_SHORTCUT_MAC_H_
#define CHROME_BROWSER_WEB_APPLICATIONS_EXTENSIONS_WEB_APP_EXTENSION_SHORTCUT_MAC_H_ #define CHROME_BROWSER_WEB_APPLICATIONS_EXTENSIONS_WEB_APP_EXTENSION_SHORTCUT_MAC_H_
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "base/files/file_path.h"
class Profile; class Profile;
namespace base { namespace base {
...@@ -26,6 +32,17 @@ bool MaybeRebuildShortcut(const base::CommandLine& command_line); ...@@ -26,6 +32,17 @@ bool MaybeRebuildShortcut(const base::CommandLine& command_line);
void RevealAppShimInFinderForApp(Profile* profile, void RevealAppShimInFinderForApp(Profile* profile,
const extensions::Extension* app); const extensions::Extension* app);
// Callback made by GetProfilesForAppShim.
using GetProfilesForAppShimCallback =
base::OnceCallback<void(const std::vector<base::FilePath>&)>;
// Call |callback| with the subset of |profile_paths_to_check| for which the app
// with |app_id| in installed.
void GetProfilesForAppShim(
const std::string& app_id,
const std::vector<base::FilePath>& profile_paths_to_check,
GetProfilesForAppShimCallback callback);
} // namespace web_app } // namespace web_app
#endif // CHROME_BROWSER_WEB_APPLICATIONS_EXTENSIONS_WEB_APP_EXTENSION_SHORTCUT_MAC_H_ #endif // CHROME_BROWSER_WEB_APPLICATIONS_EXTENSIONS_WEB_APP_EXTENSION_SHORTCUT_MAC_H_
...@@ -4,10 +4,15 @@ ...@@ -4,10 +4,15 @@
#include "chrome/browser/web_applications/extensions/web_app_extension_shortcut_mac.h" #include "chrome/browser/web_applications/extensions/web_app_extension_shortcut_mac.h"
#include <utility>
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_ui_util.h" #include "chrome/browser/extensions/extension_ui_util.h"
...@@ -19,8 +24,10 @@ ...@@ -19,8 +24,10 @@
#import "chrome/common/mac/app_mode_common.h" #import "chrome/common/mac/app_mode_common.h"
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
using content::BrowserThread; using content::BrowserThread;
...@@ -146,6 +153,38 @@ void UpdateShortcutsForAllApps(Profile* profile, base::OnceClosure callback) { ...@@ -146,6 +153,38 @@ void UpdateShortcutsForAllApps(Profile* profile, base::OnceClosure callback) {
} }
} }
void GetProfilesForAppShim(
const std::string& app_id,
const std::vector<base::FilePath>& profile_paths_to_check,
GetProfilesForAppShimCallback callback) {
auto io_thread_lambda =
[](const std::string& app_id,
const std::vector<base::FilePath>& profile_paths_to_check,
GetProfilesForAppShimCallback callback) {
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
// Determine if an extension is installed for a profile by whether or
// not the subpath Extensions/|app_id| exists. We do this instead of
// checking the Profile's properties because the Profile may not be
// loaded yet.
std::vector<base::FilePath> profile_paths_with_app;
for (const auto& profile_path : profile_paths_to_check) {
base::FilePath extension_path =
profile_path.AppendASCII(extensions::kInstallDirectoryName)
.AppendASCII(app_id);
if (base::PathExists(extension_path))
profile_paths_with_app.push_back(profile_path);
}
base::PostTask(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(std::move(callback), profile_paths_with_app));
};
internals::GetShortcutIOTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(io_thread_lambda, app_id,
profile_paths_to_check, std::move(callback)));
}
} // namespace web_app } // namespace web_app
namespace chrome { namespace chrome {
......
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