summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authormark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-02 21:26:58 +0000
committermark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-02 21:26:58 +0000
commit0a357e63d5bc32ef031cc7cf9b741db9b023958b (patch)
tree438ee2c1328d6c5264887a349be9f1171cfaa105 /chrome
parentdba272a4d92316f0ef35065aa8a1eb94fa950430 (diff)
downloadchromium_src-0a357e63d5bc32ef031cc7cf9b741db9b023958b.zip
chromium_src-0a357e63d5bc32ef031cc7cf9b741db9b023958b.tar.gz
chromium_src-0a357e63d5bc32ef031cc7cf9b741db9b023958b.tar.bz2
In-app installation when running from a disk image.
BUG=28986 TEST=Launch from a disk image without having a copy already installed in /Applications, and with permission to write to /Applications. Review URL: http://codereview.chromium.org/1599011 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43527 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/app/generated_resources.grd46
-rw-r--r--chrome/browser/browser_main.cc16
-rw-r--r--chrome/browser/browser_main_mac.mm1
-rw-r--r--chrome/browser/cocoa/install_from_dmg.h14
-rw-r--r--chrome/browser/cocoa/install_from_dmg.mm432
-rwxr-xr-xchrome/chrome_browser.gypi2
6 files changed, 511 insertions, 0 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 050d5a7..0b903a1 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -7127,6 +7127,52 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_SAVE_PAGE_FILE_FORMAT_PROMPT_MAC" desc="The title of the File Format label for saving a page.">
Format:
</message>
+
+ <!-- Install from disk image -->
+ <message name="IDS_INSTALL_FROM_DMG_TITLE" desc="Title of the dialog asking whether to install from the disk image. Mac-only.">
+ Do you want to install <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>?
+ </message>
+ <message name="IDS_INSTALL_FROM_DMG_PROMPT" desc="Prompt asking whether to install from the disk image. Mac-only.">
+ You’re running <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> from its disk image. Installing it on your computer lets you run it without the disk image, and ensures it will be kept up to date.
+ </message>
+ <message name="IDS_INSTALL_FROM_DMG_YES" desc="Button to approve installation from the disk image. Mac-only.">
+ Install
+ </message>
+ <message name="IDS_INSTALL_FROM_DMG_NO" desc="Button to cancel installation from the disk image. Mac-only.">
+ Don’t Install
+ </message>
+ <message name="IDS_INSTALL_FROM_DMG_AUTHENTICATION_PROMPT" desc="The prompt to be displayed in the authentication dialog when installing from the disk image. The system will add a sentence asking for an administrator's name and password. Mac-only.">
+ <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> will be installed.
+ </message>
+ <message name="IDS_INSTALL_FROM_DMG_ERROR_TITLE" desc="Error dialog title to be displayed when installation from the disk image fails. Mac-only.">
+ Installation failed.
+ </message>
+ <message name="IDS_INSTALL_FROM_DMG_ERROR" desc="Error dialog message to be displayed when installation from the disk image fails. Mac-only.">
+ <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> couldn’t complete installation, but will continue to run from its disk image.
+ </message>
+
+ <!-- Update from disk image -->
+ <message name="IDS_UPDATE_FROM_DMG_TITLE" desc="Title of the dialog asking whether to update from the disk image. Mac-only.">
+ Do you want to update <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>?
+ </message>
+ <message name="IDS_UPDATE_FROM_DMG_PROMPT" desc="Prompt asking whether to update from the disk image. Mac-only.">
+ You’re running <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> from its disk image. If you update the installed copy, you can run it without the disk image in the future.
+ </message>
+ <message name="IDS_UPDATE_FROM_DMG_YES" desc="Button to approve update from the disk image. Mac-only.">
+ Update
+ </message>
+ <message name="IDS_UPDATE_FROM_DMG_NO" desc="Button to cancel update from the disk image. Mac-only.">
+ Don’t Update
+ </message>
+ <message name="IDS_UPDATE_FROM_DMG_AUTHENTICATION_PROMPT" desc="The prompt to be displayed in the authentication dialog when updating from the disk image. The system will add a sentence asking for an administrator's name and password. Mac-only.">
+ <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> will be updated.
+ </message>
+ <message name="IDS_UPDATE_FROM_DMG_ERROR_TITLE" desc="Error dialog title to be displayed when update from the disk image fails. Mac-only.">
+ Update failed.
+ </message>
+ <message name="IDS_UPDATE_FROM_DMG_ERROR" desc="Error dialog message to be displayed when update from the disk image fails. Mac-only.">
+ <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> couldn’t update the installed copy, but will continue to run from its disk image.
+ </message>
</if>
<!-- Filebrowser Strings -->
diff --git a/chrome/browser/browser_main.cc b/chrome/browser/browser_main.cc
index fe83d99..20b0a07 100644
--- a/chrome/browser/browser_main.cc
+++ b/chrome/browser/browser_main.cc
@@ -127,6 +127,10 @@
#include "sandbox/src/sandbox.h"
#endif // defined(OS_WIN)
+#if defined(OS_MACOSX)
+#include "chrome/browser/cocoa/install_from_dmg.h"
+#endif
+
#if defined(TOOLKIT_VIEWS)
#include "chrome/browser/views/chrome_views_delegate.h"
#include "views/focus/accelerator_handler.h"
@@ -973,6 +977,18 @@ int BrowserMain(const MainFunctionParams& parameters) {
// Create the TranslateManager singleton.
Singleton<TranslateManager>::get();
+#if defined(OS_MACOSX)
+ if (!parsed_command_line.HasSwitch(switches::kNoFirstRun)) {
+ // Disk image installation is sort of a first-run task, so it shares the
+ // kNoFirstRun switch.
+ if (MaybeInstallFromDiskImage()) {
+ // The application was installed and the installed copy has been
+ // launched. This process is now obsolete. Exit.
+ return ResultCodes::NORMAL_EXIT;
+ }
+ }
+#endif
+
// Show the First Run UI if this is the first time Chrome has been run on
// this computer, or we're being compelled to do so by a command line flag.
// Note that this be done _after_ the PrefService is initialized and all
diff --git a/chrome/browser/browser_main_mac.mm b/chrome/browser/browser_main_mac.mm
index 38d3395..266ae24 100644
--- a/chrome/browser/browser_main_mac.mm
+++ b/chrome/browser/browser_main_mac.mm
@@ -43,6 +43,7 @@ void WillInitializeMainMessageLoop(const MainFunctionParams& parameters) {
// have the strings avaiable for localization.
ResourceBundle::InitSharedInstance(std::wstring());
}
+
// Now load the nib.
[NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
diff --git a/chrome/browser/cocoa/install_from_dmg.h b/chrome/browser/cocoa/install_from_dmg.h
new file mode 100644
index 0000000..437973d
--- /dev/null
+++ b/chrome/browser/cocoa/install_from_dmg.h
@@ -0,0 +1,14 @@
+// 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.
+
+#ifndef CHROME_BROWSER_COCOA_INSTALL_FROM_DMG_H_
+#define CHROME_BROWSER_COCOA_INSTALL_FROM_DMG_H_
+
+// If the application is running from a read-only disk image, prompts the user
+// to install it to the hard drive. If the user approves, the application
+// will be installed and launched, and MaybeInstallFromDiskImage will return
+// true. In that case, the caller must exit expeditiously.
+bool MaybeInstallFromDiskImage();
+
+#endif // CHROME_BROWSER_COCOA_INSTALL_FROM_DMG_H_
diff --git a/chrome/browser/cocoa/install_from_dmg.mm b/chrome/browser/cocoa/install_from_dmg.mm
new file mode 100644
index 0000000..d6e28f0
--- /dev/null
+++ b/chrome/browser/cocoa/install_from_dmg.mm
@@ -0,0 +1,432 @@
+// 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.
+
+#include "chrome/browser/cocoa/install_from_dmg.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+#import <AppKit/AppKit.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/CoreServices.h>
+#include <IOKit/IOKitLib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+
+#import "app/l10n_util_mac.h"
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/scoped_nsautorelease_pool.h"
+#include "base/sys_info.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+
+// When C++ exceptions are disabled, the C++ library defines |try| and
+// |catch| so as to allow exception-expecting C++ code to build properly when
+// language support for exceptions is not present. These macros interfere
+// with the use of |@try| and |@catch| in Objective-C files such as this one.
+// Undefine these macros here, after everything has been #included, since
+// there will be no C++ uses and only Objective-C uses from this point on.
+#undef try
+#undef catch
+
+namespace {
+
+// Just like scoped_cftyperef from base/scoped_cftyperef.h, but for
+// io_object_t and subclasses.
+template<typename IOT>
+class scoped_ioobject {
+ public:
+ typedef IOT element_type;
+
+ explicit scoped_ioobject(IOT object = NULL)
+ : object_(object) {
+ }
+
+ ~scoped_ioobject() {
+ if (object_)
+ IOObjectRelease(object_);
+ }
+
+ void reset(IOT object = NULL) {
+ if (object_)
+ IOObjectRelease(object_);
+ object_ = object;
+ }
+
+ bool operator==(IOT that) const {
+ return object_ == that;
+ }
+
+ bool operator!=(IOT that) const {
+ return object_ != that;
+ }
+
+ operator IOT() const {
+ return object_;
+ }
+
+ IOT get() const {
+ return object_;
+ }
+
+ void swap(scoped_ioobject& that) {
+ IOT temp = that.object_;
+ that.object_ = object_;
+ object_ = temp;
+ }
+
+ IOT release() {
+ IOT temp = object_;
+ object_ = NULL;
+ return temp;
+ }
+
+ private:
+ IOT object_;
+
+ DISALLOW_COPY_AND_ASSIGN(scoped_ioobject);
+};
+
+// Returns true if |path| is located on a read-only filesystem of a disk
+// image. Returns false if not, or in the event of an error.
+bool IsPathOnReadOnlyDiskImage(const char path[]) {
+ struct statfs statfs_buf;
+ if (statfs(path, &statfs_buf) != 0) {
+ PLOG(ERROR) << "statfs " << path;
+ return false;
+ }
+
+ if (!(statfs_buf.f_flags & MNT_RDONLY)) {
+ // Not on a read-only filesystem.
+ return false;
+ }
+
+ const char dev_root[] = "/dev/";
+ const int dev_root_length = arraysize(dev_root) - 1;
+ if (strncmp(statfs_buf.f_mntfromname, dev_root, dev_root_length) != 0) {
+ // Not rooted at dev_root, no BSD name to search on.
+ return false;
+ }
+
+ // BSD names in IOKit don't include dev_root.
+ const char* bsd_device_name = statfs_buf.f_mntfromname + dev_root_length;
+
+ const mach_port_t master_port = kIOMasterPortDefault;
+
+ // IOBSDNameMatching gives ownership of match_dict to the caller, but
+ // IOServiceGetMatchingServices will assume that reference.
+ CFMutableDictionaryRef match_dict = IOBSDNameMatching(master_port,
+ 0,
+ bsd_device_name);
+ if (!match_dict) {
+ LOG(ERROR) << "IOBSDNameMatching " << bsd_device_name;
+ return false;
+ }
+
+ io_iterator_t iterator_ref;
+ kern_return_t kr = IOServiceGetMatchingServices(master_port,
+ match_dict,
+ &iterator_ref);
+ if (kr != KERN_SUCCESS) {
+ LOG(ERROR) << "IOServiceGetMatchingServices " << bsd_device_name
+ << ": kernel error " << kr;
+ return false;
+ }
+ scoped_ioobject<io_iterator_t> iterator(iterator_ref);
+ iterator_ref = NULL;
+
+ // There needs to be exactly one matching service.
+ scoped_ioobject<io_service_t> filesystem_service(IOIteratorNext(iterator));
+ if (!filesystem_service) {
+ LOG(ERROR) << "IOIteratorNext " << bsd_device_name << ": no service";
+ return false;
+ }
+ scoped_ioobject<io_service_t> unexpected_service(IOIteratorNext(iterator));
+ if (unexpected_service) {
+ LOG(ERROR) << "IOIteratorNext " << bsd_device_name << ": too many services";
+ return false;
+ }
+
+ iterator.reset();
+
+ const char disk_image_class[] = "IOHDIXController";
+
+ // This is highly unlikely. The filesystem service is expected to be of
+ // class IOMedia. Since the filesystem service's entire ancestor chain
+ // will be checked, though, check the filesystem service's class itself.
+ if (IOObjectConformsTo(filesystem_service, disk_image_class)) {
+ return true;
+ }
+
+ kr = IORegistryEntryCreateIterator(filesystem_service,
+ kIOServicePlane,
+ kIORegistryIterateRecursively |
+ kIORegistryIterateParents,
+ &iterator_ref);
+ if (kr != KERN_SUCCESS) {
+ LOG(ERROR) << "IORegistryEntryCreateIterator " << bsd_device_name
+ << ": kernel error " << kr;
+ return false;
+ }
+ iterator.reset(iterator_ref);
+ iterator_ref = NULL;
+
+ // Look at each of the filesystem service's ancestor services, beginning
+ // with the parent, iterating all the way up to the device tree's root. If
+ // any ancestor service matches the class used for disk images, the
+ // filesystem resides on a disk image.
+ for(scoped_ioobject<io_service_t> ancestor_service(IOIteratorNext(iterator));
+ ancestor_service;
+ ancestor_service.reset(IOIteratorNext(iterator))) {
+ if (IOObjectConformsTo(ancestor_service, disk_image_class)) {
+ return true;
+ }
+ }
+
+ // The filesystem does not reside on a disk image.
+ return false;
+}
+
+// Returns true if the application is located on a read-only filesystem of a
+// disk image. Returns false if not, or in the event of an error.
+bool IsAppRunningFromReadOnlyDiskImage() {
+ return IsPathOnReadOnlyDiskImage(
+ [[[NSBundle mainBundle] bundlePath] fileSystemRepresentation]);
+}
+
+// Shows a dialog asking the user whether or not to install from the disk
+// image. Returns true if the user approves installation.
+bool ShouldInstallDialog() {
+ NSString* title = l10n_util::GetNSStringFWithFixup(
+ IDS_INSTALL_FROM_DMG_TITLE, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
+ NSString* prompt = l10n_util::GetNSStringFWithFixup(
+ IDS_INSTALL_FROM_DMG_PROMPT, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
+ NSString* yes = l10n_util::GetNSStringWithFixup(IDS_INSTALL_FROM_DMG_YES);
+ NSString* no = l10n_util::GetNSStringWithFixup(IDS_INSTALL_FROM_DMG_NO);
+
+ NSAlert* alert = [[[NSAlert alloc] init] autorelease];
+
+ [alert setAlertStyle:NSInformationalAlertStyle];
+ [alert setMessageText:title];
+ [alert setInformativeText:prompt];
+ [alert addButtonWithTitle:yes];
+ NSButton* cancel_button = [alert addButtonWithTitle:no];
+ [cancel_button setKeyEquivalent:@"\e"];
+
+ NSInteger result = [alert runModal];
+
+ return result == NSAlertFirstButtonReturn;
+}
+
+// Copies source_path to target_path and performs any additional on-disk
+// bookkeeping needed to be able to launch target_path properly.
+bool InstallFromDiskImage(NSString* source_path, NSString* target_path) {
+ NSFileManager* file_manager = [NSFileManager defaultManager];
+
+ // For the purposes of this copy, the file manager's delegate shouldn't be
+ // consulted at all. Clear the delegate and restore it after the copy is
+ // done.
+ id file_manager_delegate = [file_manager delegate];
+ [file_manager setDelegate:nil];
+
+ NSError* copy_error;
+ bool copy_result = [file_manager copyItemAtPath:source_path
+ toPath:target_path
+ error:&copy_error];
+
+ [file_manager setDelegate:file_manager_delegate];
+
+ if (!copy_result) {
+ LOG(ERROR) << "-[NSFileManager copyItemAtPath:toPath:error:]: "
+ << [[copy_error description] UTF8String];
+ return false;
+ }
+
+ // Since the application performed the copy, and the application has the
+ // quarantine bit (LSFileQuarantineEnabled) set, the installed copy will
+ // be quarantined. That's bad, because it will cause the quarantine dialog
+ // to be displayed, possibly after a long delay, when the application is
+ // relaunched. Use xattr to drop the quarantine attribute.
+ //
+ // There are three reasons not to use MDItemRemoveAttribute directly:
+ // 1. MDItemRemoveAttribute is a private API.
+ // 2. The operation needs to be recursive, and writing a bunch of code to
+ // handle the recursion just to call a private API is annoying.
+ // 3. All of this stuff will likely move into a shell script anyway, and
+ // the shell script will have no choice but to use xattr.
+
+ int32 os_major, os_minor, os_patch;
+ base::SysInfo::OperatingSystemVersionNumbers(&os_major, &os_minor, &os_patch);
+
+ const NSString* xattr_path = @"/usr/bin/xattr";
+ const NSString* quarantine_attribute = @"com.apple.quarantine";
+ NSString* launch_path;
+ NSArray* arguments;
+
+ if (os_major > 10 || (os_major == 10 && os_minor >= 6)) {
+ // On 10.6, xattr supports -r for recursive operation.
+ launch_path = xattr_path;
+ arguments = [NSArray arrayWithObjects:@"-r",
+ @"-d",
+ quarantine_attribute,
+ target_path,
+ nil];
+ } else {
+ // On earlier systems, xattr doesn't support -r, so run xattr via find.
+ launch_path = @"/usr/bin/find";
+ arguments = [NSArray arrayWithObjects:target_path,
+ @"-exec",
+ xattr_path,
+ @"-d",
+ quarantine_attribute,
+ @"{}",
+ @"+",
+ nil];
+ }
+
+ NSTask* task;
+ @try {
+ task = [NSTask launchedTaskWithLaunchPath:launch_path
+ arguments:arguments];
+ } @catch(NSException* exception) {
+ LOG(ERROR) << "+[NSTask launchedTaskWithLaunchPath:arguments:]: "
+ << [[exception description] UTF8String];
+ return false;
+ }
+
+ [task waitUntilExit];
+ int status = [task terminationStatus];
+ if (status != 0) {
+ LOG(ERROR) << "/usr/bin/xattr: status " << status;
+ return false;
+ }
+
+ return true;
+}
+
+// Launches the application at app_path. The arguments passed to app_path
+// will be the same as the arguments used to invoke this process, except any
+// arguments beginning with -psn_ will be stripped.
+bool LaunchInstalledApp(NSString* app_path) {
+ const UInt8* app_path_c =
+ reinterpret_cast<const UInt8*>([app_path fileSystemRepresentation]);
+ FSRef app_fsref;
+ OSStatus err = FSPathMakeRef(app_path_c, &app_fsref, NULL);
+ if (err != noErr) {
+ LOG(ERROR) << "FSPathMakeRef: " << err;
+ return false;
+ }
+
+ // Use an empty dictionary for the environment.
+ NSDictionary* environment = [NSDictionary dictionary];
+
+ const std::vector<std::string>& argv =
+ CommandLine::ForCurrentProcess()->argv();
+ NSMutableArray* arguments =
+ [NSMutableArray arrayWithCapacity:argv.size() - 1];
+ // Start at argv[1]. LSOpenApplication adds its own argv[0] as the path of
+ // the launched executable.
+ for (size_t index = 1; index < argv.size(); ++index) {
+ std::string argument = argv[index];
+ const char psn_flag[] = "-psn_";
+ const int psn_flag_length = arraysize(psn_flag) - 1;
+ if (argument.compare(0, psn_flag_length, psn_flag) != 0) {
+ // Strip any -psn_ arguments, as they apply to a specific process.
+ [arguments addObject:[NSString stringWithUTF8String:argument.c_str()]];
+ }
+ }
+
+ struct LSApplicationParameters parameters = {0};
+ parameters.flags = kLSLaunchDefaults;
+ parameters.application = &app_fsref;
+ parameters.environment = reinterpret_cast<CFDictionaryRef>(environment);
+ parameters.argv = reinterpret_cast<CFArrayRef>(arguments);
+
+ err = LSOpenApplication(&parameters, NULL);
+ if (err != noErr) {
+ LOG(ERROR) << "LSOpenApplication: " << err;
+ return false;
+ }
+
+ return true;
+}
+
+void ShowErrorDialog() {
+ NSString* title = l10n_util::GetNSStringWithFixup(
+ IDS_INSTALL_FROM_DMG_ERROR_TITLE);
+ NSString* error = l10n_util::GetNSStringFWithFixup(
+ IDS_INSTALL_FROM_DMG_ERROR, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
+ NSString* ok = l10n_util::GetNSStringWithFixup(IDS_OK);
+
+ NSAlert* alert = [[[NSAlert alloc] init] autorelease];
+
+ [alert setAlertStyle:NSWarningAlertStyle];
+ [alert setMessageText:title];
+ [alert setInformativeText:error];
+ [alert addButtonWithTitle:ok];
+
+ [alert runModal];
+}
+
+} // namespace
+
+bool MaybeInstallFromDiskImage() {
+ base::ScopedNSAutoreleasePool autorelease_pool;
+
+ if (!IsAppRunningFromReadOnlyDiskImage()) {
+ return false;
+ }
+
+ NSArray* application_directories =
+ NSSearchPathForDirectoriesInDomains(NSApplicationDirectory,
+ NSLocalDomainMask,
+ YES);
+ if ([application_directories count] == 0) {
+ LOG(ERROR) << "NSSearchPathForDirectoriesInDomains: "
+ << "no local application directories";
+ return false;
+ }
+ NSString* application_directory = [application_directories objectAtIndex:0];
+
+ NSFileManager* file_manager = [NSFileManager defaultManager];
+
+ BOOL is_directory;
+ if (![file_manager fileExistsAtPath:application_directory
+ isDirectory:&is_directory] ||
+ !is_directory) {
+ LOG(INFO) << "No application directory at "
+ << [application_directory UTF8String];
+ return false;
+ }
+
+ // TODO(mark): When this happens, prompt for authentication.
+ if (![file_manager isWritableFileAtPath:application_directory]) {
+ LOG(INFO) << "Non-writable application directory at "
+ << [application_directory UTF8String];
+ return false;
+ }
+
+ NSString* source_path = [[NSBundle mainBundle] bundlePath];
+ NSString* application_name = [source_path lastPathComponent];
+ NSString* target_path =
+ [application_directory stringByAppendingPathComponent:application_name];
+
+ if ([file_manager fileExistsAtPath:target_path]) {
+ LOG(INFO) << "Something already exists at " << [target_path UTF8String];
+ return false;
+ }
+
+ if (!ShouldInstallDialog()) {
+ return false;
+ }
+
+ if (!InstallFromDiskImage(source_path, target_path) ||
+ !LaunchInstalledApp(target_path)) {
+ ShowErrorDialog();
+ return false;
+ }
+
+ return true;
+}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 383018d..0d88f93 100755
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -694,6 +694,8 @@
'browser/cocoa/infobar_gradient_view.h',
'browser/cocoa/infobar_gradient_view.mm',
'browser/cocoa/infobar_test_helper.h',
+ 'browser/cocoa/install_from_dmg.h',
+ 'browser/cocoa/install_from_dmg.mm',
'browser/cocoa/keystone_glue.h',
'browser/cocoa/keystone_glue.mm',
'browser/cocoa/keystone_infobar.h',