Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / base / mac / foundation_util.mm
blobbd5d51452dcf6a1d7ec63b9fd60a39a3a2931282
1 // Copyright (c) 2012 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.
5 #include "base/mac/foundation_util.h"
7 #include <stdlib.h>
8 #include <string.h>
10 #include "base/files/file_path.h"
11 #include "base/logging.h"
12 #include "base/mac/bundle_locations.h"
13 #include "base/mac/mac_logging.h"
14 #include "base/numerics/safe_conversions.h"
15 #include "base/strings/sys_string_conversions.h"
17 #if !defined(OS_IOS)
18 extern "C" {
19 CFTypeID SecACLGetTypeID();
20 CFTypeID SecTrustedApplicationGetTypeID();
21 Boolean _CFIsObjC(CFTypeID typeID, CFTypeRef obj);
22 }  // extern "C"
23 #endif
25 namespace base {
26 namespace mac {
28 namespace {
30 bool g_cached_am_i_bundled_called = false;
31 bool g_cached_am_i_bundled_value = false;
32 bool g_override_am_i_bundled = false;
33 bool g_override_am_i_bundled_value = false;
35 bool UncachedAmIBundled() {
36 #if defined(OS_IOS)
37   // All apps are bundled on iOS.
38   return true;
39 #else
40   if (g_override_am_i_bundled)
41     return g_override_am_i_bundled_value;
43   // Yes, this is cheap.
44   return [[base::mac::OuterBundle() bundlePath] hasSuffix:@".app"];
45 #endif
48 }  // namespace
50 bool AmIBundled() {
51   // If the return value is not cached, this function will return different
52   // values depending on when it's called. This confuses some client code, see
53   // http://crbug.com/63183 .
54   if (!g_cached_am_i_bundled_called) {
55     g_cached_am_i_bundled_called = true;
56     g_cached_am_i_bundled_value = UncachedAmIBundled();
57   }
58   DCHECK_EQ(g_cached_am_i_bundled_value, UncachedAmIBundled())
59       << "The return value of AmIBundled() changed. This will confuse tests. "
60       << "Call SetAmIBundled() override manually if your test binary "
61       << "delay-loads the framework.";
62   return g_cached_am_i_bundled_value;
65 void SetOverrideAmIBundled(bool value) {
66 #if defined(OS_IOS)
67   // It doesn't make sense not to be bundled on iOS.
68   if (!value)
69     NOTREACHED();
70 #endif
71   g_override_am_i_bundled = true;
72   g_override_am_i_bundled_value = value;
75 BASE_EXPORT void ClearAmIBundledCache() {
76   g_cached_am_i_bundled_called = false;
79 bool IsBackgroundOnlyProcess() {
80   // This function really does want to examine NSBundle's idea of the main
81   // bundle dictionary.  It needs to look at the actual running .app's
82   // Info.plist to access its LSUIElement property.
83   NSDictionary* info_dictionary = [base::mac::MainBundle() infoDictionary];
84   return [[info_dictionary objectForKey:@"LSUIElement"] boolValue] != NO;
87 FilePath PathForFrameworkBundleResource(CFStringRef resourceName) {
88   NSBundle* bundle = base::mac::FrameworkBundle();
89   NSString* resourcePath = [bundle pathForResource:(NSString*)resourceName
90                                             ofType:nil];
91   return NSStringToFilePath(resourcePath);
94 OSType CreatorCodeForCFBundleRef(CFBundleRef bundle) {
95   OSType creator = kUnknownType;
96   CFBundleGetPackageInfo(bundle, NULL, &creator);
97   return creator;
100 OSType CreatorCodeForApplication() {
101   CFBundleRef bundle = CFBundleGetMainBundle();
102   if (!bundle)
103     return kUnknownType;
105   return CreatorCodeForCFBundleRef(bundle);
108 bool GetSearchPathDirectory(NSSearchPathDirectory directory,
109                             NSSearchPathDomainMask domain_mask,
110                             FilePath* result) {
111   DCHECK(result);
112   NSArray* dirs =
113       NSSearchPathForDirectoriesInDomains(directory, domain_mask, YES);
114   if ([dirs count] < 1) {
115     return false;
116   }
117   *result = NSStringToFilePath([dirs objectAtIndex:0]);
118   return true;
121 bool GetLocalDirectory(NSSearchPathDirectory directory, FilePath* result) {
122   return GetSearchPathDirectory(directory, NSLocalDomainMask, result);
125 bool GetUserDirectory(NSSearchPathDirectory directory, FilePath* result) {
126   return GetSearchPathDirectory(directory, NSUserDomainMask, result);
129 FilePath GetUserLibraryPath() {
130   FilePath user_library_path;
131   if (!GetUserDirectory(NSLibraryDirectory, &user_library_path)) {
132     DLOG(WARNING) << "Could not get user library path";
133   }
134   return user_library_path;
137 // Takes a path to an (executable) binary and tries to provide the path to an
138 // application bundle containing it. It takes the outermost bundle that it can
139 // find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app").
140 //   |exec_name| - path to the binary
141 //   returns - path to the application bundle, or empty on error
142 FilePath GetAppBundlePath(const FilePath& exec_name) {
143   const char kExt[] = ".app";
144   const size_t kExtLength = arraysize(kExt) - 1;
146   // Split the path into components.
147   std::vector<std::string> components;
148   exec_name.GetComponents(&components);
150   // It's an error if we don't get any components.
151   if (!components.size())
152     return FilePath();
154   // Don't prepend '/' to the first component.
155   std::vector<std::string>::const_iterator it = components.begin();
156   std::string bundle_name = *it;
157   DCHECK_GT(it->length(), 0U);
158   // If the first component ends in ".app", we're already done.
159   if (it->length() > kExtLength &&
160       !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength))
161     return FilePath(bundle_name);
163   // The first component may be "/" or "//", etc. Only append '/' if it doesn't
164   // already end in '/'.
165   if (bundle_name[bundle_name.length() - 1] != '/')
166     bundle_name += '/';
168   // Go through the remaining components.
169   for (++it; it != components.end(); ++it) {
170     DCHECK_GT(it->length(), 0U);
172     bundle_name += *it;
174     // If the current component ends in ".app", we're done.
175     if (it->length() > kExtLength &&
176         !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength))
177       return FilePath(bundle_name);
179     // Separate this component from the next one.
180     bundle_name += '/';
181   }
183   return FilePath();
186 #define TYPE_NAME_FOR_CF_TYPE_DEFN(TypeCF) \
187 std::string TypeNameForCFType(TypeCF##Ref) { \
188   return #TypeCF; \
191 TYPE_NAME_FOR_CF_TYPE_DEFN(CFArray);
192 TYPE_NAME_FOR_CF_TYPE_DEFN(CFBag);
193 TYPE_NAME_FOR_CF_TYPE_DEFN(CFBoolean);
194 TYPE_NAME_FOR_CF_TYPE_DEFN(CFData);
195 TYPE_NAME_FOR_CF_TYPE_DEFN(CFDate);
196 TYPE_NAME_FOR_CF_TYPE_DEFN(CFDictionary);
197 TYPE_NAME_FOR_CF_TYPE_DEFN(CFNull);
198 TYPE_NAME_FOR_CF_TYPE_DEFN(CFNumber);
199 TYPE_NAME_FOR_CF_TYPE_DEFN(CFSet);
200 TYPE_NAME_FOR_CF_TYPE_DEFN(CFString);
201 TYPE_NAME_FOR_CF_TYPE_DEFN(CFURL);
202 TYPE_NAME_FOR_CF_TYPE_DEFN(CFUUID);
204 TYPE_NAME_FOR_CF_TYPE_DEFN(CGColor);
206 TYPE_NAME_FOR_CF_TYPE_DEFN(CTFont);
207 TYPE_NAME_FOR_CF_TYPE_DEFN(CTRun);
209 #undef TYPE_NAME_FOR_CF_TYPE_DEFN
211 void NSObjectRetain(void* obj) {
212   id<NSObject> nsobj = static_cast<id<NSObject> >(obj);
213   [nsobj retain];
216 void NSObjectRelease(void* obj) {
217   id<NSObject> nsobj = static_cast<id<NSObject> >(obj);
218   [nsobj release];
221 void* CFTypeRefToNSObjectAutorelease(CFTypeRef cf_object) {
222   // When GC is on, NSMakeCollectable marks cf_object for GC and autorelease
223   // is a no-op.
224   //
225   // In the traditional GC-less environment, NSMakeCollectable is a no-op,
226   // and cf_object is autoreleased, balancing out the caller's ownership claim.
227   //
228   // NSMakeCollectable returns nil when used on a NULL object.
229   return [NSMakeCollectable(cf_object) autorelease];
232 static const char* base_bundle_id;
234 const char* BaseBundleID() {
235   if (base_bundle_id) {
236     return base_bundle_id;
237   }
239 #if defined(GOOGLE_CHROME_BUILD)
240   return "com.google.Chrome";
241 #else
242   return "org.chromium.Chromium";
243 #endif
246 void SetBaseBundleID(const char* new_base_bundle_id) {
247   if (new_base_bundle_id != base_bundle_id) {
248     free((void*)base_bundle_id);
249     base_bundle_id = new_base_bundle_id ? strdup(new_base_bundle_id) : NULL;
250   }
253 // Definitions for the corresponding CF_TO_NS_CAST_DECL macros in
254 // foundation_util.h.
255 #define CF_TO_NS_CAST_DEFN(TypeCF, TypeNS) \
257 TypeNS* CFToNSCast(TypeCF##Ref cf_val) { \
258   DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \
259   TypeNS* ns_val = \
260       const_cast<TypeNS*>(reinterpret_cast<const TypeNS*>(cf_val)); \
261   return ns_val; \
262 } \
264 TypeCF##Ref NSToCFCast(TypeNS* ns_val) { \
265   TypeCF##Ref cf_val = reinterpret_cast<TypeCF##Ref>(ns_val); \
266   DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \
267   return cf_val; \
270 #define CF_TO_NS_MUTABLE_CAST_DEFN(name) \
271 CF_TO_NS_CAST_DEFN(CF##name, NS##name) \
273 NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val) { \
274   DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \
275   NSMutable##name* ns_val = reinterpret_cast<NSMutable##name*>(cf_val); \
276   return ns_val; \
277 } \
279 CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val) { \
280   CFMutable##name##Ref cf_val = \
281       reinterpret_cast<CFMutable##name##Ref>(ns_val); \
282   DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \
283   return cf_val; \
286 CF_TO_NS_MUTABLE_CAST_DEFN(Array);
287 CF_TO_NS_MUTABLE_CAST_DEFN(AttributedString);
288 CF_TO_NS_CAST_DEFN(CFCalendar, NSCalendar);
289 CF_TO_NS_MUTABLE_CAST_DEFN(CharacterSet);
290 CF_TO_NS_MUTABLE_CAST_DEFN(Data);
291 CF_TO_NS_CAST_DEFN(CFDate, NSDate);
292 CF_TO_NS_MUTABLE_CAST_DEFN(Dictionary);
293 CF_TO_NS_CAST_DEFN(CFError, NSError);
294 CF_TO_NS_CAST_DEFN(CFLocale, NSLocale);
295 CF_TO_NS_CAST_DEFN(CFNumber, NSNumber);
296 CF_TO_NS_CAST_DEFN(CFRunLoopTimer, NSTimer);
297 CF_TO_NS_CAST_DEFN(CFTimeZone, NSTimeZone);
298 CF_TO_NS_MUTABLE_CAST_DEFN(Set);
299 CF_TO_NS_CAST_DEFN(CFReadStream, NSInputStream);
300 CF_TO_NS_CAST_DEFN(CFWriteStream, NSOutputStream);
301 CF_TO_NS_MUTABLE_CAST_DEFN(String);
302 CF_TO_NS_CAST_DEFN(CFURL, NSURL);
304 #if defined(OS_IOS)
305 CF_TO_NS_CAST_DEFN(CTFont, UIFont);
306 #else
307 // The NSFont/CTFont toll-free bridging is broken when it comes to type
308 // checking, so do some special-casing.
309 // http://www.openradar.me/15341349 rdar://15341349
310 NSFont* CFToNSCast(CTFontRef cf_val) {
311   NSFont* ns_val =
312       const_cast<NSFont*>(reinterpret_cast<const NSFont*>(cf_val));
313   DCHECK(!cf_val ||
314          CTFontGetTypeID() == CFGetTypeID(cf_val) ||
315          (_CFIsObjC(CTFontGetTypeID(), cf_val) &&
316           [ns_val isKindOfClass:NSClassFromString(@"NSFont")]));
317   return ns_val;
320 CTFontRef NSToCFCast(NSFont* ns_val) {
321   CTFontRef cf_val = reinterpret_cast<CTFontRef>(ns_val);
322   DCHECK(!cf_val ||
323          CTFontGetTypeID() == CFGetTypeID(cf_val) ||
324          [ns_val isKindOfClass:NSClassFromString(@"NSFont")]);
325   return cf_val;
327 #endif
329 #undef CF_TO_NS_CAST_DEFN
330 #undef CF_TO_NS_MUTABLE_CAST_DEFN
332 #define CF_CAST_DEFN(TypeCF) \
333 template<> TypeCF##Ref \
334 CFCast<TypeCF##Ref>(const CFTypeRef& cf_val) { \
335   if (cf_val == NULL) { \
336     return NULL; \
337   } \
338   if (CFGetTypeID(cf_val) == TypeCF##GetTypeID()) { \
339     return (TypeCF##Ref)(cf_val); \
340   } \
341   return NULL; \
342 } \
344 template<> TypeCF##Ref \
345 CFCastStrict<TypeCF##Ref>(const CFTypeRef& cf_val) { \
346   TypeCF##Ref rv = CFCast<TypeCF##Ref>(cf_val); \
347   DCHECK(cf_val == NULL || rv); \
348   return rv; \
351 CF_CAST_DEFN(CFArray);
352 CF_CAST_DEFN(CFBag);
353 CF_CAST_DEFN(CFBoolean);
354 CF_CAST_DEFN(CFData);
355 CF_CAST_DEFN(CFDate);
356 CF_CAST_DEFN(CFDictionary);
357 CF_CAST_DEFN(CFNull);
358 CF_CAST_DEFN(CFNumber);
359 CF_CAST_DEFN(CFSet);
360 CF_CAST_DEFN(CFString);
361 CF_CAST_DEFN(CFURL);
362 CF_CAST_DEFN(CFUUID);
364 CF_CAST_DEFN(CGColor);
366 CF_CAST_DEFN(CTFontDescriptor);
367 CF_CAST_DEFN(CTRun);
369 #if defined(OS_IOS)
370 CF_CAST_DEFN(CTFont);
371 #else
372 // The NSFont/CTFont toll-free bridging is broken when it comes to type
373 // checking, so do some special-casing.
374 // http://www.openradar.me/15341349 rdar://15341349
375 template<> CTFontRef
376 CFCast<CTFontRef>(const CFTypeRef& cf_val) {
377   if (cf_val == NULL) {
378     return NULL;
379   }
380   if (CFGetTypeID(cf_val) == CTFontGetTypeID()) {
381     return (CTFontRef)(cf_val);
382   }
384   if (!_CFIsObjC(CTFontGetTypeID(), cf_val))
385     return NULL;
387   id<NSObject> ns_val = reinterpret_cast<id>(const_cast<void*>(cf_val));
388   if ([ns_val isKindOfClass:NSClassFromString(@"NSFont")]) {
389     return (CTFontRef)(cf_val);
390   }
391   return NULL;
394 template<> CTFontRef
395 CFCastStrict<CTFontRef>(const CFTypeRef& cf_val) {
396   CTFontRef rv = CFCast<CTFontRef>(cf_val);
397   DCHECK(cf_val == NULL || rv);
398   return rv;
400 #endif
402 #if !defined(OS_IOS)
403 CF_CAST_DEFN(SecACL);
404 CF_CAST_DEFN(SecTrustedApplication);
405 #endif
407 #undef CF_CAST_DEFN
409 std::string GetValueFromDictionaryErrorMessage(
410     CFStringRef key, const std::string& expected_type, CFTypeRef value) {
411   ScopedCFTypeRef<CFStringRef> actual_type_ref(
412       CFCopyTypeIDDescription(CFGetTypeID(value)));
413   return "Expected value for key " +
414       base::SysCFStringRefToUTF8(key) +
415       " to be " +
416       expected_type +
417       " but it was " +
418       base::SysCFStringRefToUTF8(actual_type_ref) +
419       " instead";
422 NSString* FilePathToNSString(const FilePath& path) {
423   if (path.empty())
424     return nil;
425   return [NSString stringWithUTF8String:path.value().c_str()];
428 FilePath NSStringToFilePath(NSString* str) {
429   if (![str length])
430     return FilePath();
431   return FilePath([str fileSystemRepresentation]);
434 bool CFRangeToNSRange(CFRange range, NSRange* range_out) {
435   if (base::IsValueInRangeForNumericType<decltype(range_out->location)>(
436           range.location) &&
437       base::IsValueInRangeForNumericType<decltype(range_out->length)>(
438           range.length) &&
439       base::IsValueInRangeForNumericType<decltype(range_out->location)>(
440           range.location + range.length)) {
441     *range_out = NSMakeRange(range.location, range.length);
442     return true;
443   }
444   return false;
447 }  // namespace mac
448 }  // namespace base
450 std::ostream& operator<<(std::ostream& o, const CFStringRef string) {
451   return o << base::SysCFStringRefToUTF8(string);
454 std::ostream& operator<<(std::ostream& o, const CFErrorRef err) {
455   base::ScopedCFTypeRef<CFStringRef> desc(CFErrorCopyDescription(err));
456   base::ScopedCFTypeRef<CFDictionaryRef> user_info(CFErrorCopyUserInfo(err));
457   CFStringRef errorDesc = NULL;
458   if (user_info.get()) {
459     errorDesc = reinterpret_cast<CFStringRef>(
460         CFDictionaryGetValue(user_info.get(), kCFErrorDescriptionKey));
461   }
462   o << "Code: " << CFErrorGetCode(err)
463     << " Domain: " << CFErrorGetDomain(err)
464     << " Desc: " << desc.get();
465   if(errorDesc) {
466     o << "(" << errorDesc << ")";
467   }
468   return o;