summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorjeremya@chromium.org <jeremya@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-15 06:24:32 +0000
committerjeremya@chromium.org <jeremya@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-15 06:24:32 +0000
commitccfab591456e8c231a784823edcd560267bcb382 (patch)
tree7f083d4ba68a22f7b946d918c3cd2a97c4c5846e /chrome
parent289f05cc53e22a3073ffe1abc14d50f5bc258014 (diff)
downloadchromium_src-ccfab591456e8c231a784823edcd560267bcb382.zip
chromium_src-ccfab591456e8c231a784823edcd560267bcb382.tar.gz
chromium_src-ccfab591456e8c231a784823edcd560267bcb382.tar.bz2
Rearchitecting the 'app mode' code on mac for v2 apps.
A lot of the old code assumed an outdated model for v2 packaged apps on mac; specifically that they used to run in their own Chrome process with their own user data directory. This is no longer the case, and as such it's not possible to have apps running in their own process, because they need access to profile information from Chrome. This patch changes the code so that the 'app mode' .app bundles function more like shortcuts to open the app in an existing Chrome browser process (launching Chrome if it's not already running). This allows the app to be launched via Spotlight and the /Applications folder, and the user can e.g. drag the app onto the dock like a regular Mac application. There is as yet no way to trigger the shortcut creation code. BUG=168080 Review URL: https://chromiumcodereview.appspot.com/11737014 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@176803 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/app/app_mode_loader_mac.mm4
-rw-r--r--chrome/app/chrome_main_app_mode_mac.mm81
-rw-r--r--chrome/browser/app_controller_mac.mm45
-rw-r--r--chrome/browser/extensions/app_shortcut_manager.cc36
-rw-r--r--chrome/browser/extensions/app_shortcut_manager.h7
-rw-r--r--chrome/browser/web_applications/web_app_mac.mm6
-rw-r--r--chrome/browser/web_applications/web_app_mac_unittest.mm39
-rw-r--r--chrome/common/mac/app_mode_common.h13
-rw-r--r--chrome/common/mac/app_mode_common.mm2
9 files changed, 168 insertions, 65 deletions
diff --git a/chrome/app/app_mode_loader_mac.mm b/chrome/app/app_mode_loader_mac.mm
index e8242f0..208d3cf 100644
--- a/chrome/app/app_mode_loader_mac.mm
+++ b/chrome/app/app_mode_loader_mac.mm
@@ -95,8 +95,8 @@ void LoadFramework(void** cr_dylib, app_mode::ChromeAppModeInfo* info) {
info->user_data_dir = base::mac::NSStringToFilePath(
[info_plist objectForKey:app_mode::kCrAppModeUserDataDirKey]);
- info->extension_path = base::mac::NSStringToFilePath(
- [info_plist objectForKey:app_mode::kCrAppModeExtensionPathKey]);
+ info->profile_dir = base::mac::NSStringToFilePath(
+ [info_plist objectForKey:app_mode::kCrAppModeProfileDirKey]);
// Open the framework.
*cr_dylib = dlopen(framework_shlib_path.value().c_str(), RTLD_LAZY);
diff --git a/chrome/app/chrome_main_app_mode_mac.mm b/chrome/app/chrome_main_app_mode_mac.mm
index d78a0a5..99d0dcc 100644
--- a/chrome/app/chrome_main_app_mode_mac.mm
+++ b/chrome/app/chrome_main_app_mode_mac.mm
@@ -8,10 +8,14 @@
// those app bundles.
#include "base/basictypes.h"
-#include "base/command_line.h"
#include "base/file_path.h"
#include "base/logging.h"
#include "base/mac/bundle_locations.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/mac_logging.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/sys_string_conversions.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths_internal.h"
@@ -25,12 +29,11 @@ extern "C" {
__attribute__((visibility("default")))
int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info);
-// TODO(viettrungluu): put this in a header file somewhere.
-int ChromeMain(int argc, char** argv);
-
} // extern "C"
int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info) {
+ base::mac::ScopedNSAutoreleasePool scoped_pool;
+
if (info->major_version < app_mode::kCurrentChromeAppModeInfoMajorVersion) {
RAW_LOG(ERROR, "App Mode Loader too old.");
return 1;
@@ -40,33 +43,47 @@ int ChromeAppModeStart(const app_mode::ChromeAppModeInfo* info) {
return 1;
}
- RAW_CHECK(!info->chrome_versioned_path.empty());
- FilePath* chrome_versioned_path = new FilePath(info->chrome_versioned_path);
- RAW_CHECK(!chrome_versioned_path->empty());
- chrome::SetOverrideVersionedDirectory(chrome_versioned_path);
- base::mac::SetOverrideOuterBundlePath(info->chrome_outer_bundle_path);
- base::mac::SetOverrideFrameworkBundlePath(
- chrome_versioned_path->Append(chrome::kFrameworkName));
-
- // This struct is used to communicate information to the Chrome code, prefer
- // this to modifying the command line below.
- struct ShellIntegration::AppModeInfo app_mode_info;
- ShellIntegration::SetAppModeInfo(&app_mode_info);
-
- CommandLine command_line(CommandLine::NO_PROGRAM);
- command_line.AppendSwitch(info->argv[0]);
-
- RAW_CHECK(info->app_mode_id.size());
- command_line.AppendSwitchASCII(switches::kAppId, info->app_mode_id);
- command_line.AppendSwitchPath(switches::kUserDataDir, info->user_data_dir);
- // TODO(sail): Use a different flag that doesn't imply Location::LOAD for the
- // extension.
- command_line.AppendSwitchPath(switches::kLoadExtension, info->extension_path);
-
- int argc = command_line.argv().size();
- char* argv[argc];
- for (int i = 0; i < argc; ++i)
- argv[i] = const_cast<char*>(command_line.argv()[i].c_str());
+ FSRef app_fsref;
+ if (!base::mac::FSRefFromPath(info->chrome_outer_bundle_path.value(),
+ &app_fsref)) {
+ PLOG(ERROR) << "base::mac::FSRefFromPath failed for "
+ << info->chrome_outer_bundle_path.value();
+ return 1;
+ }
+ std::string silent = std::string("--") + switches::kSilentLaunch;
+ CFArrayRef launch_args =
+ base::mac::NSToCFCast(@[base::SysUTF8ToNSString(silent)]);
- return ChromeMain(argc, argv);
+ LSApplicationParameters ls_parameters = {
+ 0, // version
+ kLSLaunchDefaults,
+ &app_fsref,
+ NULL, // asyncLaunchRefCon
+ NULL, // environment
+ launch_args,
+ NULL // initialEvent
+ };
+ NSAppleEventDescriptor* initial_event =
+ [NSAppleEventDescriptor
+ appleEventWithEventClass:app_mode::kAEChromeAppClass
+ eventID:app_mode::kAEChromeAppLaunch
+ targetDescriptor:nil
+ returnID:kAutoGenerateReturnID
+ transactionID:kAnyTransactionID];
+ NSAppleEventDescriptor* appid_descriptor = [NSAppleEventDescriptor
+ descriptorWithString:base::SysUTF8ToNSString(info->app_mode_id)];
+ [initial_event setParamDescriptor:appid_descriptor
+ forKeyword:keyDirectObject];
+ NSAppleEventDescriptor* profile_dir_descriptor = [NSAppleEventDescriptor
+ descriptorWithString:base::SysUTF8ToNSString(info->profile_dir.value())];
+ [initial_event setParamDescriptor:profile_dir_descriptor
+ forKeyword:app_mode::kAEProfileDirKey];
+ ls_parameters.initialEvent = const_cast<AEDesc*>([initial_event aeDesc]);
+ // Send the Apple Event using launch services, launching Chrome if necessary.
+ OSStatus status = LSOpenApplication(&ls_parameters, NULL);
+ if (status != noErr) {
+ OSSTATUS_LOG(ERROR, status) << "LSOpenApplication";
+ return 1;
+ }
+ return 0;
}
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 9031dc0..7c357fc 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -22,6 +22,8 @@
#include "chrome/browser/command_updater.h"
#include "chrome/browser/download/download_service.h"
#include "chrome/browser/download/download_service_factory.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/prefs/pref_service.h"
@@ -55,12 +57,14 @@
#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
#import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h"
#include "chrome/browser/ui/cocoa/task_manager_mac.h"
+#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/browser/ui/startup/startup_browser_creator.h"
#include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_paths_internal.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/cloud_print/cloud_print_class_mac.h"
+#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/mac/app_mode_common.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/service_messages.h"
@@ -177,6 +181,8 @@ void RecordLastRunAppBundlePath() {
- (void)getUrl:(NSAppleEventDescriptor*)event
withReply:(NSAppleEventDescriptor*)reply;
- (void)submitCloudPrintJob:(NSAppleEventDescriptor*)event;
+- (void)launchPlatformApp:(NSAppleEventDescriptor*)event
+ withReply:(NSAppleEventDescriptor*)reply;
- (void)windowLayeringDidChange:(NSNotification*)inNotification;
- (void)windowChangedToProfile:(Profile*)profile;
- (void)checkForAnyKeyWindows;
@@ -208,6 +214,11 @@ void RecordLastRunAppBundlePath() {
forEventClass:'WWW!' // A particularly ancient AppleEvent that dates
andEventID:'OURL']; // back to the Spyglass days.
+ [em setEventHandler:self
+ andSelector:@selector(launchPlatformApp:withReply:)
+ forEventClass:app_mode::kAEChromeAppClass
+ andEventID:app_mode::kAEChromeAppLaunch];
+
// Register for various window layering changes. We use these to update
// various UI elements (command-key equivalents, etc) when the frontmost
// window changes.
@@ -1098,6 +1109,40 @@ void RecordLastRunAppBundlePath() {
[self openUrls:gurlVector];
}
+- (void)launchPlatformApp:(NSAppleEventDescriptor*)event
+ withReply:(NSAppleEventDescriptor*)reply {
+ NSString* appId =
+ [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
+ NSString* profileDir =
+ [[event paramDescriptorForKeyword:app_mode::kAEProfileDirKey]
+ stringValue];
+
+ ProfileManager* profileManager = g_browser_process->profile_manager();
+ FilePath path = FilePath(base::SysNSStringToUTF8(profileDir));
+ path = profileManager->user_data_dir().Append(path);
+ Profile* profile = profileManager->GetProfile(path);
+ if (!profile) {
+ LOG(ERROR) << "Unable to locate a suitable profile for profile directory '"
+ << profileDir << "' while trying to load app with id '"
+ << appId << "'.";
+ return;
+ }
+ ExtensionServiceInterface* extensionService =
+ extensions::ExtensionSystem::Get(profile)->extension_service();
+ const extensions::Extension* extension =
+ extensionService->GetExtensionById(
+ base::SysNSStringToUTF8(appId), false);
+ if (!extension) {
+ LOG(ERROR) << "Shortcut attempted to launch nonexistent app with id '"
+ << base::SysNSStringToUTF8(appId) << "'.";
+ return;
+ }
+ application_launch::LaunchParams params(profile, extension,
+ extension_misc::LAUNCH_NONE,
+ NEW_WINDOW);
+ application_launch::OpenApplication(params);
+}
+
// Apple Event handler that receives print event from service
// process, gets the required data and launches Print dialog.
- (void)submitCloudPrintJob:(NSAppleEventDescriptor*)event {
diff --git a/chrome/browser/extensions/app_shortcut_manager.cc b/chrome/browser/extensions/app_shortcut_manager.cc
index 5c90531..0eef11e 100644
--- a/chrome/browser/extensions/app_shortcut_manager.cc
+++ b/chrome/browser/extensions/app_shortcut_manager.cc
@@ -60,24 +60,6 @@ AppShortcutManager::AppShortcutManager(Profile* profile)
AppShortcutManager::~AppShortcutManager() {}
-void AppShortcutManager::OnImageLoaded(const gfx::Image& image) {
- // If the image failed to load (e.g. if the resource being loaded was empty)
- // use the standard application icon.
- if (image.IsEmpty()) {
- gfx::Image default_icon =
- ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON);
- int size = kDesiredSizes[arraysize(kDesiredSizes) - 1];
- SkBitmap bmp = skia::ImageOperations::Resize(
- *default_icon.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST,
- size, size);
- shortcut_info_.favicon = gfx::Image(bmp);
- } else {
- shortcut_info_.favicon = image;
- }
-
- web_app::UpdateAllShortcuts(shortcut_info_);
-}
-
void AppShortcutManager::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
@@ -175,6 +157,24 @@ void AppShortcutManager::UpdateApplicationShortcuts(
weak_factory_.GetWeakPtr()));
}
+void AppShortcutManager::OnImageLoaded(const gfx::Image& image) {
+ // If the image failed to load (e.g. if the resource being loaded was empty)
+ // use the standard application icon.
+ if (image.IsEmpty()) {
+ gfx::Image default_icon =
+ ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON);
+ int size = kDesiredSizes[arraysize(kDesiredSizes) - 1];
+ SkBitmap bmp = skia::ImageOperations::Resize(
+ *default_icon.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST,
+ size, size);
+ shortcut_info_.favicon = gfx::Image(bmp);
+ } else {
+ shortcut_info_.favicon = image;
+ }
+
+ web_app::UpdateAllShortcuts(shortcut_info_);
+}
+
void AppShortcutManager::DeleteApplicationShortcuts(
const Extension* extension) {
ShellIntegration::ShortcutInfo delete_info =
diff --git a/chrome/browser/extensions/app_shortcut_manager.h b/chrome/browser/extensions/app_shortcut_manager.h
index cc41519..c855683 100644
--- a/chrome/browser/extensions/app_shortcut_manager.h
+++ b/chrome/browser/extensions/app_shortcut_manager.h
@@ -28,9 +28,14 @@ class AppShortcutManager : public content::NotificationObserver {
const content::NotificationDetails& details) OVERRIDE;
private:
- void OnImageLoaded(const gfx::Image& image);
void UpdateApplicationShortcuts(const Extension* extension);
+ // Implement ImageLoadingTracker::Observer. |tracker_| is used to
+ // load the application's icon, which is done when we start creating an
+ // application's shortcuts. This method receives the icon, and completes
+ // the process of installing the shortcuts.
+ void OnImageLoaded(const gfx::Image& image);
+
#if defined(OS_WIN)
void OnAppHostInstallationComplete(scoped_refptr<Extension> extension,
bool app_host_install_success);
diff --git a/chrome/browser/web_applications/web_app_mac.mm b/chrome/browser/web_applications/web_app_mac.mm
index cdd342c..be02bc5 100644
--- a/chrome/browser/web_applications/web_app_mac.mm
+++ b/chrome/browser/web_applications/web_app_mac.mm
@@ -123,6 +123,8 @@ bool WebAppShortcutCreator::CreateShortcut() {
return false;
}
+ dst_path = dst_path.Append(app_file_name);
+ base::mac::RemoveQuarantineAttribute(dst_path);
RevealGeneratedBundleInFinder(dst_path);
return true;
@@ -187,8 +189,8 @@ bool WebAppShortcutCreator::UpdatePlist(const FilePath& app_path) const {
forKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)];
[plist setObject:base::mac::FilePathToNSString(user_data_dir_)
forKey:app_mode::kCrAppModeUserDataDirKey];
- [plist setObject:base::mac::FilePathToNSString(info_.extension_path)
- forKey:app_mode::kCrAppModeExtensionPathKey];
+ [plist setObject:base::mac::FilePathToNSString(info_.profile_path.BaseName())
+ forKey:app_mode::kCrAppModeProfileDirKey];
return [plist writeToFile:plist_path atomically:YES];
}
diff --git a/chrome/browser/web_applications/web_app_mac_unittest.mm b/chrome/browser/web_applications/web_app_mac_unittest.mm
index 6a7efd6..575eafc 100644
--- a/chrome/browser/web_applications/web_app_mac_unittest.mm
+++ b/chrome/browser/web_applications/web_app_mac_unittest.mm
@@ -6,6 +6,9 @@
#import <Cocoa/Cocoa.h>
+#include <sys/xattr.h>
+#include <errno.h>
+
#include "base/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/mac/foundation_util.h"
@@ -45,6 +48,7 @@ ShellIntegration::ShortcutInfo GetShortcutInfo() {
info.extension_path = FilePath("/fake/extension/path");
info.title = ASCIIToUTF16("Shortcut Title");
info.url = GURL("http://example.com/");
+ info.profile_path = FilePath("Default");
return info;
}
@@ -52,19 +56,18 @@ ShellIntegration::ShortcutInfo GetShortcutInfo() {
namespace web_app {
-// This test is disabled for the following reasons:
-// * The plist still isn't filled in correctly.
-// * WebAppShortcutCreator::CreateShortcut() opens a Finder window which it
-// shouldn't be doing when run from a unit test.
TEST(WebAppShortcutCreatorTest, CreateShortcut) {
base::ScopedTempDir scoped_temp_dir;
EXPECT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
- FilePath dst_path = scoped_temp_dir.path().Append("a.app");
ShellIntegration::ShortcutInfo info = GetShortcutInfo();
+
+ FilePath dst_folder = scoped_temp_dir.path();
+ FilePath dst_path = dst_folder.Append(UTF16ToUTF8(info.title) + ".app");
+
NiceMock<WebAppShortcutCreatorMock> shortcut_creator(info);
EXPECT_CALL(shortcut_creator, GetDestinationPath(_))
- .WillRepeatedly(Return(dst_path));
+ .WillRepeatedly(Return(dst_folder));
EXPECT_CALL(shortcut_creator, RevealGeneratedBundleInFinder(dst_path));
EXPECT_TRUE(shortcut_creator.CreateShortcut());
@@ -91,6 +94,30 @@ TEST(WebAppShortcutCreatorTest, CreateShortcut) {
}
}
+TEST(WebAppShortcutCreatorTest, RunShortcut) {
+ base::ScopedTempDir scoped_temp_dir;
+ EXPECT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
+
+ ShellIntegration::ShortcutInfo info = GetShortcutInfo();
+
+ FilePath dst_folder = scoped_temp_dir.path();
+ dst_folder = FilePath("/Applications");
+ FilePath dst_path = dst_folder.Append(UTF16ToUTF8(info.title) + ".app");
+
+ NiceMock<WebAppShortcutCreatorMock> shortcut_creator(info);
+ EXPECT_CALL(shortcut_creator, GetDestinationPath(_))
+ .WillRepeatedly(Return(dst_folder));
+ EXPECT_CALL(shortcut_creator, RevealGeneratedBundleInFinder(dst_path));
+
+ EXPECT_TRUE(shortcut_creator.CreateShortcut());
+ EXPECT_TRUE(file_util::PathExists(dst_path));
+
+ ssize_t status = getxattr(
+ dst_path.value().c_str(), "com.apple.quarantine", NULL, 0, 0, 0);
+ EXPECT_EQ(-1, status);
+ EXPECT_EQ(ENOATTR, errno);
+}
+
TEST(WebAppShortcutCreatorTest, CreateFailure) {
NiceMock<WebAppShortcutCreatorMock> shortcut_creator(GetShortcutInfo());
EXPECT_CALL(shortcut_creator, GetDestinationPath(_))
diff --git a/chrome/common/mac/app_mode_common.h b/chrome/common/mac/app_mode_common.h
index 265e047..4b604ff 100644
--- a/chrome/common/mac/app_mode_common.h
+++ b/chrome/common/mac/app_mode_common.h
@@ -15,6 +15,13 @@
namespace app_mode {
+// Keys for a custom 'launch platform app' Apple event. When Chrome receives
+// this event, it should launch the app specified by the direct key in the
+// event, in the profile specified by the 'pdir' key.
+const AEEventClass kAEChromeAppClass = 'cApp';
+const AEEventID kAEChromeAppLaunch = 'lnch';
+const AEKeyword kAEProfileDirKey = 'pdir';
+
// The key under which the browser's bundle ID will be stored in the
// app mode launcher bundle's Info.plist.
extern NSString* const kBrowserBundleIDKey;
@@ -32,7 +39,7 @@ extern NSString* const kCrAppModeShortcutURLKey;
extern NSString* const kCrAppModeUserDataDirKey;
// Key for the app's extension path.
-extern NSString* const kCrAppModeExtensionPathKey;
+extern NSString* const kCrAppModeProfileDirKey;
// When the Chrome browser is run, it stores its location in the defaults
// system using this key.
@@ -92,8 +99,8 @@ struct ChromeAppModeInfo {
// Path to the app's user data directory.
FilePath user_data_dir;
- // Path to the app's extension.
- FilePath extension_path;
+ // Directory of the profile associated with the app.
+ FilePath profile_dir;
};
} // namespace app_mode
diff --git a/chrome/common/mac/app_mode_common.mm b/chrome/common/mac/app_mode_common.mm
index c6d5573..3efcccb 100644
--- a/chrome/common/mac/app_mode_common.mm
+++ b/chrome/common/mac/app_mode_common.mm
@@ -11,7 +11,7 @@ NSString* const kCrAppModeShortcutIDKey = @"CrAppModeShortcutID";
NSString* const kCrAppModeShortcutNameKey = @"CrAppModeShortcutName";
NSString* const kCrAppModeShortcutURLKey = @"CrAppModeShortcutURL";
NSString* const kCrAppModeUserDataDirKey = @"CrAppModeUserDataDir";
-NSString* const kCrAppModeExtensionPathKey = @"CrAppModeExtensionPath";
+NSString* const kCrAppModeProfileDirKey = @"CrAppModeProfileDir";
NSString* const kLastRunAppBundlePathPrefsKey = @"LastRunAppBundlePath";