Commit c54a52fa authored by Sam McNally's avatar Sam McNally Committed by Commit Bot

Allow background mode while an extension is communicating with a native app.

Add support for enabling background mode while an extension capable of
accepting inbound native messaging launch requests is active. Allow
cooperating native apps to launch Chrome without a browser window when
initiating communications with a cooperating extension.

Bug: 967262
Change-Id: I61353c0becb341ad3e08a5a8635e171f9f9bd59b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1898894Reviewed-by: default avatarSergey Ulanov <sergeyu@chromium.org>
Reviewed-by: default avatarDrew Wilson <atwilson@chromium.org>
Reviewed-by: default avatarTommy Martino <tmartino@chromium.org>
Reviewed-by: default avatarSergei Datsenko <dats@chromium.org>
Commit-Queue: Sam McNally <sammc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#716480}
parent 4dab5cc0
......@@ -19,10 +19,12 @@
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/extensions/extension_constants.h"
#include "components/crx_file/id_util.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
......@@ -85,9 +87,14 @@ void GetServiceApplications(extensions::ExtensionService* service,
ExtensionRegistry* registry = ExtensionRegistry::Get(service->profile());
const ExtensionSet& enabled_extensions = registry->enabled_extensions();
auto* process_manager = extensions::ProcessManager::Get(service->profile());
for (const auto& extension : enabled_extensions) {
if (BackgroundApplicationListModel::IsBackgroundApp(*extension,
service->profile())) {
if (BackgroundApplicationListModel::IsPersistentBackgroundApp(
*extension, service->profile()) ||
(BackgroundApplicationListModel::IsTransientBackgroundApp(
*extension, service->profile()) &&
process_manager->GetBackgroundHostForExtension(extension->id()))) {
applications_result->push_back(extension);
}
}
......@@ -96,8 +103,8 @@ void GetServiceApplications(extensions::ExtensionService* service,
// crashed doesn't mean we should ignore it).
const ExtensionSet& terminated_extensions = registry->terminated_extensions();
for (const auto& extension : terminated_extensions) {
if (BackgroundApplicationListModel::IsBackgroundApp(*extension,
service->profile())) {
if (BackgroundApplicationListModel::IsPersistentBackgroundApp(
*extension, service->profile())) {
applications_result->push_back(extension);
}
}
......@@ -230,8 +237,9 @@ int BackgroundApplicationListModel::GetPosition(
}
// static
bool BackgroundApplicationListModel::IsBackgroundApp(
const Extension& extension, Profile* profile) {
bool BackgroundApplicationListModel::IsPersistentBackgroundApp(
const Extension& extension,
Profile* profile) {
// An extension is a "background app" if it has the "background API"
// permission, and meets one of the following criteria:
// 1) It is an extension (not a hosted app).
......@@ -269,6 +277,25 @@ bool BackgroundApplicationListModel::IsBackgroundApp(
return false;
}
// static
bool BackgroundApplicationListModel::IsTransientBackgroundApp(
const Extension& extension,
Profile* profile) {
return base::FeatureList::IsEnabled(features::kOnConnectNative) &&
extension.permissions_data()->HasAPIPermission(
APIPermission::kTransientBackground) &&
extensions::BackgroundInfo::HasLazyBackgroundPage(&extension);
}
bool BackgroundApplicationListModel::HasPersistentBackgroundApps() const {
for (auto& extension : extensions_) {
if (IsPersistentBackgroundApp(*extension, profile_)) {
return true;
}
}
return false;
}
void BackgroundApplicationListModel::Observe(
int type,
const content::NotificationSource& source,
......@@ -321,12 +348,16 @@ void BackgroundApplicationListModel::OnExtensionSystemReady() {
background_contents_service_observer_.Add(
BackgroundContentsServiceFactory::GetForProfile(profile_));
if (base::FeatureList::IsEnabled(features::kOnConnectNative))
process_manager_observer_.Add(extensions::ProcessManager::Get(profile_));
startup_done_ = true;
}
void BackgroundApplicationListModel::OnShutdown(ExtensionRegistry* registry) {
DCHECK_EQ(ExtensionRegistry::Get(profile_), registry);
extension_registry_observer_.Remove(registry);
process_manager_observer_.RemoveAll();
}
void BackgroundApplicationListModel::OnBackgroundContentsServiceChanged() {
......@@ -341,17 +372,18 @@ void BackgroundApplicationListModel::OnExtensionPermissionsUpdated(
const Extension* extension,
UpdatedExtensionPermissionsInfo::Reason reason,
const PermissionSet& permissions) {
if (permissions.HasAPIPermission(APIPermission::kBackground)) {
if (permissions.HasAPIPermission(APIPermission::kBackground) ||
(base::FeatureList::IsEnabled(features::kOnConnectNative) &&
permissions.HasAPIPermission(APIPermission::kTransientBackground))) {
switch (reason) {
case UpdatedExtensionPermissionsInfo::ADDED:
DCHECK(IsBackgroundApp(*extension, profile_));
Update();
AssociateApplicationData(extension);
break;
case UpdatedExtensionPermissionsInfo::REMOVED:
DCHECK(!IsBackgroundApp(*extension, profile_));
Update();
if (IsBackgroundApp(*extension, profile_)) {
AssociateApplicationData(extension);
} else {
DissociateApplicationData(extension);
}
break;
default:
NOTREACHED();
......@@ -391,3 +423,22 @@ void BackgroundApplicationListModel::Update() {
observer.OnApplicationListChanged(profile_);
}
}
void BackgroundApplicationListModel::OnBackgroundHostCreated(
extensions::ExtensionHost* host) {
if (IsTransientBackgroundApp(*host->extension(), profile_)) {
Update();
}
}
void BackgroundApplicationListModel::OnBackgroundHostClose(
const std::string& extension_id) {
auto* extension =
ExtensionRegistry::Get(profile_)->GetInstalledExtension(extension_id);
if (!extension || !IsTransientBackgroundApp(*extension, profile_)) {
return;
}
Update();
}
......@@ -20,6 +20,8 @@
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_manager_observer.h"
#include "extensions/common/extension.h"
class Profile;
......@@ -38,7 +40,8 @@ class ExtensionRegistry;
class BackgroundApplicationListModel
: public content::NotificationObserver,
public extensions::ExtensionRegistryObserver,
public BackgroundContentsServiceObserver {
public BackgroundContentsServiceObserver,
public extensions::ProcessManagerObserver {
public:
// Observer is informed of changes to the model. Users of the
// BackgroundApplicationListModel should anticipate that associated data,
......@@ -83,6 +86,19 @@ class BackgroundApplicationListModel
// Returns true if the passed extension is a background app.
static bool IsBackgroundApp(const extensions::Extension& extension,
Profile* profile) {
return IsPersistentBackgroundApp(extension, profile) ||
IsTransientBackgroundApp(extension, profile);
}
// Returns true if the passed extension is a persistent background app.
static bool IsPersistentBackgroundApp(const extensions::Extension& extension,
Profile* profile);
// Returns true if the passed extension is a transient background app.
// Transient background apps should only be treated as background apps while
// their background page is active.
static bool IsTransientBackgroundApp(const extensions::Extension& extension,
Profile* profile);
// Dissociate observer from this model.
......@@ -103,6 +119,8 @@ class BackgroundApplicationListModel
// Returns true if all startup notifications have already been issued.
bool startup_done() const { return startup_done_; }
bool HasPersistentBackgroundApps() const;
private:
// Contains data associated with a background application that is not
// represented by the Extension class.
......@@ -145,10 +163,6 @@ class BackgroundApplicationListModel
// application, e.g. the Icon, has changed.
void SendApplicationDataChangedNotifications();
// Notifies observers that at least one background application has been added
// or removed.
void SendApplicationListChangedNotifications();
// Invoked by Observe for NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED.
void OnExtensionPermissionsUpdated(
const extensions::Extension* extension,
......@@ -158,6 +172,10 @@ class BackgroundApplicationListModel
// Refresh the list of background applications and generate notifications.
void Update();
// ProcessManagerObserver:
void OnBackgroundHostCreated(extensions::ExtensionHost* host) override;
void OnBackgroundHostClose(const std::string& extension_id) override;
// Associates extension id strings with Application objects.
std::map<std::string, std::unique_ptr<Application>> applications_;
......@@ -175,6 +193,9 @@ class BackgroundApplicationListModel
ScopedObserver<BackgroundContentsService, BackgroundContentsServiceObserver>
background_contents_service_observer_{this};
ScopedObserver<extensions::ProcessManager, extensions::ProcessManagerObserver>
process_manager_observer_{this};
base::WeakPtrFactory<BackgroundApplicationListModel> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(BackgroundApplicationListModel);
......
......@@ -130,14 +130,19 @@ Browser* BackgroundModeManager::BackgroundModeData::GetBrowserWindow() {
return BackgroundModeManager::GetBrowserWindowForProfile(profile_);
}
bool BackgroundModeManager::BackgroundModeData::HasBackgroundClient() const {
bool BackgroundModeManager::BackgroundModeData::HasPersistentBackgroundClient()
const {
return applications_->HasPersistentBackgroundApps();
}
bool BackgroundModeManager::BackgroundModeData::HasAnyBackgroundClient() const {
return applications_->size() > 0;
}
void BackgroundModeManager::BackgroundModeData::BuildProfileMenu(
StatusIconMenuModel* menu,
StatusIconMenuModel* containing_menu) {
if (HasBackgroundClient()) {
if (HasAnyBackgroundClient()) {
// Add a menu item for each application (extension).
for (const auto& application : *applications_) {
gfx::ImageSkia icon = applications_->GetIcon(application.get());
......@@ -205,12 +210,15 @@ BackgroundModeManager::BackgroundModeData::GetNewBackgroundApps() {
// Not found in our set yet - add it and maybe return as a previously
// unseen extension.
current_extensions_.insert(id);
// If this application has been newly loaded after the initial startup,
// notify the user.
if (applications_->startup_done())
// If this application has been newly loaded after the initial startup and
// this is a persistent background app, notify the user.
if (applications_->startup_done() &&
BackgroundApplicationListModel::IsPersistentBackgroundApp(
*application, profile_)) {
new_apps.insert(application.get());
}
}
}
return new_apps;
}
......@@ -416,6 +424,7 @@ void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() {
bool enabled = IsBackgroundModePrefEnabled();
UMA_HISTOGRAM_BOOLEAN("BackgroundMode.BackgroundModeEnabledPrefChanged",
enabled);
UpdateEnableLaunchOnStartup();
if (enabled)
EnableBackgroundMode();
else
......@@ -481,8 +490,8 @@ void BackgroundModeManager::OnProfileWillBeRemoved(
background_mode_data_.erase(it);
// If there are no background mode profiles any longer, then turn off
// background mode.
UpdateEnableLaunchOnStartup();
if (!ShouldBeInBackgroundMode()) {
EnableLaunchOnStartup(false);
EndBackgroundMode();
}
UpdateStatusTrayIconContextMenu();
......@@ -633,7 +642,7 @@ void BackgroundModeManager::EnableBackgroundMode() {
if (!in_background_mode_ && ShouldBeInBackgroundMode()) {
StartBackgroundMode();
EnableLaunchOnStartup(true);
UpdateEnableLaunchOnStartup();
}
}
......@@ -642,7 +651,6 @@ void BackgroundModeManager::DisableBackgroundMode() {
// If background mode is currently enabled, turn it off.
if (in_background_mode_) {
EndBackgroundMode();
EnableLaunchOnStartup(false);
}
}
......@@ -685,13 +693,14 @@ void BackgroundModeManager::OnClientsChanged(
ProfileAttributesEntry* entry;
if (profile_storage_->
GetProfileAttributesWithPath(profile->GetPath(), &entry)) {
entry->SetBackgroundStatus(HasBackgroundClientForProfile(profile));
entry->SetBackgroundStatus(
HasPersistentBackgroundClientForProfile(profile));
}
UpdateEnableLaunchOnStartup();
if (!ShouldBeInBackgroundMode()) {
// We've uninstalled our last background client, make sure we exit
// background mode and no longer launch on startup.
EnableLaunchOnStartup(false);
EndBackgroundMode();
} else {
// We have at least one background client - make sure we're in background
......@@ -701,7 +710,6 @@ void BackgroundModeManager::OnClientsChanged(
// enabled. On Mac, the platform-specific code tracks whether the user
// has deleted a login item in the past, and if so, no login item will
// be created (to avoid overriding the specific user action).
EnableLaunchOnStartup(true);
StartBackgroundMode();
}
......@@ -714,24 +722,32 @@ void BackgroundModeManager::OnClientsChanged(
}
}
bool BackgroundModeManager::HasBackgroundClient() const {
bool BackgroundModeManager::HasPersistentBackgroundClient() const {
for (const auto& it : background_mode_data_) {
if (it.second->HasBackgroundClient())
if (it.second->HasPersistentBackgroundClient())
return true;
}
return false;
}
bool BackgroundModeManager::HasBackgroundClientForProfile(
bool BackgroundModeManager::HasAnyBackgroundClient() const {
for (const auto& it : background_mode_data_) {
if (it.second->HasAnyBackgroundClient())
return true;
}
return false;
}
bool BackgroundModeManager::HasPersistentBackgroundClientForProfile(
const Profile* profile) const {
BackgroundModeManager::BackgroundModeData* bmd =
GetBackgroundModeData(profile);
return bmd && bmd->HasBackgroundClient();
return bmd && bmd->HasPersistentBackgroundClient();
}
bool BackgroundModeManager::ShouldBeInBackgroundMode() const {
return IsBackgroundModePrefEnabled() &&
(HasBackgroundClient() || keep_alive_for_test_);
(HasAnyBackgroundClient() || keep_alive_for_test_);
}
void BackgroundModeManager::OnBackgroundClientInstalled(
......@@ -750,6 +766,17 @@ void BackgroundModeManager::OnBackgroundClientInstalled(
DisplayClientInstalledNotification(name);
}
void BackgroundModeManager::UpdateEnableLaunchOnStartup() {
bool new_launch_on_startup =
ShouldBeInBackgroundMode() && HasPersistentBackgroundClient();
if (launch_on_startup_enabled_ &&
new_launch_on_startup == *launch_on_startup_enabled_) {
return;
}
launch_on_startup_enabled_.emplace(new_launch_on_startup);
EnableLaunchOnStartup(*launch_on_startup_enabled_);
}
// Gets the image for the status tray icon, at the correct size for the current
// platform and display settings.
gfx::ImageSkia GetStatusTrayIcon() {
......@@ -841,7 +868,7 @@ void BackgroundModeManager::UpdateStatusTrayIconContextMenu() {
for (auto* bmd : bmd_vector) {
// We should only display the profile in the status icon if it has at
// least one background app.
if (bmd->HasBackgroundClient()) {
if (bmd->HasAnyBackgroundClient()) {
// The submenu constructor caller owns the lifetime of the submenu.
// The containing menu does not handle the lifetime.
submenus.push_back(std::make_unique<StatusIconMenuModel>(bmd));
......
......@@ -168,8 +168,13 @@ class BackgroundModeManager : public content::NotificationObserver,
// Browser window.
Browser* GetBrowserWindow();
// Returns if this profile has background clients. A client is an extension.
bool HasBackgroundClient() const;
// Returns if this profile has persistent background clients. A client is an
// extension.
bool HasPersistentBackgroundClient() const;
// Returns if this profile has any background clients. A client is an
// extension.
bool HasAnyBackgroundClient() const;
// Builds the profile specific parts of the menu. The menu passed in may
// be a submenu in the case of multi-profiles or the main menu in the case
......@@ -257,6 +262,10 @@ class BackgroundModeManager : public content::NotificationObserver,
// launch-on-startup is enabled if appropriate.
void OnBackgroundClientInstalled(const base::string16& name);
// Update whether Chrome should be launched on startup, depending on whether
// |this| has any persistent background clients.
void UpdateEnableLaunchOnStartup();
// Called to make sure that our launch-on-startup mode is properly set.
// (virtual so it can be mocked in tests).
virtual void EnableLaunchOnStartup(bool should_launch);
......@@ -321,13 +330,18 @@ class BackgroundModeManager : public content::NotificationObserver,
// Turns on background mode if it's currently disabled.
void EnableBackgroundMode();
// Returns if any profile on the system has a background client.
// Returns if any profile on the system has a persistent background client.
// A client is an extension. (virtual to allow overriding in unit tests)
virtual bool HasBackgroundClient() const;
virtual bool HasPersistentBackgroundClient() const;
// Returns if there are background clients for a profile. A client is an
// extension.
virtual bool HasBackgroundClientForProfile(const Profile* profile) const;
// Returns if any profile on the system has any background client.
// A client is an extension. (virtual to allow overriding in unit tests)
virtual bool HasAnyBackgroundClient() const;
// Returns if there are persistent background clients for a profile. A client
// is an extension.
virtual bool HasPersistentBackgroundClientForProfile(
const Profile* profile) const;
// Returns true if we should be in background mode.
bool ShouldBeInBackgroundMode() const;
......@@ -406,6 +420,8 @@ class BackgroundModeManager : public content::NotificationObserver,
// Set to true when background mode is suspended.
bool background_mode_suspended_ = false;
base::Optional<bool> launch_on_startup_enabled_;
// Task runner for making startup/login configuration changes that may
// require file system or registry access.
const scoped_refptr<base::SequencedTaskRunner> task_runner_;
......
......@@ -136,35 +136,54 @@ class AdvancedTestBackgroundModeManager : public TestBackgroundModeManager {
~AdvancedTestBackgroundModeManager() override {}
// TestBackgroundModeManager:
bool HasBackgroundClient() const override {
for (const auto& profile_count_pair : profile_app_counts_) {
if (profile_count_pair.second > 0)
return true;
bool HasPersistentBackgroundClient() const override {
return std::find_if(profile_app_counts_.begin(), profile_app_counts_.end(),
[](const auto& profile_count_pair) {
return profile_count_pair.second.persistent > 0;
}) != profile_app_counts_.end();
}
return false;
bool HasAnyBackgroundClient() const override {
return std::find_if(profile_app_counts_.begin(), profile_app_counts_.end(),
[](const auto& profile_count_pair) {
return profile_count_pair.second.any > 0;
}) != profile_app_counts_.end();
}
bool HasBackgroundClientForProfile(const Profile* profile) const override {
bool HasPersistentBackgroundClientForProfile(
const Profile* profile) const override {
auto it = profile_app_counts_.find(profile);
if (it == profile_app_counts_.end()) {
ADD_FAILURE();
return false;
}
return it->second > 0;
return it->second.persistent > 0;
}
bool IsBackgroundModePrefEnabled() const override { return enabled_; }
void SetBackgroundClientCountForProfile(const Profile* profile,
size_t count) {
profile_app_counts_[profile] = count;
profile_app_counts_[profile] = {count, count};
}
void SetPersistentBackgroundClientCountForProfile(const Profile* profile,
size_t count) {
profile_app_counts_[profile].persistent = count;
}
void SetEnabled(bool enabled) {
enabled_ = enabled;
OnBackgroundModeEnabledPrefChanged();
}
using BackgroundModeManager::OnApplicationListChanged;
private:
struct AppCounts {
size_t any = 0;
size_t persistent = 0;
};
bool enabled_;
std::map<const Profile*, size_t> profile_app_counts_;
std::map<const Profile*, AppCounts> profile_app_counts_;
DISALLOW_COPY_AND_ASSIGN(AdvancedTestBackgroundModeManager);
};
......@@ -358,10 +377,12 @@ TEST_F(BackgroundModeManagerTest, BackgroundAppInstallUninstallWhileDisabled) {
*command_line_, profile_manager_->profile_attributes_storage(), true);
manager.RegisterProfile(profile_);
// Turn off background mode (shouldn't explicitly disable launch-on-startup as
// the app-count is zero and launch-on-startup shouldn't be considered on).
// Turn off background mode (should explicitly disable launch-on-startup as
// the app-count is zero and launch-on-startup hasn't been initialized yet).
EXPECT_CALL(manager, EnableLaunchOnStartup(false)).Times(Exactly(1));
manager.SetEnabled(false);
AssertBackgroundModeInactive(manager);
Mock::VerifyAndClearExpectations(&manager);
// When a new client is installed, status tray icons will not be created,
// launch on startup status will not be modified.
......@@ -890,3 +911,128 @@ TEST_F(BackgroundModeManagerWithExtensionsTest, BalloonDisplay) {
service->AddExtension(upgraded_no_bg_ext_has_bg.get());
EXPECT_TRUE(manager_->HasShownBalloon());
}
TEST_F(BackgroundModeManagerTest, TransientBackgroundApp) {
AdvancedTestBackgroundModeManager manager(
*command_line_, profile_manager_->profile_attributes_storage(), true);
manager.RegisterProfile(profile_);
ProfileAttributesEntry* entry;
ASSERT_TRUE(profile_manager_->profile_attributes_storage()
->GetProfileAttributesWithPath(profile_->GetPath(), &entry));
EXPECT_FALSE(entry->GetBackgroundStatus());
EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());
EXPECT_CALL(manager, EnableLaunchOnStartup(false)).Times(1);
manager.SetBackgroundClientCountForProfile(profile_, 0);
manager.OnApplicationListChanged(profile_);
Mock::VerifyAndClearExpectations(&manager);
// Mimic transient app launch.
EXPECT_CALL(manager, EnableLaunchOnStartup(_)).Times(0);
manager.SetBackgroundClientCountForProfile(profile_, 1);
manager.SetPersistentBackgroundClientCountForProfile(profile_, 0);
manager.OnApplicationListChanged(profile_);
Mock::VerifyAndClearExpectations(&manager);
AssertBackgroundModeActive(manager);
EXPECT_FALSE(entry->GetBackgroundStatus());
manager.SuspendBackgroundMode();
AssertBackgroundModeInactive(manager);
EXPECT_FALSE(entry->GetBackgroundStatus());
manager.ResumeBackgroundMode();
// Mimic transient app shutdown.
EXPECT_CALL(manager, EnableLaunchOnStartup(_)).Times(0);
manager.SetBackgroundClientCountForProfile(profile_, 0);
manager.OnApplicationListChanged(profile_);
Mock::VerifyAndClearExpectations(&manager);
AssertBackgroundModeInactive(manager);
EXPECT_FALSE(entry->GetBackgroundStatus());
}
TEST_F(BackgroundModeManagerTest, TransientBackgroundAppWithPersistent) {
AdvancedTestBackgroundModeManager manager(
*command_line_, profile_manager_->profile_attributes_storage(), true);
manager.RegisterProfile(profile_);
ProfileAttributesEntry* entry;
ASSERT_TRUE(profile_manager_->profile_attributes_storage()
->GetProfileAttributesWithPath(profile_->GetPath(), &entry));
EXPECT_FALSE(entry->GetBackgroundStatus());
EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());
EXPECT_CALL(manager, EnableLaunchOnStartup(true)).Times(1);
manager.SetBackgroundClientCountForProfile(profile_, 1);
manager.OnApplicationListChanged(profile_);
Mock::VerifyAndClearExpectations(&manager);
AssertBackgroundModeActive(manager);
EXPECT_TRUE(entry->GetBackgroundStatus());
// Mimic transient app launch.
EXPECT_CALL(manager, EnableLaunchOnStartup(_)).Times(0);
manager.SetBackgroundClientCountForProfile(profile_, 2);
manager.SetPersistentBackgroundClientCountForProfile(profile_, 1);
manager.OnApplicationListChanged(profile_);
Mock::VerifyAndClearExpectations(&manager);
AssertBackgroundModeActive(manager);
EXPECT_TRUE(entry->GetBackgroundStatus());
manager.SuspendBackgroundMode();
AssertBackgroundModeInactive(manager);
EXPECT_TRUE(entry->GetBackgroundStatus());
manager.ResumeBackgroundMode();
// Mimic transient app shutdown.
EXPECT_CALL(manager, EnableLaunchOnStartup(_)).Times(0);
manager.SetBackgroundClientCountForProfile(profile_, 1);
manager.OnApplicationListChanged(profile_);
Mock::VerifyAndClearExpectations(&manager);
AssertBackgroundModeActive(manager);
EXPECT_TRUE(entry->GetBackgroundStatus());
}
TEST_F(BackgroundModeManagerTest,
BackgroundPersistentAppWhileTransientRunning) {
AdvancedTestBackgroundModeManager manager(
*command_line_, profile_manager_->profile_attributes_storage(), true);
manager.RegisterProfile(profile_);
ProfileAttributesEntry* entry;
ASSERT_TRUE(profile_manager_->profile_attributes_storage()
->GetProfileAttributesWithPath(profile_->GetPath(), &entry));
EXPECT_FALSE(entry->GetBackgroundStatus());
EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());
// Mimic transient app launch.
EXPECT_CALL(manager, EnableLaunchOnStartup(false)).Times(1);
manager.SetBackgroundClientCountForProfile(profile_, 1);
manager.SetPersistentBackgroundClientCountForProfile(profile_, 0);
manager.OnApplicationListChanged(profile_);
Mock::VerifyAndClearExpectations(&manager);
AssertBackgroundModeActive(manager);
EXPECT_FALSE(entry->GetBackgroundStatus());
// Mimic persistent app install.
EXPECT_CALL(manager, EnableLaunchOnStartup(true)).Times(1);
manager.SetBackgroundClientCountForProfile(profile_, 2);
manager.SetPersistentBackgroundClientCountForProfile(profile_, 1);
manager.OnApplicationListChanged(profile_);
Mock::VerifyAndClearExpectations(&manager);
AssertBackgroundModeActive(manager);
EXPECT_TRUE(entry->GetBackgroundStatus());
manager.SuspendBackgroundMode();
AssertBackgroundModeInactive(manager);
EXPECT_TRUE(entry->GetBackgroundStatus());
manager.ResumeBackgroundMode();
// Mimic persistent app uninstall.
EXPECT_CALL(manager, EnableLaunchOnStartup(false)).Times(1);
manager.SetBackgroundClientCountForProfile(profile_, 1);
manager.SetPersistentBackgroundClientCountForProfile(profile_, 0);
manager.OnApplicationListChanged(profile_);
Mock::VerifyAndClearExpectations(&manager);
AssertBackgroundModeActive(manager);
EXPECT_FALSE(entry->GetBackgroundStatus());
}
......@@ -8,6 +8,8 @@
#include "base/scoped_observer.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/background/background_mode_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.h"
#include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
#include "chrome/browser/extensions/extension_apitest.h"
......@@ -88,6 +90,7 @@ base::CommandLine CreateNativeMessagingConnectCommandLine(
kSupportsNativeInitiatedConnectionsHostName);
command_line.AppendSwitchASCII(switches::kNativeMessagingConnectId,
connect_id);
command_line.AppendSwitch(switches::kNoStartupWindow);
return command_line;
}
......@@ -120,10 +123,16 @@ IN_PROC_BROWSER_TEST_F(NativeMessagingLaunchApiTest, MAYBE_Success) {
ResultCatcher catcher;
EXPECT_FALSE(
g_browser_process->background_mode_manager()->IsBackgroundModeActive());
StartupBrowserCreator::ProcessCommandLineAlreadyRunning(
CreateNativeMessagingConnectCommandLine("test-connect-id"), {},
profile()->GetPath());
EXPECT_TRUE(
g_browser_process->background_mode_manager()->IsBackgroundModeActive());
if (!catcher.GetNextResult()) {
FAIL() << catcher.message();
}
......@@ -156,6 +165,7 @@ IN_PROC_BROWSER_TEST_F(NativeMessagingLaunchApiTest, UnsupportedByNativeHost) {
extension->id());
command_line.AppendSwitchASCII(switches::kNativeMessagingConnectHost,
ScopedTestNativeMessagingHost::kHostName);
command_line.AppendSwitch(switches::kNoStartupWindow);
StartupBrowserCreator::ProcessCommandLineAlreadyRunning(command_line, {},
profile()->GetPath());
......@@ -310,6 +320,75 @@ IN_PROC_BROWSER_TEST_F(NativeMessagingLaunchApiTest, InvalidExtensionId) {
test_host_.temp_dir().AppendASCII("invalid_connect_id.txt")));
}
constexpr char kExtensionId[] = "knldjmfmopnpolahpmmgbagdohdnhkik";
class NativeMessagingLaunchBackgroundModeApiTest
: public NativeMessagingLaunchApiTest {
public:
NativeMessagingLaunchBackgroundModeApiTest() {
ProcessManager::SetEventPageIdleTimeForTesting(1);
ProcessManager::SetEventPageSuspendingTimeForTesting(1);
test_host_.RegisterTestHost(false);
}
NativeMessagingLaunchBackgroundModeApiTest(
const NativeMessagingLaunchBackgroundModeApiTest&) = delete;
NativeMessagingLaunchBackgroundModeApiTest& operator=(
const NativeMessagingLaunchBackgroundModeApiTest&) = delete;
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
NativeMessagingLaunchApiTest::SetUpCommandLine(command_line);
if (base::StringPiece(
::testing::UnitTest::GetInstance()->current_test_info()->name())
.starts_with("PRE")) {
return;
}
set_exit_when_last_browser_closes(false);
command_line->AppendSwitchASCII(switches::kNativeMessagingConnectExtension,
kExtensionId);
command_line->AppendSwitchASCII(
switches::kNativeMessagingConnectHost,
ScopedTestNativeMessagingHost::
kSupportsNativeInitiatedConnectionsHostName);
command_line->AppendSwitchASCII(switches::kNativeMessagingConnectId,
"test-connect-id");
command_line->AppendSwitch(switches::kNoStartupWindow);
}
void SetUpOnMainThread() override {
NativeMessagingLaunchApiTest::SetUpOnMainThread();
catcher_ = std::make_unique<ResultCatcher>();
}
std::unique_ptr<ResultCatcher> catcher_;
};
IN_PROC_BROWSER_TEST_F(NativeMessagingLaunchBackgroundModeApiTest,
PRE_Success) {
auto* extension =
LoadExtension(test_data_dir_.AppendASCII("native_messaging_launch"));
EXPECT_EQ(kExtensionId, extension->id());
}
IN_PROC_BROWSER_TEST_F(NativeMessagingLaunchBackgroundModeApiTest, Success) {
EXPECT_TRUE(
g_browser_process->background_mode_manager()->IsBackgroundModeActive());
if (!catcher_->GetNextResult()) {
FAIL() << catcher_->message();
}
size_t tabs = 0;
for (auto* browser : *BrowserList::GetInstance()) {
tabs += browser->tab_strip_model()->count();
}
EXPECT_EQ(0u, tabs);
ASSERT_NO_FATAL_FAILURE(TestKeepAliveStateObserver().WaitForNoKeepAlive());
}
#endif // !defined(OS_CHROMEOS)
} // namespace
......
......@@ -688,17 +688,25 @@ bool StartupBrowserCreator::ProcessCmdLineImpl(
}
#if !defined(OS_CHROMEOS)
if (!process_startup &&
base::FeatureList::IsEnabled(features::kOnConnectNative) &&
if (base::FeatureList::IsEnabled(features::kOnConnectNative) &&
command_line.HasSwitch(switches::kNativeMessagingConnectHost) &&
command_line.HasSwitch(switches::kNativeMessagingConnectExtension)) {
silent_launch = true;
extensions::LaunchNativeMessageHostFromNativeApp(
command_line.GetSwitchValueASCII(
switches::kNativeMessagingConnectExtension),
command_line.GetSwitchValueASCII(switches::kNativeMessagingConnectHost),
command_line.GetSwitchValueASCII(switches::kNativeMessagingConnectId),
last_used_profile);
// Chrome's lifetime, if the specified extension and native messaging host
// are both valid and a connection is established, is prolonged by
// BackgroundModeManager. If |process_startup| is true, --no-startup-window
// must be set or a browser window must be created for BackgroundModeManager
// to start background mode. Without this, nothing will take the first
// keep-alive and the browser process will not terminate. To avoid this
// situation, don't set |silent_launch| in response to the native messaging
// connect switches; require the client to pass --no-startup-window if
// suppressing the creation of a window is desired.
}
#endif
......
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