Skip to content

Commit 3bb5899

Browse files
committed
Bug 1700795. Detect running from .dmg on macOS and report telemetry. r=mstange,mossop
Differential Revision: https://phabricator.services.mozilla.com/D121567
1 parent 558464a commit 3bb5899

File tree

7 files changed

+164
-0
lines changed

7 files changed

+164
-0
lines changed

toolkit/components/telemetry/Scalars.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5668,6 +5668,28 @@ startup:
56685668
record_in_processes:
56695669
- main
56705670

5671+
first_run_is_from_dmg:
5672+
bug_numbers:
5673+
- 1700795
5674+
description: >
5675+
Recorded on first run of a Firefox install on macOS, with a boolean value
5676+
indicating whether Firefox is being run directly from .dmg without
5677+
installing, or not. Note if running from a .dmg we will get a new ping
5678+
everytime the .dmg is remounted due to App Translocation.
5679+
expires: "99"
5680+
keyed: false
5681+
kind: boolean
5682+
notification_emails:
5683+
- jwatt@jwatt.org
5684+
- mbalfanz@mozilla.com
5685+
operating_systems:
5686+
- mac
5687+
products:
5688+
- 'firefox'
5689+
record_in_processes:
5690+
- 'main'
5691+
release_channel_collection: opt-out
5692+
56715693
script.preloader:
56725694
mainthread_recompile:
56735695
bug_numbers:

toolkit/profile/nsIToolkitProfileService.idl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ interface nsIToolkitProfileService : nsISupports
134134
*/
135135
readonly attribute unsigned long profileCount;
136136

137+
/**
138+
* Returns true if this is the first time the current install has run.
139+
*/
140+
[noscript, notxpcom] readonly attribute boolean isFirstRun;
141+
137142
/**
138143
* Flush the profiles list file. This will fail with
139144
* NS_ERROR_DATABASE_CHANGED if the files on disk have changed since the

toolkit/profile/nsToolkitProfileService.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,6 +2163,9 @@ nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
21632163
return NS_OK;
21642164
}
21652165

2166+
NS_IMETHODIMP_(bool)
2167+
nsToolkitProfileService::GetIsFirstRun() { return mIsFirstRun; }
2168+
21662169
NS_IMETHODIMP
21672170
nsToolkitProfileService::Flush() {
21682171
if (GetIsListOutdated()) {

toolkit/xre/MacRunFromDmgUtils.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/* -*- Mode: C++; tab-width: 4; 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 defines the interface between Cocoa-specific Obj-C++ and generic
7+
// C++, so it itself cannot have any Obj-C bits in it.
8+
9+
#ifndef MacRunFromDmgUtils_h_
10+
#define MacRunFromDmgUtils_h_
11+
12+
namespace mozilla {
13+
namespace MacRunFromDmgUtils {
14+
15+
/**
16+
* Returns true if the app is running from the read-only filesystem of a
17+
* mounted .dmg. Returns false if not, or if we fail to determine whether it
18+
* is.
19+
*/
20+
bool IsAppRunningFromDmg();
21+
22+
} // namespace MacRunFromDmgUtils
23+
} // namespace mozilla
24+
25+
#endif

toolkit/xre/MacRunFromDmgUtils.mm

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/* -*- Mode: C++; tab-width: 4; 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+
#include <ApplicationServices/ApplicationServices.h>
7+
#include <CoreFoundation/CoreFoundation.h>
8+
#include <CoreServices/CoreServices.h>
9+
#include <IOKit/IOKitLib.h>
10+
#include <string.h>
11+
#include <sys/mount.h>
12+
#include <sys/param.h>
13+
14+
#include "MacRunFromDmgUtils.h"
15+
16+
#include "nsCocoaFeatures.h"
17+
#include "nsCommandLine.h"
18+
#include "nsCommandLineServiceMac.h"
19+
#include "nsILocalFileMac.h"
20+
#include "nsIMacDockSupport.h"
21+
#include "nsObjCExceptions.h"
22+
#include "nsString.h"
23+
24+
// For IOKit docs, see:
25+
// https://developer.apple.com/documentation/iokit
26+
// https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/
27+
28+
namespace mozilla {
29+
namespace MacRunFromDmgUtils {
30+
31+
bool IsAppRunningFromDmg() {
32+
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
33+
34+
const char* path = [[[NSBundle mainBundle] bundlePath] fileSystemRepresentation];
35+
36+
struct statfs statfsBuf;
37+
if (statfs(path, &statfsBuf) != 0) {
38+
return false;
39+
}
40+
41+
// Optimization to minimize impact on startup time:
42+
if (!(statfsBuf.f_flags & MNT_RDONLY)) {
43+
return false;
44+
}
45+
46+
// Get the "BSD device name" ("diskXsY", as found in /dev) of the filesystem
47+
// our app is on so we can use IOKit to get its IOMedia object:
48+
const char devDirPath[] = "/dev/";
49+
const int devDirPathLength = strlen(devDirPath);
50+
if (strncmp(statfsBuf.f_mntfromname, devDirPath, devDirPathLength) != 0) {
51+
// Does this ever happen?
52+
return false;
53+
}
54+
const char* bsdDeviceName = statfsBuf.f_mntfromname + devDirPathLength;
55+
56+
// Get the IOMedia object:
57+
// (Note: IOServiceGetMatchingServices takes ownership of serviceDict's ref.)
58+
CFMutableDictionaryRef serviceDict = IOBSDNameMatching(kIOMasterPortDefault, 0, bsdDeviceName);
59+
if (!serviceDict) {
60+
return false;
61+
}
62+
io_service_t media = IOServiceGetMatchingService(kIOMasterPortDefault, serviceDict);
63+
if (!media || !IOObjectConformsTo(media, "IOMedia")) {
64+
return false;
65+
}
66+
67+
// Search the parent chain for a service implementing the disk image class
68+
// (taking care to start with `media` itself):
69+
io_service_t imageDrive = IO_OBJECT_NULL;
70+
io_iterator_t iter;
71+
if (IORegistryEntryCreateIterator(media, kIOServicePlane,
72+
kIORegistryIterateRecursively | kIORegistryIterateParents,
73+
&iter) != KERN_SUCCESS) {
74+
IOObjectRelease(media);
75+
return false;
76+
}
77+
const char* imageClass =
78+
nsCocoaFeatures::macOSVersionMajor() >= 12 ? "AppleDiskImageDevice" : "IOHDIXHDDrive";
79+
for (imageDrive = media; imageDrive; imageDrive = IOIteratorNext(iter)) {
80+
if (IOObjectConformsTo(imageDrive, imageClass)) {
81+
break;
82+
}
83+
IOObjectRelease(imageDrive);
84+
}
85+
IOObjectRelease(iter);
86+
87+
if (imageDrive) {
88+
IOObjectRelease(imageDrive);
89+
return true;
90+
}
91+
return false;
92+
93+
NS_OBJC_END_TRY_BLOCK_RETURN(false);
94+
}
95+
96+
} // namespace MacRunFromDmgUtils
97+
} // namespace mozilla

toolkit/xre/moz.build

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,14 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
8080
"LauncherRegistryInfo.cpp",
8181
]
8282
elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
83+
EXPORTS.mozilla += [
84+
"MacRunFromDmgUtils.h",
85+
]
8386
UNIFIED_SOURCES += [
8487
"MacApplicationDelegate.mm",
8588
"MacAutoreleasePool.mm",
8689
"MacLaunchHelper.mm",
90+
"MacRunFromDmgUtils.mm",
8791
"nsCommandLineServiceMac.mm",
8892
"nsNativeAppSupportCocoa.mm",
8993
"updaterfileutils_osx.mm",

toolkit/xre/nsAppRunner.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
# include "MacLaunchHelper.h"
4949
# include "MacApplicationDelegate.h"
5050
# include "MacAutoreleasePool.h"
51+
# include "MacRunFromDmgUtils.h"
5152
// these are needed for sysctl
5253
# include <sys/types.h>
5354
# include <sys/sysctl.h>
@@ -5475,6 +5476,13 @@ int XREMain::XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig) {
54755476
rv = mScopedXPCOM->Initialize(/* aInitJSContext = */ false);
54765477
NS_ENSURE_SUCCESS(rv, 1);
54775478

5479+
#ifdef XP_MACOSX
5480+
if (mProfileSvc->GetIsFirstRun()) {
5481+
Telemetry::ScalarSet(Telemetry::ScalarID::STARTUP_FIRST_RUN_IS_FROM_DMG,
5482+
MacRunFromDmgUtils::IsAppRunningFromDmg());
5483+
}
5484+
#endif
5485+
54785486
// run!
54795487
rv = XRE_mainRun();
54805488

0 commit comments

Comments
 (0)