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[];