Home | History | Annotate | Download | only in mac
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "base/mac/mac_util.h"
      6 
      7 #import <Cocoa/Cocoa.h>
      8 
      9 #include "base/file_path.h"
     10 #include "base/logging.h"
     11 #include "base/mac/scoped_cftyperef.h"
     12 #include "base/memory/scoped_nsobject.h"
     13 #include "base/sys_string_conversions.h"
     14 
     15 namespace base {
     16 namespace mac {
     17 
     18 namespace {
     19 
     20 // The current count of outstanding requests for full screen mode from browser
     21 // windows, plugins, etc.
     22 int g_full_screen_requests[kNumFullScreenModes] = { 0, 0, 0};
     23 
     24 // Sets the appropriate SystemUIMode based on the current full screen requests.
     25 // Since only one SystemUIMode can be active at a given time, full screen
     26 // requests are ordered by priority.  If there are no outstanding full screen
     27 // requests, reverts to normal mode.  If the correct SystemUIMode is already
     28 // set, does nothing.
     29 void SetUIMode() {
     30   // Get the current UI mode.
     31   SystemUIMode current_mode;
     32   GetSystemUIMode(&current_mode, NULL);
     33 
     34   // Determine which mode should be active, based on which requests are
     35   // currently outstanding.  More permissive requests take precedence.  For
     36   // example, plugins request |kFullScreenModeAutoHideAll|, while browser
     37   // windows request |kFullScreenModeHideDock| when the fullscreen overlay is
     38   // down.  Precedence goes to plugins in this case, so AutoHideAll wins over
     39   // HideDock.
     40   SystemUIMode desired_mode = kUIModeNormal;
     41   SystemUIOptions desired_options = 0;
     42   if (g_full_screen_requests[kFullScreenModeAutoHideAll] > 0) {
     43     desired_mode = kUIModeAllHidden;
     44     desired_options = kUIOptionAutoShowMenuBar;
     45   } else if (g_full_screen_requests[kFullScreenModeHideDock] > 0) {
     46     desired_mode = kUIModeContentHidden;
     47   } else if (g_full_screen_requests[kFullScreenModeHideAll] > 0) {
     48     desired_mode = kUIModeAllHidden;
     49   }
     50 
     51   if (current_mode != desired_mode)
     52     SetSystemUIMode(desired_mode, desired_options);
     53 }
     54 
     55 bool WasLaunchedAsLoginItem() {
     56   ProcessSerialNumber psn = { 0, kCurrentProcess };
     57 
     58   scoped_nsobject<NSDictionary> process_info(
     59       CFToNSCast(ProcessInformationCopyDictionary(&psn,
     60                      kProcessDictionaryIncludeAllInformationMask)));
     61 
     62   long long temp = [[process_info objectForKey:@"ParentPSN"] longLongValue];
     63   ProcessSerialNumber parent_psn =
     64       { (temp >> 32) & 0x00000000FFFFFFFFLL, temp & 0x00000000FFFFFFFFLL };
     65 
     66   scoped_nsobject<NSDictionary> parent_info(
     67       CFToNSCast(ProcessInformationCopyDictionary(&parent_psn,
     68                      kProcessDictionaryIncludeAllInformationMask)));
     69 
     70   // Check that creator process code is that of loginwindow.
     71   BOOL result =
     72       [[parent_info objectForKey:@"FileCreator"] isEqualToString:@"lgnw"];
     73 
     74   return result == YES;
     75 }
     76 
     77 // Looks into Shared File Lists corresponding to Login Items for the item
     78 // representing the current application. If such an item is found, returns a
     79 // retained reference to it. Caller is responsible for releasing the reference.
     80 LSSharedFileListItemRef GetLoginItemForApp() {
     81   ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate(
     82       NULL, kLSSharedFileListSessionLoginItems, NULL));
     83 
     84   if (!login_items.get()) {
     85     LOG(ERROR) << "Couldn't get a Login Items list.";
     86     return NULL;
     87   }
     88 
     89   scoped_nsobject<NSArray> login_items_array(
     90       CFToNSCast(LSSharedFileListCopySnapshot(login_items, NULL)));
     91 
     92   NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
     93 
     94   for(NSUInteger i = 0; i < [login_items_array count]; ++i) {
     95     LSSharedFileListItemRef item = reinterpret_cast<LSSharedFileListItemRef>(
     96         [login_items_array objectAtIndex:i]);
     97     CFURLRef item_url_ref = NULL;
     98 
     99     if (LSSharedFileListItemResolve(item, 0, &item_url_ref, NULL) == noErr) {
    100       ScopedCFTypeRef<CFURLRef> item_url(item_url_ref);
    101       if (CFEqual(item_url, url)) {
    102         CFRetain(item);
    103         return item;
    104       }
    105     }
    106   }
    107 
    108   return NULL;
    109 }
    110 
    111 #if !defined(MAC_OS_X_VERSION_10_6) || \
    112     MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
    113 // kLSSharedFileListLoginItemHidden is supported on
    114 // 10.5, but missing from the 10.5 headers.
    115 // http://openradar.appspot.com/6482251
    116 static NSString* kLSSharedFileListLoginItemHidden =
    117     @"com.apple.loginitem.HideOnLaunch";
    118 #endif
    119 
    120 bool IsHiddenLoginItem(LSSharedFileListItemRef item) {
    121   ScopedCFTypeRef<CFBooleanRef> hidden(reinterpret_cast<CFBooleanRef>(
    122       LSSharedFileListItemCopyProperty(item,
    123           reinterpret_cast<CFStringRef>(kLSSharedFileListLoginItemHidden))));
    124 
    125   return hidden && hidden == kCFBooleanTrue;
    126 }
    127 
    128 }  // namespace
    129 
    130 std::string PathFromFSRef(const FSRef& ref) {
    131   ScopedCFTypeRef<CFURLRef> url(
    132       CFURLCreateFromFSRef(kCFAllocatorDefault, &ref));
    133   NSString *path_string = [(NSURL *)url.get() path];
    134   return [path_string fileSystemRepresentation];
    135 }
    136 
    137 bool FSRefFromPath(const std::string& path, FSRef* ref) {
    138   OSStatus status = FSPathMakeRef((const UInt8*)path.c_str(),
    139                                   ref, nil);
    140   return status == noErr;
    141 }
    142 
    143 CGColorSpaceRef GetSRGBColorSpace() {
    144   // Leaked.  That's OK, it's scoped to the lifetime of the application.
    145   static CGColorSpaceRef g_color_space_sRGB =
    146       CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
    147   LOG_IF(ERROR, !g_color_space_sRGB) << "Couldn't get the sRGB color space";
    148   return g_color_space_sRGB;
    149 }
    150 
    151 CGColorSpaceRef GetSystemColorSpace() {
    152   // Leaked.  That's OK, it's scoped to the lifetime of the application.
    153   // Try to get the main display's color space.
    154   static CGColorSpaceRef g_system_color_space =
    155       CGDisplayCopyColorSpace(CGMainDisplayID());
    156 
    157   if (!g_system_color_space) {
    158     // Use a generic RGB color space.  This is better than nothing.
    159     g_system_color_space = CGColorSpaceCreateDeviceRGB();
    160 
    161     if (g_system_color_space) {
    162       LOG(WARNING) <<
    163           "Couldn't get the main display's color space, using generic";
    164     } else {
    165       LOG(ERROR) << "Couldn't get any color space";
    166     }
    167   }
    168 
    169   return g_system_color_space;
    170 }
    171 
    172 // Add a request for full screen mode.  Must be called on the main thread.
    173 void RequestFullScreen(FullScreenMode mode) {
    174   DCHECK_LT(mode, kNumFullScreenModes);
    175   if (mode >= kNumFullScreenModes)
    176     return;
    177 
    178   DCHECK_GE(g_full_screen_requests[mode], 0);
    179   g_full_screen_requests[mode] = std::max(g_full_screen_requests[mode] + 1, 1);
    180   SetUIMode();
    181 }
    182 
    183 // Release a request for full screen mode.  Must be called on the main thread.
    184 void ReleaseFullScreen(FullScreenMode mode) {
    185   DCHECK_LT(mode, kNumFullScreenModes);
    186   if (mode >= kNumFullScreenModes)
    187     return;
    188 
    189   DCHECK_GT(g_full_screen_requests[mode], 0);
    190   g_full_screen_requests[mode] = std::max(g_full_screen_requests[mode] - 1, 0);
    191   SetUIMode();
    192 }
    193 
    194 // Switches full screen modes.  Releases a request for |from_mode| and adds a
    195 // new request for |to_mode|.  Must be called on the main thread.
    196 void SwitchFullScreenModes(FullScreenMode from_mode, FullScreenMode to_mode) {
    197   DCHECK_LT(from_mode, kNumFullScreenModes);
    198   DCHECK_LT(to_mode, kNumFullScreenModes);
    199   if (from_mode >= kNumFullScreenModes || to_mode >= kNumFullScreenModes)
    200     return;
    201 
    202   DCHECK_GT(g_full_screen_requests[from_mode], 0);
    203   DCHECK_GE(g_full_screen_requests[to_mode], 0);
    204   g_full_screen_requests[from_mode] =
    205       std::max(g_full_screen_requests[from_mode] - 1, 0);
    206   g_full_screen_requests[to_mode] =
    207       std::max(g_full_screen_requests[to_mode] + 1, 1);
    208   SetUIMode();
    209 }
    210 
    211 void SetCursorVisibility(bool visible) {
    212   if (visible)
    213     [NSCursor unhide];
    214   else
    215     [NSCursor hide];
    216 }
    217 
    218 bool ShouldWindowsMiniaturizeOnDoubleClick() {
    219   // We use an undocumented method in Cocoa; if it doesn't exist, default to
    220   // |true|. If it ever goes away, we can do (using an undocumented pref key):
    221   //   NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
    222   //   return ![defaults objectForKey:@"AppleMiniaturizeOnDoubleClick"] ||
    223   //          [defaults boolForKey:@"AppleMiniaturizeOnDoubleClick"];
    224   BOOL methodImplemented =
    225       [NSWindow respondsToSelector:@selector(_shouldMiniaturizeOnDoubleClick)];
    226   DCHECK(methodImplemented);
    227   return !methodImplemented ||
    228       [NSWindow performSelector:@selector(_shouldMiniaturizeOnDoubleClick)];
    229 }
    230 
    231 void ActivateProcess(pid_t pid) {
    232   ProcessSerialNumber process;
    233   OSStatus status = GetProcessForPID(pid, &process);
    234   if (status == noErr) {
    235     SetFrontProcess(&process);
    236   } else {
    237     LOG(WARNING) << "Unable to get process for pid " << pid;
    238   }
    239 }
    240 
    241 bool SetFileBackupExclusion(const FilePath& file_path, bool exclude) {
    242   NSString* filePath =
    243       [NSString stringWithUTF8String:file_path.value().c_str()];
    244 
    245   // If being asked to exclude something in a tmp directory, just lie and say it
    246   // was done.  TimeMachine will already ignore tmp directories.  This keeps the
    247   // temporary profiles used by unittests from being added to the exclude list.
    248   // Otherwise, as /Library/Preferences/com.apple.TimeMachine.plist grows the
    249   // bots slow down due to reading/writing all the temporary profiles used over
    250   // time.
    251 
    252   NSString* tmpDir = NSTemporaryDirectory();
    253   // Make sure the temp dir is terminated with a slash
    254   if (tmpDir && ![tmpDir hasSuffix:@"/"])
    255     tmpDir = [tmpDir stringByAppendingString:@"/"];
    256   // '/var' is a link to '/private/var', make sure to check both forms.
    257   NSString* privateTmpDir = nil;
    258   if ([tmpDir hasPrefix:@"/var/"])
    259     privateTmpDir = [@"/private" stringByAppendingString:tmpDir];
    260 
    261   if ((tmpDir && [filePath hasPrefix:tmpDir]) ||
    262       (privateTmpDir && [filePath hasPrefix:privateTmpDir]) ||
    263       [filePath hasPrefix:@"/tmp/"] ||
    264       [filePath hasPrefix:@"/var/tmp/"] ||
    265       [filePath hasPrefix:@"/private/tmp/"] ||
    266       [filePath hasPrefix:@"/private/var/tmp/"]) {
    267     return true;
    268   }
    269 
    270   NSURL* url = [NSURL fileURLWithPath:filePath];
    271   // Note that we always set CSBackupSetItemExcluded's excludeByPath param
    272   // to true.  This prevents a problem with toggling the setting: if the file
    273   // is excluded with excludeByPath set to true then excludeByPath must
    274   // also be true when un-excluding the file, otherwise the un-excluding
    275   // will be ignored.
    276   bool success =
    277       CSBackupSetItemExcluded((CFURLRef)url, exclude, true) == noErr;
    278   if (!success)
    279     LOG(WARNING) << "Failed to set backup exclusion for file '"
    280                  << file_path.value().c_str() << "'.  Continuing.";
    281   return success;
    282 }
    283 
    284 void SetProcessName(CFStringRef process_name) {
    285   if (!process_name || CFStringGetLength(process_name) == 0) {
    286     NOTREACHED() << "SetProcessName given bad name.";
    287     return;
    288   }
    289 
    290   if (![NSThread isMainThread]) {
    291     NOTREACHED() << "Should only set process name from main thread.";
    292     return;
    293   }
    294 
    295   // Warning: here be dragons! This is SPI reverse-engineered from WebKit's
    296   // plugin host, and could break at any time (although realistically it's only
    297   // likely to break in a new major release).
    298   // When 10.7 is available, check that this still works, and update this
    299   // comment for 10.8.
    300 
    301   // Private CFType used in these LaunchServices calls.
    302   typedef CFTypeRef PrivateLSASN;
    303   typedef PrivateLSASN (*LSGetCurrentApplicationASNType)();
    304   typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN,
    305                                                           CFStringRef,
    306                                                           CFStringRef,
    307                                                           CFDictionaryRef*);
    308 
    309   static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
    310       NULL;
    311   static LSSetApplicationInformationItemType
    312       ls_set_application_information_item_func = NULL;
    313   static CFStringRef ls_display_name_key = NULL;
    314 
    315   static bool did_symbol_lookup = false;
    316   if (!did_symbol_lookup) {
    317     did_symbol_lookup = true;
    318     CFBundleRef launch_services_bundle =
    319         CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
    320     if (!launch_services_bundle) {
    321       LOG(ERROR) << "Failed to look up LaunchServices bundle";
    322       return;
    323     }
    324 
    325     ls_get_current_application_asn_func =
    326         reinterpret_cast<LSGetCurrentApplicationASNType>(
    327             CFBundleGetFunctionPointerForName(
    328                 launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN")));
    329     if (!ls_get_current_application_asn_func)
    330       LOG(ERROR) << "Could not find _LSGetCurrentApplicationASN";
    331 
    332     ls_set_application_information_item_func =
    333         reinterpret_cast<LSSetApplicationInformationItemType>(
    334             CFBundleGetFunctionPointerForName(
    335                 launch_services_bundle,
    336                 CFSTR("_LSSetApplicationInformationItem")));
    337     if (!ls_set_application_information_item_func)
    338       LOG(ERROR) << "Could not find _LSSetApplicationInformationItem";
    339 
    340     CFStringRef* key_pointer = reinterpret_cast<CFStringRef*>(
    341         CFBundleGetDataPointerForName(launch_services_bundle,
    342                                       CFSTR("_kLSDisplayNameKey")));
    343     ls_display_name_key = key_pointer ? *key_pointer : NULL;
    344     if (!ls_display_name_key)
    345       LOG(ERROR) << "Could not find _kLSDisplayNameKey";
    346 
    347     // Internally, this call relies on the Mach ports that are started up by the
    348     // Carbon Process Manager.  In debug builds this usually happens due to how
    349     // the logging layers are started up; but in release, it isn't started in as
    350     // much of a defined order.  So if the symbols had to be loaded, go ahead
    351     // and force a call to make sure the manager has been initialized and hence
    352     // the ports are opened.
    353     ProcessSerialNumber psn;
    354     GetCurrentProcess(&psn);
    355   }
    356   if (!ls_get_current_application_asn_func ||
    357       !ls_set_application_information_item_func ||
    358       !ls_display_name_key) {
    359     return;
    360   }
    361 
    362   PrivateLSASN asn = ls_get_current_application_asn_func();
    363   // Constant used by WebKit; what exactly it means is unknown.
    364   const int magic_session_constant = -2;
    365   OSErr err =
    366       ls_set_application_information_item_func(magic_session_constant, asn,
    367                                                ls_display_name_key,
    368                                                process_name,
    369                                                NULL /* optional out param */);
    370   LOG_IF(ERROR, err) << "Call to set process name failed, err " << err;
    371 }
    372 
    373 // Converts a NSImage to a CGImageRef.  Normally, the system frameworks can do
    374 // this fine, especially on 10.6.  On 10.5, however, CGImage cannot handle
    375 // converting a PDF-backed NSImage into a CGImageRef.  This function will
    376 // rasterize the PDF into a bitmap CGImage.  The caller is responsible for
    377 // releasing the return value.
    378 CGImageRef CopyNSImageToCGImage(NSImage* image) {
    379   // This is based loosely on http://www.cocoadev.com/index.pl?CGImageRef .
    380   NSSize size = [image size];
    381   ScopedCFTypeRef<CGContextRef> context(
    382       CGBitmapContextCreate(NULL,  // Allow CG to allocate memory.
    383                             size.width,
    384                             size.height,
    385                             8,  // bitsPerComponent
    386                             0,  // bytesPerRow - CG will calculate by default.
    387                             [[NSColorSpace genericRGBColorSpace] CGColorSpace],
    388                             kCGBitmapByteOrder32Host |
    389                                 kCGImageAlphaPremultipliedFirst));
    390   if (!context.get())
    391     return NULL;
    392 
    393   [NSGraphicsContext saveGraphicsState];
    394   [NSGraphicsContext setCurrentContext:
    395       [NSGraphicsContext graphicsContextWithGraphicsPort:context.get()
    396                                                  flipped:NO]];
    397   [image drawInRect:NSMakeRect(0,0, size.width, size.height)
    398            fromRect:NSZeroRect
    399           operation:NSCompositeCopy
    400            fraction:1.0];
    401   [NSGraphicsContext restoreGraphicsState];
    402 
    403   return CGBitmapContextCreateImage(context);
    404 }
    405 
    406 bool CheckLoginItemStatus(bool* is_hidden) {
    407   ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp());
    408   if (!item.get())
    409     return false;
    410 
    411   if (is_hidden)
    412     *is_hidden = IsHiddenLoginItem(item);
    413 
    414   return true;
    415 }
    416 
    417 void AddToLoginItems(bool hide_on_startup) {
    418   ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp());
    419   if (item.get() && (IsHiddenLoginItem(item) == hide_on_startup)) {
    420     return;  // Already is a login item with required hide flag.
    421   }
    422 
    423   ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate(
    424       NULL, kLSSharedFileListSessionLoginItems, NULL));
    425 
    426   if (!login_items.get()) {
    427     LOG(ERROR) << "Couldn't get a Login Items list.";
    428     return;
    429   }
    430 
    431   // Remove the old item, it has wrong hide flag, we'll create a new one.
    432   if (item.get()) {
    433     LSSharedFileListItemRemove(login_items, item);
    434   }
    435 
    436   NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
    437 
    438   BOOL hide = hide_on_startup ? YES : NO;
    439   NSDictionary* properties =
    440       [NSDictionary
    441         dictionaryWithObject:[NSNumber numberWithBool:hide]
    442                       forKey:(NSString*)kLSSharedFileListLoginItemHidden];
    443 
    444   ScopedCFTypeRef<LSSharedFileListItemRef> new_item;
    445   new_item.reset(LSSharedFileListInsertItemURL(
    446       login_items, kLSSharedFileListItemLast, NULL, NULL,
    447       reinterpret_cast<CFURLRef>(url),
    448       reinterpret_cast<CFDictionaryRef>(properties), NULL));
    449 
    450   if (!new_item.get()) {
    451     LOG(ERROR) << "Couldn't insert current app into Login Items list.";
    452   }
    453 }
    454 
    455 void RemoveFromLoginItems() {
    456   ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp());
    457   if (!item.get())
    458     return;
    459 
    460   ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate(
    461       NULL, kLSSharedFileListSessionLoginItems, NULL));
    462 
    463   if (!login_items.get()) {
    464     LOG(ERROR) << "Couldn't get a Login Items list.";
    465     return;
    466   }
    467 
    468   LSSharedFileListItemRemove(login_items, item);
    469 }
    470 
    471 bool WasLaunchedAsHiddenLoginItem() {
    472   if (!WasLaunchedAsLoginItem())
    473     return false;
    474 
    475   ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp());
    476   if (!item.get()) {
    477     LOG(ERROR) << "Process launched at Login but can't access Login Item List.";
    478     return false;
    479   }
    480   return IsHiddenLoginItem(item);
    481 }
    482 
    483 // Definitions for the corresponding CF_TO_NS_CAST_DECL macros in mac_util.h.
    484 #define CF_TO_NS_CAST_DEFN(TypeCF, TypeNS) \
    485 \
    486 TypeNS* CFToNSCast(TypeCF##Ref cf_val) { \
    487   DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \
    488   TypeNS* ns_val = \
    489       const_cast<TypeNS*>(reinterpret_cast<const TypeNS*>(cf_val)); \
    490   return ns_val; \
    491 } \
    492 \
    493 TypeCF##Ref NSToCFCast(TypeNS* ns_val) { \
    494   TypeCF##Ref cf_val = reinterpret_cast<TypeCF##Ref>(ns_val); \
    495   DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \
    496   return cf_val; \
    497 } \
    498 
    499 #define CF_TO_NS_MUTABLE_CAST_DEFN(name) \
    500 CF_TO_NS_CAST_DEFN(CF##name, NS##name) \
    501 \
    502 NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val) { \
    503   DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \
    504   NSMutable##name* ns_val = reinterpret_cast<NSMutable##name*>(cf_val); \
    505   return ns_val; \
    506 } \
    507 \
    508 CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val) { \
    509   CFMutable##name##Ref cf_val = \
    510       reinterpret_cast<CFMutable##name##Ref>(ns_val); \
    511   DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \
    512   return cf_val; \
    513 } \
    514 
    515 CF_TO_NS_MUTABLE_CAST_DEFN(Array);
    516 CF_TO_NS_MUTABLE_CAST_DEFN(AttributedString);
    517 CF_TO_NS_CAST_DEFN(CFCalendar, NSCalendar);
    518 CF_TO_NS_MUTABLE_CAST_DEFN(CharacterSet);
    519 CF_TO_NS_MUTABLE_CAST_DEFN(Data);
    520 CF_TO_NS_CAST_DEFN(CFDate, NSDate);
    521 CF_TO_NS_MUTABLE_CAST_DEFN(Dictionary);
    522 CF_TO_NS_CAST_DEFN(CFError, NSError);
    523 CF_TO_NS_CAST_DEFN(CFLocale, NSLocale);
    524 CF_TO_NS_CAST_DEFN(CFNumber, NSNumber);
    525 CF_TO_NS_CAST_DEFN(CFRunLoopTimer, NSTimer);
    526 CF_TO_NS_CAST_DEFN(CFTimeZone, NSTimeZone);
    527 CF_TO_NS_MUTABLE_CAST_DEFN(Set);
    528 CF_TO_NS_CAST_DEFN(CFReadStream, NSInputStream);
    529 CF_TO_NS_CAST_DEFN(CFWriteStream, NSOutputStream);
    530 CF_TO_NS_MUTABLE_CAST_DEFN(String);
    531 CF_TO_NS_CAST_DEFN(CFURL, NSURL);
    532 
    533 }  // namespace mac
    534 }  // namespace base
    535 
    536 std::ostream& operator<<(std::ostream& o, const CFStringRef string) {
    537   return o << base::SysCFStringRefToUTF8(string);
    538 }
    539 
    540 std::ostream& operator<<(std::ostream& o, const CFErrorRef err) {
    541   base::mac::ScopedCFTypeRef<CFStringRef> desc(CFErrorCopyDescription(err));
    542   base::mac::ScopedCFTypeRef<CFDictionaryRef> user_info(
    543       CFErrorCopyUserInfo(err));
    544   CFStringRef errorDesc = NULL;
    545   if (user_info.get()) {
    546     errorDesc = reinterpret_cast<CFStringRef>(
    547         CFDictionaryGetValue(user_info.get(), kCFErrorDescriptionKey));
    548   }
    549   o << "Code: " << CFErrorGetCode(err)
    550     << " Domain: " << CFErrorGetDomain(err)
    551     << " Desc: " << desc.get();
    552   if(errorDesc) {
    553     o << "(" << errorDesc << ")";
    554   }
    555   return o;
    556 }
    557