Allow launching an app from the command line using --load-and-launch-app.
BUG=151580
TEST=PlatformAppBrowserTest.LoadAndLaunchApp, PlatformAppLoadAndLaunchBrowserTest.LoadAndLaunchApp
Review URL: https://chromiumcodereview.appspot.com/12177008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@183157 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc
index 152b244..f1b7b5c 100644
--- a/chrome/browser/extensions/extension_browsertest.cc
+++ b/chrome/browser/extensions/extension_browsertest.cc
@@ -36,6 +36,7 @@
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
+#include "chrome/common/extensions/extension_set.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
@@ -78,6 +79,20 @@
return profile_;
}
+// static
+const Extension* ExtensionBrowserTest::GetExtensionByPath(
+ const ExtensionSet* extensions, const base::FilePath& path) {
+ base::FilePath extension_path = path;
+ EXPECT_TRUE(file_util::AbsolutePath(&extension_path));
+ for (ExtensionSet::const_iterator iter = extensions->begin();
+ iter != extensions->end(); ++iter) {
+ if ((*iter)->path() == extension_path) {
+ return *iter;
+ }
+ }
+ return NULL;
+}
+
void ExtensionBrowserTest::SetUpCommandLine(CommandLine* command_line) {
PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_);
test_data_dir_ = test_data_dir_.AppendASCII("extensions");
@@ -111,16 +126,7 @@
// Find the loaded extension by its path. See crbug.com/59531 for why
// we cannot just use last_loaded_extension_id_.
- base::FilePath extension_path = path;
- file_util::AbsolutePath(&extension_path);
- const Extension* extension = NULL;
- for (ExtensionSet::const_iterator iter = service->extensions()->begin();
- iter != service->extensions()->end(); ++iter) {
- if ((*iter)->path() == extension_path) {
- extension = *iter;
- break;
- }
- }
+ const Extension* extension = GetExtensionByPath(service->extensions(), path);
if (!extension)
return NULL;
diff --git a/chrome/browser/extensions/extension_browsertest.h b/chrome/browser/extensions/extension_browsertest.h
index 375b15e..3c8ff93 100644
--- a/chrome/browser/extensions/extension_browsertest.h
+++ b/chrome/browser/extensions/extension_browsertest.h
@@ -24,8 +24,9 @@
#include "content/public/browser/notification_types.h"
#include "content/public/browser/web_contents.h"
-class ExtensionService;
class ExtensionProcessManager;
+class ExtensionService;
+class ExtensionSet;
class Profile;
// Base class for extension browser tests. Provides utilities for loading,
@@ -63,6 +64,9 @@
// Get the profile to use.
Profile* profile();
+ static const extensions::Extension* GetExtensionByPath(
+ const ExtensionSet* extensions, const base::FilePath& path);
+
// InProcessBrowserTest
virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE;
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index e423d815..f4f7997 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -2310,6 +2310,10 @@
child_process_logging::SetActiveExtensions(extension_ids);
}
+void ExtensionService::ScheduleLaunchOnLoad(const std::string& extension_id) {
+ on_load_events_[extension_id] = EVENT_LAUNCHED;
+}
+
void ExtensionService::OnExtensionInstalled(
const Extension* extension,
const syncer::StringOrdinal& page_ordinal,
diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h
index 2f71d56..810725c3 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -437,6 +437,9 @@
virtual void AddComponentExtension(const extensions::Extension* extension)
OVERRIDE;
+ // Launch an extension the next time it is loaded.
+ void ScheduleLaunchOnLoad(const std::string& extension_id);
+
// Informs the service that an extension's files are in place for loading.
//
// Please make sure the Blacklist is checked some time before calling this
diff --git a/chrome/browser/extensions/extension_system.cc b/chrome/browser/extensions/extension_system.cc
index f2da0b4..d1031f271 100644
--- a/chrome/browser/extensions/extension_system.cc
+++ b/chrome/browser/extensions/extension_system.cc
@@ -194,7 +194,7 @@
FILE_PATH_LITERAL(","));
while (t.GetNext()) {
UnpackedInstaller::Create(extension_service_.get())->
- LoadFromCommandLine(base::FilePath(t.token()));
+ LoadFromCommandLine(base::FilePath(t.token()), false);
}
}
}
diff --git a/chrome/browser/extensions/load_and_launch_browsertest.cc b/chrome/browser/extensions/load_and_launch_browsertest.cc
new file mode 100644
index 0000000..7f1e1d1
--- /dev/null
+++ b/chrome/browser/extensions/load_and_launch_browsertest.cc
@@ -0,0 +1,97 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Tests for the --load-and-launch-app switch.
+// The two cases are when chrome is running and another process uses the switch
+// and when chrome is started from scratch.
+
+#include "base/test/test_timeouts.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/extensions/extension_test_message_listener.h"
+#include "chrome/browser/extensions/platform_app_browsertest_util.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/test/test_launcher.h"
+
+namespace extensions {
+
+// TODO(jackhou): Enable this test once it works on OSX. It currently does not
+// work for the same reason --app-id doesn't. See http://crbug.com/148465
+#if defined(OS_MACOSX)
+#define MAYBE_LoadAndLaunchAppChromeRunning \
+ DISABLED_LoadAndLaunchAppChromeRunning
+#else
+#define MAYBE_LoadAndLaunchAppChromeRunning LoadAndLaunchAppChromeRunning
+#endif
+
+// Case where Chrome is already running.
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ MAYBE_LoadAndLaunchAppChromeRunning) {
+ ExtensionTestMessageListener launched_listener("Launched", false);
+
+ const CommandLine& cmdline = *CommandLine::ForCurrentProcess();
+ CommandLine new_cmdline(cmdline.GetProgram());
+
+ const char* kSwitchNames[] = {
+ switches::kUserDataDir,
+ };
+ new_cmdline.CopySwitchesFrom(cmdline, kSwitchNames, arraysize(kSwitchNames));
+
+ base::FilePath app_path = test_data_dir_
+ .AppendASCII("platform_apps")
+ .AppendASCII("minimal");
+
+ new_cmdline.AppendSwitchNative(switches::kLoadAndLaunchApp,
+ app_path.value());
+
+ new_cmdline.AppendSwitch(content::kLaunchAsBrowser);
+ base::ProcessHandle process;
+ base::LaunchProcess(new_cmdline, base::LaunchOptions(), &process);
+ ASSERT_NE(base::kNullProcessHandle, process);
+
+ ASSERT_TRUE(launched_listener.WaitUntilSatisfied());
+ ASSERT_TRUE(base::WaitForSingleProcess(
+ process, TestTimeouts::action_timeout()));
+}
+
+namespace {
+
+// TestFixture that appends --load-and-launch-app before calling BrowserMain.
+class PlatformAppLoadAndLaunchBrowserTest : public PlatformAppBrowserTest {
+ protected:
+ PlatformAppLoadAndLaunchBrowserTest() {}
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ PlatformAppBrowserTest::SetUpCommandLine(command_line);
+ app_path_ = test_data_dir_
+ .AppendASCII("platform_apps")
+ .AppendASCII("minimal");
+ command_line->AppendSwitchNative(switches::kLoadAndLaunchApp,
+ app_path_.value());
+ }
+
+ void LoadAndLaunchApp() {
+ ExtensionTestMessageListener launched_listener("Launched", false);
+ ASSERT_TRUE(launched_listener.WaitUntilSatisfied());
+
+ // Start an actual browser because we can't shut down with just an app
+ // window.
+ CreateBrowser(ProfileManager::GetDefaultProfile());
+ }
+
+ private:
+ base::FilePath app_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformAppLoadAndLaunchBrowserTest);
+};
+
+} // namespace
+
+// Case where Chrome is not running.
+IN_PROC_BROWSER_TEST_F(PlatformAppLoadAndLaunchBrowserTest,
+ LoadAndLaunchAppChromeNotRunning) {
+ LoadAndLaunchApp();
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/platform_app_browsertest.cc b/chrome/browser/extensions/platform_app_browsertest.cc
index 3e39b37..d2a00c9 100644
--- a/chrome/browser/extensions/platform_app_browsertest.cc
+++ b/chrome/browser/extensions/platform_app_browsertest.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "base/bind.h"
+#include "base/file_util.h"
#include "base/prefs/pref_service.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
diff --git a/chrome/browser/extensions/unpacked_installer.cc b/chrome/browser/extensions/unpacked_installer.cc
index b6bc15f8..ec6739c 100644
--- a/chrome/browser/extensions/unpacked_installer.cc
+++ b/chrome/browser/extensions/unpacked_installer.cc
@@ -99,7 +99,8 @@
: service_weak_(extension_service->AsWeakPtr()),
prompt_for_plugins_(true),
requirements_checker_(new RequirementsChecker()),
- require_modern_manifest_version_(true) {
+ require_modern_manifest_version_(true),
+ launch_on_load_(false) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
@@ -115,7 +116,8 @@
base::Bind(&UnpackedInstaller::GetAbsolutePath, this));
}
-void UnpackedInstaller::LoadFromCommandLine(const base::FilePath& path_in) {
+void UnpackedInstaller::LoadFromCommandLine(const base::FilePath& path_in,
+ bool launch_on_load) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(extension_path_.empty());
@@ -145,6 +147,8 @@
return;
}
+ launch_on_load_ = launch_on_load;
+
CheckRequirements();
}
@@ -264,6 +268,10 @@
PermissionsUpdater perms_updater(service_weak_->profile());
perms_updater.GrantActivePermissions(extension_, false);
+
+ if (launch_on_load_)
+ service_weak_->ScheduleLaunchOnLoad(extension_->id());
+
service_weak_->OnExtensionInstalled(extension_,
syncer::StringOrdinal(),
false /* no requirement errors */,
diff --git a/chrome/browser/extensions/unpacked_installer.h b/chrome/browser/extensions/unpacked_installer.h
index d060aa8..61790d09 100644
--- a/chrome/browser/extensions/unpacked_installer.h
+++ b/chrome/browser/extensions/unpacked_installer.h
@@ -39,8 +39,10 @@
// Loads the extension from the directory |extension_path|;
// for use with command line switch --load-extension=path.
- // This is equivalent to Load, except that it runs synchronously.
- void LoadFromCommandLine(const base::FilePath& extension_path);
+ // This is equivalent to Load, except that it runs synchronously and
+ // optionally launches the extension once it's loaded.
+ void LoadFromCommandLine(const base::FilePath& extension_path,
+ bool launch_on_load);
// Allows prompting for plugins to be disabled; intended for testing only.
bool prompt_for_plugins() { return prompt_for_plugins_; }
@@ -108,6 +110,9 @@
// version.
bool require_modern_manifest_version_;
+ // Whether to launch the extension once it's loaded.
+ bool launch_on_load_;
+
DISALLOW_COPY_AND_ASSIGN(UnpackedInstaller);
};
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index 207f9e0..8239f227 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -32,6 +32,7 @@
#include "chrome/browser/browser_process.h"
#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
#include "chrome/browser/extensions/startup_helper.h"
+#include "chrome/browser/extensions/unpacked_installer.h"
#include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/google/google_util.h"
#include "chrome/browser/net/url_fixer_upper.h"
@@ -570,59 +571,78 @@
// If we don't want to launch a new browser window or tab (in the case
// of an automation request), we are done here.
- if (!silent_launch) {
- chrome::startup::IsProcessStartup is_process_startup = process_startup ?
- chrome::startup::IS_PROCESS_STARTUP :
- chrome::startup::IS_NOT_PROCESS_STARTUP;
- chrome::startup::IsFirstRun is_first_run = first_run::IsChromeFirstRun() ?
- chrome::startup::IS_FIRST_RUN : chrome::startup::IS_NOT_FIRST_RUN;
- // |last_opened_profiles| will be empty in the following circumstances:
- // - This is the first launch. |last_used_profile| is the initial profile.
- // - The user exited the browser by closing all windows for all
- // profiles. |last_used_profile| is the profile which owned the last open
- // window.
- // - Only incognito windows were open when the browser exited.
- // |last_used_profile| is the last used incognito profile. Restoring it will
- // create a browser window for the corresponding original profile.
- if (last_opened_profiles.empty()) {
- if (!browser_creator->LaunchBrowser(command_line, last_used_profile,
- cur_dir, is_process_startup,
- is_first_run, return_code)) {
- return false;
- }
- } else {
- // Launch the last used profile with the full command line, and the other
- // opened profiles without the URLs to launch.
- CommandLine command_line_without_urls(command_line.GetProgram());
- const CommandLine::SwitchMap& switches = command_line.GetSwitches();
- for (CommandLine::SwitchMap::const_iterator switch_it = switches.begin();
- switch_it != switches.end(); ++switch_it) {
- command_line_without_urls.AppendSwitchNative(switch_it->first,
- switch_it->second);
- }
- // Launch the profiles in the order they became active.
- for (Profiles::const_iterator it = last_opened_profiles.begin();
- it != last_opened_profiles.end(); ++it) {
- // Don't launch additional profiles which would only open a new tab
- // page. When restarting after an update, all profiles will reopen last
- // open pages.
- SessionStartupPref startup_pref =
- GetSessionStartupPref(command_line, *it);
- if (*it != last_used_profile &&
- startup_pref.type == SessionStartupPref::DEFAULT &&
- !HasPendingUncleanExit(*it))
- continue;
- if (!browser_creator->LaunchBrowser((*it == last_used_profile) ?
- command_line : command_line_without_urls, *it, cur_dir,
- is_process_startup, is_first_run, return_code))
- return false;
- // We've launched at least one browser.
- is_process_startup = chrome::startup::IS_NOT_PROCESS_STARTUP;
- }
- // This must be done after all profiles have been launched so the observer
- // knows about all profiles to wait for before activating this one.
- profile_launch_observer.Get().set_profile_to_activate(last_used_profile);
+ if (silent_launch)
+ return true;
+
+ // Check for --load-and-launch-app.
+ if (command_line.HasSwitch(switches::kLoadAndLaunchApp) &&
+ !IncognitoModePrefs::ShouldLaunchIncognito(
+ command_line, last_used_profile->GetPrefs())) {
+ CommandLine::StringType path = command_line.GetSwitchValueNative(
+ switches::kLoadAndLaunchApp);
+ extensions::UnpackedInstaller::Create(
+ last_used_profile->GetExtensionService())->
+ LoadFromCommandLine(base::FilePath(path), true);
+ // Return early here since we don't want to open a browser window.
+ // The exception is when there are no browser windows, since we don't want
+ // chrome to shut down.
+ // TODO(jackhou): Do this properly once keep-alive is handled by the
+ // background page of apps. Tracked at http://crbug.com/175381
+ if (chrome::GetBrowserCount(last_used_profile) != 0)
+ return true;
+ }
+
+ chrome::startup::IsProcessStartup is_process_startup = process_startup ?
+ chrome::startup::IS_PROCESS_STARTUP :
+ chrome::startup::IS_NOT_PROCESS_STARTUP;
+ chrome::startup::IsFirstRun is_first_run = first_run::IsChromeFirstRun() ?
+ chrome::startup::IS_FIRST_RUN : chrome::startup::IS_NOT_FIRST_RUN;
+ // |last_opened_profiles| will be empty in the following circumstances:
+ // - This is the first launch. |last_used_profile| is the initial profile.
+ // - The user exited the browser by closing all windows for all
+ // profiles. |last_used_profile| is the profile which owned the last open
+ // window.
+ // - Only incognito windows were open when the browser exited.
+ // |last_used_profile| is the last used incognito profile. Restoring it will
+ // create a browser window for the corresponding original profile.
+ if (last_opened_profiles.empty()) {
+ if (!browser_creator->LaunchBrowser(command_line, last_used_profile,
+ cur_dir, is_process_startup,
+ is_first_run, return_code)) {
+ return false;
}
+ } else {
+ // Launch the last used profile with the full command line, and the other
+ // opened profiles without the URLs to launch.
+ CommandLine command_line_without_urls(command_line.GetProgram());
+ const CommandLine::SwitchMap& switches = command_line.GetSwitches();
+ for (CommandLine::SwitchMap::const_iterator switch_it = switches.begin();
+ switch_it != switches.end(); ++switch_it) {
+ command_line_without_urls.AppendSwitchNative(switch_it->first,
+ switch_it->second);
+ }
+ // Launch the profiles in the order they became active.
+ for (Profiles::const_iterator it = last_opened_profiles.begin();
+ it != last_opened_profiles.end(); ++it) {
+ // Don't launch additional profiles which would only open a new tab
+ // page. When restarting after an update, all profiles will reopen last
+ // open pages.
+ SessionStartupPref startup_pref =
+ GetSessionStartupPref(command_line, *it);
+ if (*it != last_used_profile &&
+ startup_pref.type == SessionStartupPref::DEFAULT &&
+ !HasPendingUncleanExit(*it))
+ continue;
+ if (!browser_creator->LaunchBrowser((*it == last_used_profile) ?
+ command_line : command_line_without_urls, *it, cur_dir,
+ is_process_startup, is_first_run, return_code))
+ return false;
+ // We've launched at least one browser.
+ is_process_startup = chrome::startup::IS_NOT_PROCESS_STARTUP;
+ }
+ // This must be done after all profiles have been launched so the observer
+ // knows about all profiles to wait for before activating this one.
+ profile_launch_observer.Get().set_profile_to_activate(last_used_profile);
}
return true;
}
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index b8fd74e..be939e70 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1223,6 +1223,7 @@
'browser/extensions/isolated_app_browsertest.cc',
'browser/extensions/lazy_background_page_apitest.cc',
'browser/extensions/lazy_background_page_test_util.h',
+ 'browser/extensions/load_and_launch_browsertest.cc',
'browser/extensions/mutation_observers_apitest.cc',
'browser/extensions/options_page_apitest.cc',
'browser/extensions/page_action_browsertest.cc',
@@ -1606,6 +1607,7 @@
],
}, { # chromeos==1
'sources!': [
+ 'browser/extensions/load_and_launch_browsertest.cc',
'browser/printing/cloud_print/test/cloud_print_policy_browsertest.cc',
'browser/printing/cloud_print/test/cloud_print_proxy_process_browsertest.cc',
'browser/service/service_process_control_browsertest.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 276dcc2..91a5ac2 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -806,6 +806,9 @@
// given item, and then prompt the user to download and install it.
const char kLimitedInstallFromWebstore[] = "limited-install-from-webstore";
+// Loads an app from the specified directory and launches it.
+const char kLoadAndLaunchApp[] = "load-and-launch-app";
+
// Comma-separated list of directories with component extensions to load.
const char kLoadComponentExtension[] = "load-component-extension";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 09ff95f4..3f2a3b015 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -221,6 +221,7 @@
extern const char kKioskMode[];
extern const char kKioskModePrinting[];
extern const char kLimitedInstallFromWebstore[];
+extern const char kLoadAndLaunchApp[];
extern const char kLoadComponentExtension[];
extern const char kLoadExtension[];
extern const char kLoadOpencryptoki[];