[Mac] Use an AppleEvent to tell the Finder to open downloaded items, rather than NSWorkspace.

BUG=32921,50263
TEST=Force a PDF to download. Quit Preview, if open. Open the downloaded PDF from the download shelf. Preview opens and becomes frontmost.
TEST=Download a file of a type that you do not have an application with which to open it. Open it from the download shelf. Finder bounces for your attention to choose an application to open it.

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@56026 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/base/base.gypi b/base/base.gypi
index 62b3de3..a3078200 100644
--- a/base/base.gypi
+++ b/base/base.gypi
@@ -184,6 +184,7 @@
           'resource_util.h',
           'safe_strerror_posix.cc',
           'safe_strerror_posix.h',
+          'scoped_aedesc.h',
           'scoped_bstr_win.cc',
           'scoped_bstr_win.h',
           'scoped_callback_factory.h',
@@ -331,6 +332,11 @@
               ],
             },
           ],
+          [ 'OS != "mac"', {
+              'sources!': [
+                'scoped_aedesc.h'
+              ],
+          }],
           # For now, just test the *BSD platforms enough to exclude them.
           # Subsequent changes will include them further.
           [ 'OS != "freebsd"', {
diff --git a/base/scoped_aedesc.h b/base/scoped_aedesc.h
new file mode 100644
index 0000000..9239887
--- /dev/null
+++ b/base/scoped_aedesc.h
@@ -0,0 +1,47 @@
+// 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 BASE_SCOPED_AEDESC_H_
+#define BASE_SCOPED_AEDESC_H_
+#pragma once
+
+#import <CoreServices/CoreServices.h>
+
+#include "base/basictypes.h"
+
+// The scoped_aedesc is used to scope AppleEvent descriptors.  On creation,
+// it will store a NULL descriptor.  On destruction, it will dispose of the
+// descriptor.
+//
+// This class is parameterized for additional type safety checks.  You can use
+// the generic AEDesc type by not providing a template parameter:
+//  scoped_aedesc<> desc;
+template <typename AEDescType = AEDesc>
+class scoped_aedesc {
+ public:
+  scoped_aedesc() {
+    AECreateDesc(typeNull, NULL, 0, &desc_);
+  }
+
+  ~scoped_aedesc() {
+    AEDisposeDesc(&desc_);
+  }
+
+  // Used for in parameters.
+  operator const AEDescType*() {
+    return &desc_;
+  }
+
+  // Used for out parameters.
+  AEDescType* OutPointer() {
+    return &desc_;
+  }
+
+ private:
+  AEDescType desc_;
+
+  DISALLOW_COPY_AND_ASSIGN(scoped_aedesc);
+};
+
+#endif  // BASE_SCOPED_AEDESC_H_
diff --git a/chrome/browser/platform_util_mac.mm b/chrome/browser/platform_util_mac.mm
index 3192fcc..7c71950 100644
--- a/chrome/browser/platform_util_mac.mm
+++ b/chrome/browser/platform_util_mac.mm
@@ -5,12 +5,14 @@
 #include "chrome/browser/platform_util.h"
 
 #import <Cocoa/Cocoa.h>
+#import <CoreServices/CoreServices.h>
 
 #include "app/l10n_util.h"
 #include "app/l10n_util_mac.h"
 #include "base/file_path.h"
 #include "base/logging.h"
 #include "base/mac_util.h"
+#include "base/scoped_aedesc.h"
 #include "base/sys_string_conversions.h"
 #include "googleurl/src/gurl.h"
 #include "grit/generated_resources.h"
@@ -25,11 +27,97 @@
     LOG(WARNING) << "NSWorkspace failed to select file " << full_path.value();
 }
 
+// This function opens a file.  This doesn't use LaunchServices or NSWorkspace
+// because of two bugs:
+//  1. Incorrect app activation with com.apple.quarantine:
+//     http://crbug.com/32921
+//  2. Silent no-op for unassociated file types: http://crbug.com/50263
+// Instead, an AppleEvent is constructed to tell the Finder to open the
+// document.
 void OpenItem(const FilePath& full_path) {
   DCHECK_EQ([NSThread currentThread], [NSThread mainThread]);
   NSString* path_string = base::SysUTF8ToNSString(full_path.value());
-  if (!path_string || ![[NSWorkspace sharedWorkspace] openFile:path_string])
-    LOG(WARNING) << "NSWorkspace failed to open file " << full_path.value();
+  if (!path_string)
+    return;
+
+  OSErr status;
+
+  // Create the target of this AppleEvent, the Finder.
+  scoped_aedesc<AEAddressDesc> address;
+  const OSType finderCreatorCode = 'MACS';
+  status = AECreateDesc(typeApplSignature,  // type
+                        &finderCreatorCode,  // data
+                        sizeof(finderCreatorCode),  // dataSize
+                        address.OutPointer());  // result
+  if (status != noErr) {
+    LOG(WARNING) << "Could not create OpenItem() AE target";
+    return;
+  }
+
+  // Build the AppleEvent data structure that instructs Finder to open files.
+  scoped_aedesc<AppleEvent> theEvent;
+  status = AECreateAppleEvent(kCoreEventClass,  // theAEEventClass
+                              kAEOpenDocuments,  // theAEEventID
+                              address,  // target
+                              kAutoGenerateReturnID,  // returnID
+                              kAnyTransactionID,  // transactionID
+                              theEvent.OutPointer());  // result
+  if (status != noErr) {
+    LOG(WARNING) << "Could not create OpenItem() AE event";
+    return;
+  }
+
+  // Create the list of files (only ever one) to open.
+  scoped_aedesc<AEDescList> fileList;
+  status = AECreateList(NULL,  // factoringPtr
+                        0,  // factoredSize
+                        false,  // isRecord
+                        fileList.OutPointer());  // resultList
+  if (status != noErr) {
+    LOG(WARNING) << "Could not create OpenItem() AE file list";
+    return;
+  }
+
+  // Add the single path to the file list.  C-style cast to avoid both a
+  // static_cast and a const_cast to get across the toll-free bridge.
+  CFURLRef pathURLRef = (CFURLRef)[NSURL fileURLWithPath:path_string];
+  FSRef pathRef;
+  if (CFURLGetFSRef(pathURLRef, &pathRef)) {
+    status = AEPutPtr(fileList.OutPointer(),  // theAEDescList
+                      0,  // index
+                      typeFSRef,  // typeCode
+                      &pathRef,  // dataPtr
+                      sizeof(pathRef));  // dataSize
+    if (status != noErr) {
+      LOG(WARNING) << "Could not add file path to AE list in OpenItem()";
+      return;
+    }
+  } else {
+    LOG(WARNING) << "Could not get FSRef for path URL in OpenItem()";
+    return;
+  }
+
+  // Attach the file list to the AppleEvent.
+  status = AEPutParamDesc(theEvent.OutPointer(),  // theAppleEvent
+                          keyDirectObject,  // theAEKeyword
+                          fileList);  // theAEDesc
+  if (status != noErr) {
+    LOG(WARNING) << "Could not put the AE file list the path in OpenItem()";
+    return;
+  }
+
+  // Send the actual event.  Do not care about the reply.
+  scoped_aedesc<AppleEvent> reply;
+  status = AESend(theEvent,  // theAppleEvent
+                  reply.OutPointer(),  // reply
+                  kAENoReply + kAEAlwaysInteract,  // sendMode
+                  kAENormalPriority,  // sendPriority
+                  kAEDefaultTimeout,  // timeOutInTicks
+                  NULL, // idleProc
+                  NULL);  // filterProc
+  if (status != noErr) {
+    LOG(WARNING) << "Could not send AE to Finder in OpenItem()";
+  }
 }
 
 void OpenExternal(const GURL& url) {