diff options
author | viettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-18 22:09:40 +0000 |
---|---|---|
committer | viettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-18 22:09:40 +0000 |
commit | adf0225bfea22dd085a1e0816871af9e8f1fe372 (patch) | |
tree | 96fd5362f73388c6a72359db01b33dfbc6d15f81 | |
parent | 98e94a40052f163889a5d6026a24d750edf28818 (diff) | |
download | chromium_src-adf0225bfea22dd085a1e0816871af9e8f1fe372.zip chromium_src-adf0225bfea22dd085a1e0816871af9e8f1fe372.tar.gz chromium_src-adf0225bfea22dd085a1e0816871af9e8f1fe372.tar.bz2 |
Mac: app mode loader (shim).
Define an interface using which Chrome can be loaded from a minimal loader.
Produce a binary for this loader (which can be put into a bundle).
BUG=13148
TEST=not yet
Review URL: http://codereview.chromium.org/2066004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47576 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/app/app_mode-Info.plist | 36 | ||||
-rw-r--r-- | chrome/app/app_mode_loader_mac.mm | 145 | ||||
-rw-r--r-- | chrome/app/chrome_dll_app_mode_mac.mm | 56 | ||||
-rw-r--r-- | chrome/app/framework.order | 3 | ||||
-rw-r--r-- | chrome/chrome.gyp | 21 | ||||
-rw-r--r-- | chrome/chrome_dll.gypi | 1 | ||||
-rw-r--r-- | chrome/common/app_mode_common_mac.h | 41 | ||||
-rw-r--r-- | chrome/common/app_mode_common_mac.mm | 4 | ||||
-rw-r--r-- | chrome/common/chrome_paths_internal.h | 7 | ||||
-rw-r--r-- | chrome/common/chrome_paths_mac.mm | 14 |
10 files changed, 326 insertions, 2 deletions
diff --git a/chrome/app/app_mode-Info.plist b/chrome/app/app_mode-Info.plist new file mode 100644 index 0000000..611f7c5 --- /dev/null +++ b/chrome/app/app_mode-Info.plist @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleDisplayName</key> + <string>@APP_MODE_SHORTCUT_SHORT_NAME@</string> + <key>CFBundleExecutable</key> + <string>@APP_MODE_SHORTCUT_ID@</string> + <key>CFBundleIconFile</key> + <string>app.icns</string> + <key>CFBundleIdentifier</key> + <string>????</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>@APP_MODE_SHORTCUT_ID@</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CrAppModeShortcutID</key> + <string>@APP_MODE_SHORTCUT_ID@</string> + <key>CrAppModeShortcutShortName</key> + <string>@APP_MODE_SHORTCUT_SHORT_NAME@</string> + <key>CrAppModeShortcutName</key> + <string>@APP_MODE_SHORTCUT_NAME@</string> + <key>CrAppModeShortcutURL</key> + <string>@APP_MODE_SHORTCUT_URL@</string> + <key>LSFileQuarantineEnabled</key> + <true/> + <key>LSMinimumSystemVersion</key> + <string>10.5.0</string> + <key>NSAppleScriptEnabled</key> + <true/> +</dict> +</plist> diff --git a/chrome/app/app_mode_loader_mac.mm b/chrome/app/app_mode_loader_mac.mm new file mode 100644 index 0000000..9ffb697 --- /dev/null +++ b/chrome/app/app_mode_loader_mac.mm @@ -0,0 +1,145 @@ +// Copyright (c) 2010 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. + +// On Mac, shortcuts can't have command-line arguments. Instead, produce small +// app bundles which locate the Chromium framework and load it, passing the +// appropriate data. This is the code for such an app bundle. It should be kept +// minimal and do as little work as possible (with as much work done on +// framework side as possible). + +#include <dlfcn.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <CoreFoundation/CoreFoundation.h> +#import <Foundation/Foundation.h> + +#include "chrome/common/app_mode_common_mac.h" + +namespace { + +// Checks that condition |x| is true. If not, outputs |msg| (together with +// source filename and line number) and exits. +#define CHECK_MSG(x, msg) if (!(x)) check_msg_helper(__FILE__, __LINE__, msg); +void check_msg_helper(const char* file, int line, const char* msg) { + fprintf(stderr, "%s (%d): %s\n", file, line, msg); + exit(1); +} + +// Converts an NSString to a UTF8 C string (which is allocated, and may be freed +// using |free()|). If |s| is nil or can't produce such a string, this returns +// |NULL|. +char* NSStringToUTF8CString(NSString* s) { + CHECK_MSG([s isKindOfClass:[NSString class]], "expected an NSString"); + const char* cstring = [s UTF8String]; + return cstring ? strdup(cstring) : NULL; +} + +// Converts an NSString to a file-system representation C string (which is +// allocated, and may be freed using |free()|). If |s| is nil or can't produce +// such a string, this returns |NULL|. +char* NSStringToFSCString(NSString* s) { + CHECK_MSG([s isKindOfClass:[NSString class]], "expected an NSString"); + const char* cstring = [s fileSystemRepresentation]; + return cstring ? strdup(cstring) : NULL; +} + +} // namespace + +__attribute__((visibility("default"))) +int main(int argc, char** argv) { + app_mode::ChromeAppModeInfo info; + info.major_version = 0; // v0.1 + info.minor_version = 1; + info.argc = argc; + info.argv = argv; + + // The Cocoa APIs are a bit more convenient; for this an autorelease pool is + // needed. + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + // Get the browser bundle path. + // TODO(viettrungluu): more fun + NSString* cr_bundle_path = + [(NSString*)CFPreferencesCopyAppValue( + app_mode::kLastRunAppBundlePathPrefsKey, + app_mode::kAppPrefsID) autorelease]; + CHECK_MSG(cr_bundle_path, "couldn't get browser bundle path"); + + // Get the browser bundle. + NSBundle* cr_bundle = [NSBundle bundleWithPath:cr_bundle_path]; + CHECK_MSG(cr_bundle, "couldn't get browser bundle"); + + // Get the current browser version. + NSString* cr_version = + [cr_bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + CHECK_MSG(cr_version, "couldn't get browser version"); + + // Get the current browser versioned directory. + NSArray* cr_versioned_path_components = + [NSArray arrayWithObjects:cr_bundle_path, + @"Contents", + @"Versions", + cr_version, + nil]; + NSString* cr_versioned_path = + [[NSString pathWithComponents:cr_versioned_path_components] + stringByStandardizingPath]; + CHECK_MSG(cr_versioned_path, "couldn't get browser versioned path"); + // And copy it, since |cr_versioned_path| will go away with the pool. + info.chrome_versioned_path = NSStringToFSCString(cr_versioned_path); + + // Get the current main bundle, i.e., that of the app loader that's running. + NSBundle* app_bundle = [NSBundle mainBundle]; + CHECK_MSG(app_bundle, "couldn't get loader bundle"); + // Optional, so okay if it's NULL. + info.app_mode_bundle_path = NSStringToFSCString([app_bundle bundlePath]); + + // Read information about the this app shortcut from the Info.plist. + // Don't check for null-ness on optional items. + NSDictionary* info_plist = [app_bundle infoDictionary]; + CHECK_MSG(info_plist, "couldn't get loader Info.plist"); + + info.app_mode_id = NSStringToUTF8CString( + [info_plist objectForKey:@"CrAppModeShortcutID"]); + CHECK_MSG(info.app_mode_id, "couldn't get app shortcut ID"); + + info.app_mode_short_name = NSStringToUTF8CString( + [info_plist objectForKey:@"CrAppModeShortcutShortName"]); + + info.app_mode_name = NSStringToUTF8CString( + [info_plist objectForKey:@"CrAppModeShortcutName"]); + + info.app_mode_url = NSStringToUTF8CString( + [info_plist objectForKey:@"CrAppModeShortcutURL"]); + CHECK_MSG(info.app_mode_url, "couldn't get app shortcut URL"); + + // Get the framework path. + NSString* cr_bundle_exe = + [cr_bundle objectForInfoDictionaryKey:@"CFBundleExecutable"]; + NSString* cr_framework_path = + [cr_versioned_path stringByAppendingPathComponent: + [cr_bundle_exe stringByAppendingString:@" Framework.framework"]]; + cr_framework_path = + [cr_framework_path stringByAppendingPathComponent: + [cr_bundle_exe stringByAppendingString:@" Framework"]]; + + // Open the framework. + void* cr_dylib = dlopen([cr_framework_path fileSystemRepresentation], + RTLD_LAZY); + CHECK_MSG(cr_dylib, "couldn't load framework"); + + // Drain the pool as late as possible. + [pool drain]; + + typedef int (*StartFun)(const app_mode::ChromeAppModeInfo*); + StartFun ChromeAppModeStart = (StartFun)dlsym(cr_dylib, "ChromeAppModeStart"); + CHECK_MSG(ChromeAppModeStart, "couldn't get entry point"); + + // Exit instead of returning to avoid the the removal of |main()| from stack + // backtraces under tail call optimization. + int rv = ChromeAppModeStart(&info); + exit(rv); +} diff --git a/chrome/app/chrome_dll_app_mode_mac.mm b/chrome/app/chrome_dll_app_mode_mac.mm new file mode 100644 index 0000000..678547d --- /dev/null +++ b/chrome/app/chrome_dll_app_mode_mac.mm @@ -0,0 +1,56 @@ +// Copyright (c) 2010 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. + +// On Mac, one can't make shortcuts with command-line arguments. Instead, we +// produce small app bundles which locate the Chromium framework and load it, +// passing the appropriate data. This is the entry point into the framework for +// those app bundles. + +#include <string> // TODO(viettrungluu): only needed for temporary hack + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "chrome/common/app_mode_common_mac.h" +#include "chrome/common/chrome_paths_internal.h" + +extern "C" { + +// |ChromeAppModeStart()| is the point of entry into the framework from the app +// mode loader. +__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) { + if (info->major_version < app_mode::kCurrentChromeAppModeInfoMajorVersion) { + RAW_LOG(ERROR, "App Mode Loader too old."); + return 1; + } + if (info->major_version > app_mode::kCurrentChromeAppModeInfoMajorVersion) { + RAW_LOG(ERROR, "Browser Framework too old to load App Shortcut."); + return 1; + } + + RAW_CHECK(info->chrome_versioned_path); + FilePath* chrome_versioned_path = new FilePath(info->chrome_versioned_path); + RAW_CHECK(!chrome_versioned_path->empty()); + chrome::SetOverrideVersionedDirectory(chrome_versioned_path); + + // TODO(viettrungluu): do something intelligent with data +// return ChromeMain(info->argc, info->argv); + // For now, a cheesy hack instead. + RAW_CHECK(info->app_mode_url); + std::string argv1(std::string("--app=") + info->app_mode_url); + RAW_CHECK(info->app_mode_id); + std::string argv2(std::string("--user-data-dir=/tmp/") + info->app_mode_id); + char* argv[] = { info->argv[0], + const_cast<char*>(argv1.c_str()), + const_cast<char*>(argv2.c_str()) }; + return ChromeMain(static_cast<int>(arraysize(argv)), argv); +} diff --git a/chrome/app/framework.order b/chrome/app/framework.order index 3ff59b0..c527f6e 100644 --- a/chrome/app/framework.order +++ b/chrome/app/framework.order @@ -63,5 +63,8 @@ ___gcov_fork _NaClSwitch _NaClSyscallSeg +# Entry point from the app mode loader. +_ChromeAppModeStart + # _ChromeMain must be listed last. That's the whole point of this file. _ChromeMain diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 5472fe5..4a46af3 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1147,6 +1147,27 @@ ], }, # target helper_app { + # This produces the app mode loader, but not as a bundle. Chromium + # itself is responsible for producing bundles. + 'target_name': 'app_mode_app', + 'type': 'executable', + 'product_name': '<(mac_product_name) App Mode Loader', + 'sources': [ + 'app/app_mode_loader_mac.mm', + 'common/app_mode_common_mac.h', + 'common/app_mode_common_mac.mm', + ], + 'include_dirs': [ + '..', + ], + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/CoreFoundation.framework', + '$(SDKROOT)/System/Library/Frameworks/Foundation.framework', + ], + }, + }, # target app_mode_app + { # Convenience target to build a disk image. 'target_name': 'build_app_dmg', # Don't place this in the 'all' list; most won't want it. diff --git a/chrome/chrome_dll.gypi b/chrome/chrome_dll.gypi index a23125a..3884aa0 100644 --- a/chrome/chrome_dll.gypi +++ b/chrome/chrome_dll.gypi @@ -170,6 +170,7 @@ 'ORDER_FILE': 'app/framework.order', }, 'sources': [ + 'app/chrome_dll_app_mode_mac.mm', 'app/chrome_dll_main.cc', 'app/chrome_dll_resource.h', ], diff --git a/chrome/common/app_mode_common_mac.h b/chrome/common/app_mode_common_mac.h index 0929be5..48d72fe 100644 --- a/chrome/common/app_mode_common_mac.h +++ b/chrome/common/app_mode_common_mac.h @@ -20,6 +20,47 @@ extern const CFStringRef kAppPrefsID; // bundle; this key is recorded under the ID given by |kAppPrefsID|. extern const CFStringRef kLastRunAppBundlePathPrefsKey; +// Current major/minor version numbers of |ChromeAppModeInfo| (defined below). +const unsigned kCurrentChromeAppModeInfoMajorVersion = 0; +const unsigned kCurrentChromeAppModeInfoMinorVersion = 1; + +// The structure used to pass information from the app mode loader to the +// (browser) framework. This is versioned using major and minor version numbers, +// written below as v<major>.<minor>. Version-number checking is done by the +// framework, and the framework must accept all structures with the same major +// version number. It may refuse to load if the major version of the structure +// is different from the one it accepts. +struct ChromeAppModeInfo { + // Major and minor version number of this structure. + unsigned major_version; // Required: all versions + unsigned minor_version; // Required: all versions + + // Original |argc| and |argv|. + int argc; // Required: v0.1 + char** argv; // Required: v0.1 + + // Versioned path to the browser which is being loaded. + char* chrome_versioned_path; // Required: v0.1 + + // Information about the App Mode shortcut: + + // Path to the App Mode Loader application bundle originally run. + char* app_mode_bundle_path; // Optional: v0.1 + + // Short ID string, preferably derived from |app_mode_short_name|. Should be + // safe for the file system. + char* app_mode_id; // Required: v0.1 + + // Short (e.g., one-word) UTF8-encoded name for the shortcut. + char* app_mode_short_name; // Optional: v0.1 + + // Unrestricted (e.g., several-word) UTF8-encoded name for the shortcut. + char* app_mode_name; // Optional: v0.1 + + // URL for the shortcut. Must be a valid URL. + char* app_mode_url; // Required: v0.1 +}; + } // namespace app_mode #endif // CHROME_COMMON_APP_MODE_COMMON_MAC_H_ diff --git a/chrome/common/app_mode_common_mac.mm b/chrome/common/app_mode_common_mac.mm index aa35af7..3705c34 100644 --- a/chrome/common/app_mode_common_mac.mm +++ b/chrome/common/app_mode_common_mac.mm @@ -7,9 +7,9 @@ namespace app_mode { #if defined(GOOGLE_CHROME_BUILD) -extern const CFStringRef kAppPrefsID = CFSTR("com.google.Chrome"); +const CFStringRef kAppPrefsID = CFSTR("com.google.Chrome"); #else -extern const CFStringRef kAppPrefsID = CFSTR("org.chromium.Chromium"); +const CFStringRef kAppPrefsID = CFSTR("org.chromium.Chromium"); #endif const CFStringRef kLastRunAppBundlePathPrefsKey = CFSTR("LastRunAppBundlePath"); diff --git a/chrome/common/chrome_paths_internal.h b/chrome/common/chrome_paths_internal.h index 5254f51..7d731e1 100644 --- a/chrome/common/chrome_paths_internal.h +++ b/chrome/common/chrome_paths_internal.h @@ -40,6 +40,13 @@ bool GetUserDesktop(FilePath* result); // in the .app at Contents/Versions/w.x.y.z. FilePath GetVersionedDirectory(); +// This overrides the directory returned by |GetVersionedDirectory()|, to be +// used when |GetVersionedDirectory()| can't automatically determine the proper +// location. This is the case when the browser didn't load itself but by, e.g., +// the app mode loader. This should be called before |ChromeMain()|. This takes +// ownership of the object |path| and the caller must not delete it. +void SetOverrideVersionedDirectory(const FilePath* path); + // Most of the application is further contained within the framework. The // framework bundle is located within the versioned directory at a specific // path. The only components in the versioned directory not included in the diff --git a/chrome/common/chrome_paths_mac.mm b/chrome/common/chrome_paths_mac.mm index 21868cd..c7db483 100644 --- a/chrome/common/chrome_paths_mac.mm +++ b/chrome/common/chrome_paths_mac.mm @@ -12,6 +12,10 @@ #include "base/path_service.h" #include "chrome/common/chrome_constants.h" +namespace { +const FilePath* g_override_versioned_directory = NULL; +} // namespace + namespace chrome { bool GetDefaultUserDataDirectory(FilePath* result) { @@ -53,6 +57,9 @@ bool GetUserDesktop(FilePath* result) { } FilePath GetVersionedDirectory() { + if (g_override_versioned_directory) + return *g_override_versioned_directory; + // Start out with the path to the running executable. FilePath path; PathService::Get(base::FILE_EXE, &path); @@ -75,6 +82,13 @@ FilePath GetVersionedDirectory() { return path; } +void SetOverrideVersionedDirectory(const FilePath* path) { + if (path != g_override_versioned_directory) { + delete g_override_versioned_directory; + g_override_versioned_directory = path; + } +} + FilePath GetFrameworkBundlePath() { // It's tempting to use +[NSBundle bundleWithIdentifier:], but it's really // slow (about 30ms on 10.5 and 10.6), despite Apple's documentation stating |