1/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6// This file makes some assumptions about the versions of macOS.
7// We are assuming that the major, minor and bugfix versions are each less than
8// 256.
9// There are MOZ_ASSERTs for that.
10
11// The formula for the version integer is (major << 16) + (minor << 8) + bugfix.
12
13#define MACOS_VERSION_MASK 0x00FFFFFF
14#define MACOS_MAJOR_VERSION_MASK 0x00FFFFFF
15#define MACOS_MINOR_VERSION_MASK 0x00FFFFFF
16#define MACOS_BUGFIX_VERSION_MASK 0x00FFFFFF
17#define MACOS_VERSION_10_0_HEX 0x000A0000
18#define MACOS_VERSION_10_9_HEX 0x000A0900
19#define MACOS_VERSION_10_10_HEX 0x000A0A00
20#define MACOS_VERSION_10_11_HEX 0x000A0B00
21#define MACOS_VERSION_10_12_HEX 0x000A0C00
22#define MACOS_VERSION_10_13_HEX 0x000A0D00
23#define MACOS_VERSION_10_14_HEX 0x000A0E00
24#define MACOS_VERSION_10_15_HEX 0x000A0F00
25#define MACOS_VERSION_10_16_HEX 0x000A1000
26#define MACOS_VERSION_11_0_HEX 0x000B0000
27#define MACOS_VERSION_12_0_HEX 0x000C0000
28
29#include "nsCocoaFeatures.h"
30#include "nsCocoaUtils.h"
31#include "nsDebug.h"
32#include "nsObjCExceptions.h"
33
34#import <Cocoa/Cocoa.h>
35#include <sys/sysctl.h>
36
37/*static*/ int32_t nsCocoaFeatures::mOSVersion = 0;
38
39// This should not be called with unchecked aMajor, which should be >= 10.
40inline int32_t AssembleVersion(int32_t aMajor, int32_t aMinor, int32_t aBugFix) {
41  MOZ_ASSERT(aMajor >= 10);
42  return (aMajor << 16) + (aMinor << 8) + aBugFix;
43}
44
45int32_t nsCocoaFeatures::ExtractMajorVersion(int32_t aVersion) {
46  MOZ_ASSERT((aVersion & MACOS_VERSION_MASK) == aVersion);
47  return (aVersion & 0xFF0000) >> 16;
48}
49
50int32_t nsCocoaFeatures::ExtractMinorVersion(int32_t aVersion) {
51  MOZ_ASSERT((aVersion & MACOS_VERSION_MASK) == aVersion);
52  return (aVersion & 0xFF00) >> 8;
53}
54
55int32_t nsCocoaFeatures::ExtractBugFixVersion(int32_t aVersion) {
56  MOZ_ASSERT((aVersion & MACOS_VERSION_MASK) == aVersion);
57  return aVersion & 0xFF;
58}
59
60static int intAtStringIndex(NSArray* array, int index) {
61  return [(NSString*)[array objectAtIndex:index] integerValue];
62}
63
64void nsCocoaFeatures::GetSystemVersion(int& major, int& minor, int& bugfix) {
65  major = minor = bugfix = 0;
66
67  NSString* versionString = [[NSDictionary
68      dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]
69      objectForKey:@"ProductVersion"];
70  if (!versionString) {
71    NS_ERROR("Couldn't read /System/Library/CoreServices/SystemVersion.plist to determine macOS "
72             "version.");
73    return;
74  }
75  NSArray* versions = [versionString componentsSeparatedByString:@"."];
76  NSUInteger count = [versions count];
77  if (count > 0) {
78    major = intAtStringIndex(versions, 0);
79    if (count > 1) {
80      minor = intAtStringIndex(versions, 1);
81      if (count > 2) {
82        bugfix = intAtStringIndex(versions, 2);
83      }
84    }
85  }
86}
87
88int32_t nsCocoaFeatures::GetVersion(int32_t aMajor, int32_t aMinor, int32_t aBugFix) {
89  int32_t macOSVersion;
90  if (aMajor < 10) {
91    aMajor = 10;
92    NS_ERROR("Couldn't determine macOS version, assuming 10.9");
93    macOSVersion = MACOS_VERSION_10_9_HEX;
94  } else if (aMajor == 10 && aMinor < 9) {
95    aMinor = 9;
96    NS_ERROR("macOS version too old, assuming 10.9");
97    macOSVersion = MACOS_VERSION_10_9_HEX;
98  } else {
99    MOZ_ASSERT(aMajor >= 10);
100    MOZ_ASSERT(aMajor < 256);
101    MOZ_ASSERT(aMinor >= 0);
102    MOZ_ASSERT(aMinor < 256);
103    MOZ_ASSERT(aBugFix >= 0);
104    MOZ_ASSERT(aBugFix < 256);
105    macOSVersion = AssembleVersion(aMajor, aMinor, aBugFix);
106  }
107  MOZ_ASSERT(aMajor == ExtractMajorVersion(macOSVersion));
108  MOZ_ASSERT(aMinor == ExtractMinorVersion(macOSVersion));
109  MOZ_ASSERT(aBugFix == ExtractBugFixVersion(macOSVersion));
110  return macOSVersion;
111}
112
113/*static*/ void nsCocoaFeatures::InitializeVersionNumbers() {
114  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
115
116  // Provide an autorelease pool to avoid leaking Cocoa objects,
117  // as this gets called before the main autorelease pool is in place.
118  nsAutoreleasePool localPool;
119
120  int major, minor, bugfix;
121  GetSystemVersion(major, minor, bugfix);
122  mOSVersion = GetVersion(major, minor, bugfix);
123
124  NS_OBJC_END_TRY_IGNORE_BLOCK;
125}
126
127/* static */ int32_t nsCocoaFeatures::macOSVersion() {
128  // Don't let this be called while we're first setting the value...
129  MOZ_ASSERT((mOSVersion & MACOS_VERSION_MASK) >= 0);
130  if (!mOSVersion) {
131    mOSVersion = -1;
132    InitializeVersionNumbers();
133  }
134  return mOSVersion;
135}
136
137/* static */ int32_t nsCocoaFeatures::macOSVersionMajor() {
138  return ExtractMajorVersion(macOSVersion());
139}
140
141/* static */ int32_t nsCocoaFeatures::macOSVersionMinor() {
142  return ExtractMinorVersion(macOSVersion());
143}
144
145/* static */ int32_t nsCocoaFeatures::macOSVersionBugFix() {
146  return ExtractBugFixVersion(macOSVersion());
147}
148
149/* static */ bool nsCocoaFeatures::OnSierraExactly() {
150  return (macOSVersion() >= MACOS_VERSION_10_12_HEX) && (macOSVersion() < MACOS_VERSION_10_13_HEX);
151}
152
153/* Version of OnSierraExactly as global function callable from cairo & skia */
154bool Gecko_OnSierraExactly() { return nsCocoaFeatures::OnSierraExactly(); }
155
156/* static */ bool nsCocoaFeatures::OnHighSierraOrLater() {
157  return (macOSVersion() >= MACOS_VERSION_10_13_HEX);
158}
159
160/* static */ bool nsCocoaFeatures::OnMojaveOrLater() {
161  return (macOSVersion() >= MACOS_VERSION_10_14_HEX);
162}
163
164/* static */ bool nsCocoaFeatures::OnCatalinaOrLater() {
165  return (macOSVersion() >= MACOS_VERSION_10_15_HEX);
166}
167
168/* static */ bool nsCocoaFeatures::OnBigSurOrLater() {
169  // Account for the version being 10.16 or 11.0 on Big Sur.
170  // The version is reported as 10.16 if SYSTEM_VERSION_COMPAT is set to 1,
171  // or if SYSTEM_VERSION_COMPAT is not set and the application is linked
172  // with a pre-Big Sur SDK.
173  // Firefox sets SYSTEM_VERSION_COMPAT to 0 in its Info.plist, so it'll
174  // usually see the correct 11.* version, despite being linked against an
175  // old SDK. However, it still sees the 10.16 compatibility version when
176  // launched from the command line, see bug 1727624. (This only applies to
177  // the Intel build - the arm64 build is linked against a Big Sur SDK and
178  // always sees the correct version.)
179  return ((macOSVersion() >= MACOS_VERSION_10_16_HEX) ||
180          (macOSVersion() >= MACOS_VERSION_11_0_HEX));
181}
182
183/* static */ bool nsCocoaFeatures::OnMontereyOrLater() {
184  // This check only works if SYSTEM_VERSION_COMPAT is off, otherwise
185  // Monterey pretends to be 10.16 and is indistinguishable from Big Sur.
186  // In practice, this means that an Intel Firefox build can return false
187  // from this function if it's launched from the command line, see bug 1727624.
188  // This will not be an issue anymore once we link against the Big Sur SDK.
189  return (macOSVersion() >= MACOS_VERSION_12_0_HEX);
190}
191
192/* static */ bool nsCocoaFeatures::IsAtLeastVersion(int32_t aMajor, int32_t aMinor,
193                                                    int32_t aBugFix) {
194  return macOSVersion() >= GetVersion(aMajor, aMinor, aBugFix);
195}
196
197/*
198 * Returns true if the process is running under Rosetta translation. Returns
199 * false if running natively or if an error was encountered. We use the
200 * `sysctl.proc_translated` sysctl which is documented by Apple to be used
201 * for this purpose. Note: using this in a sandboxed process requires allowing
202 * the sysctl in the sandbox policy.
203 */
204/* static */ bool nsCocoaFeatures::ProcessIsRosettaTranslated() {
205  int ret = 0;
206  size_t size = sizeof(ret);
207  if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) {
208    if (errno != ENOENT) {
209      fprintf(stderr, "Failed to check for translation environment\n");
210    }
211    return false;
212  }
213  return (ret == 1);
214}
215