Implement launch disposition for extension-apps.

This change adds an --app-id command switch that signifies that the extension-app with the given id should be launched according to its configuration.

It also adds parsing for app.window_type in the manifest and the behavior that when installed and a desktop shortcut is created, the --app-id switch is used rather than the --app switch.

BUG=32361

Review URL: http://codereview.chromium.org/573016

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38184 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/browser_init.cc b/chrome/browser/browser_init.cc
index 4c90a4b..4353846 100644
--- a/chrome/browser/browser_init.cc
+++ b/chrome/browser/browser_init.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/chrome_thread.h"
 #include "chrome/browser/defaults.h"
 #include "chrome/browser/extensions/extension_creator.h"
+#include "chrome/browser/extensions/extensions_service.h"
 #include "chrome/browser/first_run.h"
 #include "chrome/browser/net/dns_global.h"
 #include "chrome/browser/profile.h"
@@ -504,13 +505,42 @@
 }
 
 bool BrowserInit::LaunchWithProfile::OpenApplicationURL(Profile* profile) {
-  if (!command_line_.HasSwitch(switches::kApp))
+  if (!command_line_.HasSwitch(switches::kApp) &&
+      !command_line_.HasSwitch(switches::kAppId))
     return false;
-  bool launch_as_panel =
-      command_line_.HasSwitch(switches::kEnableExtensionApps) &&
-      command_line_.HasSwitch(switches::kAppLaunchAsPanel);
 
   std::string url_string(command_line_.GetSwitchValueASCII(switches::kApp));
+  bool launch_as_panel = false;
+
+  if (command_line_.HasSwitch(switches::kEnableExtensionApps)) {
+    if (command_line_.HasSwitch(switches::kAppId)) {
+      // TODO(rafaelw): There are two legitimate cases where the extensions
+      // service could not be ready at this point which need to be handled:
+      // 1) The locale has changed and the manifests stored in the preferences
+      //    need to be relocalized.
+      // 2) An externally installed extension will be found and installed.
+      ExtensionsService* extensions_service = profile->GetExtensionsService();
+      if (!extensions_service->is_ready())
+        return false;
+
+      std::string app_id(command_line_.GetSwitchValueASCII(switches::kAppId));
+      Extension* extension_app =
+          extensions_service->GetExtensionById(app_id, false);
+
+      // The extension with |app_id| could't be found, most likely because it
+      // was uninstalled.
+      // TODO(rafaelw): Do something reasonable here. Pop up a warning panel?
+      // Open a URL to the gallery page of the extension id?
+      if (!extension_app)
+        return false;
+      url_string = extension_app->app_launch_url().spec();
+      launch_as_panel =
+          extension_app->app_launch_window_type() == Extension::PANEL;
+    } else {
+      launch_as_panel = command_line_.HasSwitch(switches::kAppLaunchAsPanel);
+    }
+  }
+
 #if defined(OS_WIN)  // Fix up Windows shortcuts.
   ReplaceSubstringsAfterOffset(&url_string, 0, "\\x", "%");
 #endif
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index 675fc02..7549eb2e 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -250,6 +250,7 @@
 
     ShellIntegration::ShortcutInfo shortcut_info;
     shortcut_info.url = extension_->app_launch_url();
+    shortcut_info.extension_id = UTF8ToUTF16(extension_->id());
     shortcut_info.title = UTF8ToUTF16(extension_->name());
     shortcut_info.description = UTF8ToUTF16(extension_->description());
     shortcut_info.favicon = icon;
diff --git a/chrome/browser/shell_integration.cc b/chrome/browser/shell_integration.cc
index 4ec9096..debaca55 100644
--- a/chrome/browser/shell_integration.cc
+++ b/chrome/browser/shell_integration.cc
@@ -4,10 +4,64 @@
 
 #include "chrome/browser/shell_integration.h"
 
+#include "base/command_line.h"
+#include "base/file_util.h"
 #include "base/path_service.h"
 #include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/browser/chrome_thread.h"
 
+std::string ShellIntegration::GetCommandLineArgumentsCommon(const GURL& url,
+    const string16& extension_app_id) {
+  const CommandLine cmd = *CommandLine::ForCurrentProcess();
+  std::wstring arguments_w;
+
+  // Use the same UserDataDir for new launches that we currently have set.
+  std::wstring user_data_dir = cmd.GetSwitchValue(switches::kUserDataDir);
+  if (!user_data_dir.empty()) {
+    // Make sure user_data_dir is an absolute path.
+    if (file_util::AbsolutePath(&user_data_dir) &&
+        file_util::PathExists(FilePath::FromWStringHack(user_data_dir))) {
+      arguments_w += std::wstring(L"--") + ASCIIToWide(switches::kUserDataDir) +
+                     L"=\"" + user_data_dir + L"\" ";
+    }
+  }
+
+#if defined (OS_CHROMEOS)
+  std::wstring profile = cmd.GetSwitchValue(switches::kProfile);
+  if (!profile.empty()) {
+    arguments_w += std::wstring(L"--") + ASCIIToWide(switches::kProfile) +
+                   L"=\"" + profile + L"\" ";
+  }
+#endif
+
+  // If |extension_app_id| is present, we use the kAppId switch rather than
+  // the kApp switch (the launch url will be read from the extension app
+  // during launch.
+  if (cmd.HasSwitch(switches::kEnableExtensionApps) &&
+      !extension_app_id.empty()) {
+    arguments_w += std::wstring(L"--") + ASCIIToWide(switches::kAppId) +
+        L"=\"" + ASCIIToWide(UTF16ToASCII(extension_app_id)) + L"\" --" +
+        ASCIIToWide(switches::kEnableExtensionApps);
+  } else {  
+    // Use '--app=url' instead of just 'url' to launch the browser with minimal
+    // chrome.
+    // Note: Do not change this flag!  Old Gears shortcuts will break if you do!
+    std::string url_string = url.spec();
+    ReplaceSubstringsAfterOffset(&url_string, 0, "\\", "%5C");
+    ReplaceSubstringsAfterOffset(&url_string, 0, "\"", "%22");
+    ReplaceSubstringsAfterOffset(&url_string, 0, ";",  "%3B");
+    ReplaceSubstringsAfterOffset(&url_string, 0, "$",  "%24");
+#if defined(OS_WIN)  // Windows shortcuts can't escape % so we use \x instead.
+    ReplaceSubstringsAfterOffset(&url_string, 0, "%",  "\\x");
+#endif
+    std::wstring url_w = UTF8ToWide(url_string);
+    arguments_w += std::wstring(L"--") + ASCIIToWide(switches::kApp) +
+        L"=\"" + url_w + L"\"";
+  }
+  return WideToUTF8(arguments_w);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // ShellIntegration::DefaultBrowserWorker
 //
diff --git a/chrome/browser/shell_integration.h b/chrome/browser/shell_integration.h
index 020e46df..5a24ea92 100644
--- a/chrome/browser/shell_integration.h
+++ b/chrome/browser/shell_integration.h
@@ -43,6 +43,10 @@
 
   struct ShortcutInfo {
     GURL url;
+    // If |extension_id| is non-empty, this is short cut is to an extension-app
+    // and the launch url will be detected at start-up. In this case, |url|
+    // is still used to generate the app id (windows app id, not chrome app id).
+    string16 extension_id;
     string16 title;
     string16 description;
     SkBitmap favicon;
@@ -57,6 +61,13 @@
     bool create_in_quick_launch_bar;
   };
 
+  // Re-implementation of chrome_plugin_utill::CPB_GetCommandLineArgumentsCommon
+  // which is deprecated. If |extension_app_id| is non-empty, an arguments
+  // string is created using the kAppId=<id> flag. Otherwise, the kApp=<url> is
+  // used.
+  static std::string GetCommandLineArgumentsCommon(const GURL& url,
+      const string16& extension_app_id);
+
 #if defined(OS_LINUX)
   // Returns filename for .desktop file based on |url|, sanitized for security.
   static FilePath GetDesktopShortcutFilename(const GURL& url);
@@ -66,7 +77,8 @@
   // used to launch Chrome.
   static std::string GetDesktopFileContents(
       const std::string& template_contents, const GURL& url,
-      const string16& title, const std::string& icon_name);
+      const string16& extension_id, const string16& title,
+      const std::string& icon_name);
 
   // Creates a desktop shortcut. It is not guaranteed to exist immediately after
   // returning from this function, because actual file operation is done on the
diff --git a/chrome/browser/shell_integration_linux.cc b/chrome/browser/shell_integration_linux.cc
index 37662499..78f765b 100644
--- a/chrome/browser/shell_integration_linux.cc
+++ b/chrome/browser/shell_integration_linux.cc
@@ -135,8 +135,8 @@
     std::string icon_name = CreateIcon(shortcut_filename);
 
     std::string contents = ShellIntegration::GetDesktopFileContents(
-        template_contents, shortcut_info_.url, shortcut_info_.title,
-        icon_name);
+        template_contents, shortcut_info_.url, shortcut_info_.extension_id,
+        shortcut_info_.title, icon_name);
 
     if (shortcut_info_.create_on_desktop)
       CreateOnDesktop(shortcut_filename, contents);
@@ -327,7 +327,8 @@
 
 std::string ShellIntegration::GetDesktopFileContents(
     const std::string& template_contents, const GURL& url,
-    const string16& title, const std::string& icon_name) {
+    const string16& extension_id, const string16& title,
+    const std::string& icon_name) {
   // See http://standards.freedesktop.org/desktop-entry-spec/latest/
   // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
   // launchers with an xdg-open shebang. Follow that convention.
@@ -342,8 +343,8 @@
         if (exec_tokenizer.token() != "%U")
           final_path += exec_tokenizer.token() + " ";
       }
-      std::string switches;
-      CPB_GetCommandLineArgumentsCommon(url.spec().c_str(), &switches);
+      std::string switches =
+          ShellIntegration::GetCommandLineArgumentsCommon(url, extension_id);
       output_buffer += std::string("Exec=") + final_path + switches + "\n";
     } else if (tokenizer.token().substr(0, 5) == "Name=") {
       std::string final_title = UTF16ToUTF8(title);
diff --git a/chrome/browser/shell_integration_unittest.cc b/chrome/browser/shell_integration_unittest.cc
index e4090136..cfbaed9 100644
--- a/chrome/browser/shell_integration_unittest.cc
+++ b/chrome/browser/shell_integration_unittest.cc
@@ -167,6 +167,7 @@
               ShellIntegration::GetDesktopFileContents(
                   test_cases[i].template_contents,
                   GURL(test_cases[i].url),
+                  EmptyString16(),
                   ASCIIToUTF16(test_cases[i].title),
                   test_cases[i].icon_name));
   }
diff --git a/chrome/browser/web_applications/web_app.cc b/chrome/browser/web_applications/web_app.cc
index 192c04d..b85035d 100644
--- a/chrome/browser/web_applications/web_app.cc
+++ b/chrome/browser/web_applications/web_app.cc
@@ -345,13 +345,9 @@
   // Working directory.
   std::wstring chrome_folder = file_util::GetDirectoryFromPath(chrome_exe);
 
-  // Gets the command line switches.
-  std::string switches;
-  if (CPB_GetCommandLineArgumentsCommon(shortcut_info_.url.spec().c_str(),
-      &switches) != CPERR_SUCCESS) {
-    NOTREACHED();
-    return false;
-  }
+  std::string switches =
+     ShellIntegration::GetCommandLineArgumentsCommon(shortcut_info_.url,
+     shortcut_info_.extension_id);
   std::wstring wide_switchs(UTF8ToWide(switches));
 
   // Generates app id from web app url and profile path.
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index cd00a4b4..2a879fd 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -27,6 +27,10 @@
 // Specifies that the associated value should be launched in "application" mode.
 const char kApp[]                           = "app";
 
+// Specifies that the extension-app with the specified id should be launched
+// according to its configuration.
+const char kAppId[]                         = "app-id";
+
 // Lacks meaning with out kApp. Causes the specified app to be launched in an
 // panel window.
 const char kAppLaunchAsPanel[]              = "app-launch-as-panel";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 07ba50b..e61a217 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -23,6 +23,7 @@
 extern const char kAllowSandboxDebugging[];
 extern const char kAlwaysEnableDevTools[];
 extern const char kApp[];
+extern const char kAppId[];
 extern const char kAppLaunchAsPanel[];
 extern const char kAutomationClientChannelID[];
 extern const char kBookmarkMenu[];
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
index 7bc46b6..8bd7b2f 100644
--- a/chrome/common/extensions/extension.cc
+++ b/chrome/common/extensions/extension.cc
@@ -537,6 +537,18 @@
     return false;
   }
 
+  // launch window type
+  app_launch_window_type_ = APP;
+  std::string window_type_string;
+  if (app->GetString(keys::kAppLaunchWindowType, &window_type_string)) {
+    if (window_type_string == std::string(values::kWindowTypePanel)) {
+      app_launch_window_type_ = PANEL;
+    } else if (window_type_string != std::string(values::kWindowTypeApp)) {
+      *error = errors::kInvalidAppLaunchWindowType;
+      return false;
+    }
+  }
+
   // The launch URL is automatically added to the extent.
   URLPattern pattern;
   pattern.set_scheme(app_launch_url_.scheme());
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index c0c5a53..c858534 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -62,6 +62,11 @@
     EXTENSION_ICON_BITTY = 16,
   };
 
+  enum AppLaunchWindowType {
+    APP,
+    PANEL
+  };
+
   // Icon sizes used by the extension system.
   static const int kIconSizes[];
 
@@ -283,6 +288,9 @@
   const URLPatternList& app_extent() const { return app_extent_; }
   const GURL& app_launch_url() const { return app_launch_url_; }
   bool IsApp() const { return !app_launch_url_.is_empty(); }
+  AppLaunchWindowType app_launch_window_type() {
+    return app_launch_window_type_;
+  }
 
  private:
   // Helper method that loads a UserScript object from a
@@ -415,8 +423,10 @@
 
   // The URL an app should launch to.
   GURL app_launch_url_;
-
-
+  
+  // The type of window to start when the application is launched.
+  AppLaunchWindowType app_launch_window_type_;
+  
   // Runtime data:
 
   // True if the background page is ready.
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index 1caa9e7..570e90d 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -10,6 +10,7 @@
 const wchar_t* kApp = L"app";
 const wchar_t* kAppExtent = L"extent";
 const wchar_t* kAppLaunchUrl = L"launch.url";
+const wchar_t* kAppLaunchWindowType = L"launch.window_type";
 const wchar_t* kBackground = L"background_page";
 const wchar_t* kBrowserAction = L"browser_action";
 const wchar_t* kChromeURLOverrides = L"chrome_url_overrides";
@@ -64,6 +65,8 @@
 const char* kRunAtDocumentIdle = "document_idle";
 const char* kPageActionTypeTab = "tab";
 const char* kPageActionTypePermanent = "permanent";
+const char* kWindowTypeApp = "app";
+const char* kWindowTypePanel = "panel";
 }  // namespace extension_manifest_values
 
 // Extension-related error messages. Some of these are simple patterns, where a
@@ -80,6 +83,8 @@
 const char* kInvalidAppExtentPattern = "Invalid value for app.extent[*].";
 const char* kInvalidAppLaunchUrl =
     "Required value 'app.launch.url' is missing or invalid.";
+const char* kInvalidAppLaunchWindowType =
+    "Invalid value for 'app.launch.window_type'.";
 const char* kInvalidBrowserAction =
     "Invalid value for 'browser_action'.";
 const char* kInvalidChromeURLOverrides =
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index ac37214..f71d1eb7 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -11,6 +11,7 @@
   extern const wchar_t* kApp;
   extern const wchar_t* kAppExtent;
   extern const wchar_t* kAppLaunchUrl;
+  extern const wchar_t* kAppLaunchWindowType;
   extern const wchar_t* kBackground;
   extern const wchar_t* kBrowserAction;
   extern const wchar_t* kMinimumChromeVersion;
@@ -66,6 +67,8 @@
   extern const char* kRunAtDocumentIdle;
   extern const char* kPageActionTypeTab;
   extern const char* kPageActionTypePermanent;
+  extern const char* kWindowTypeApp;
+  extern const char* kWindowTypePanel;
 }  // namespace extension_manifest_values
 
 // Error messages returned from Extension::InitFromValue().
@@ -76,6 +79,7 @@
   extern const char* kInvalidAppExtent;
   extern const char* kInvalidAppExtentPattern;
   extern const char* kInvalidAppLaunchUrl;
+  extern const char* kInvalidAppLaunchWindowType;
   extern const char* kInvalidBackground;
   extern const char* kInvalidBrowserAction;
   extern const char* kInvalidChromeURLOverrides;