Bug 1937061: Hide new profile instances from the recent applications section of the dock. r=nika,spohl,profiles-reviewers,jhirsch

Adds an API to nsIMacDockSupport to allow launching an application bundle and hiding it from the
list of recent applications.

Differential Revision: https://phabricator.services.mozilla.com/D238206
This commit is contained in:
Dave Townsend
2025-03-06 11:32:40 +00:00
parent fb9b1427f2
commit 8c83e98f5e
7 changed files with 99 additions and 39 deletions

View File

@@ -663,36 +663,51 @@ class SelectableProfileServiceClass extends EventEmitter {
// App session lifecycle methods and multi-process support
/*
* Helper that returns an inited Firefox executable process (nsIProcess).
* Mostly useful for mocking in unit testing.
* Helper that executes a new Firefox process. Mostly useful for mocking in
* unit testing.
*/
getExecutableProcess() {
execProcess(aArgs) {
let executable = SelectableProfileServiceClass.getDirectory("XREExeF");
if (AppConstants.platform == "macosx") {
// Use the application bundle if possible.
let appBundle = executable.parent.parent.parent;
if (appBundle.path.endsWith(".app")) {
executable = appBundle;
Cc["@mozilla.org/widget/macdocksupport;1"]
.getService(Ci.nsIMacDockSupport)
.launchAppBundle(appBundle, aArgs, { addsToRecentItems: false });
return;
}
}
let process = Cc["@mozilla.org/process/util;1"].createInstance(
Ci.nsIProcess
);
let executable = SelectableProfileServiceClass.getDirectory("XREExeF");
process.init(executable);
return process;
process.runw(false, aArgs, aArgs.length);
}
/**
* Launch a new Firefox instance using the given selectable profile.
*
* @param {SelectableProfile} aProfile The profile to launch
* @param {string} url A url to open in launched profile
* @param {string} aUrl A url to open in launched profile
*/
launchInstance(aProfile, url) {
let process = this.getExecutableProcess();
launchInstance(aProfile, aUrl) {
let args = ["--profile", aProfile.path];
if (Services.appinfo.OS === "Darwin") {
args.unshift("-foreground");
}
if (url) {
args.push("-url", url);
if (aUrl) {
args.push("-url", aUrl);
} else {
args.push(`--${COMMAND_LINE_ACTIVATE}`);
}
process.runw(false, args, args.length);
this.execProcess(args);
}
/**

View File

@@ -15,15 +15,12 @@ async function promiseAppMenuOpened() {
add_task(async function test_appmenu_updates_on_edit() {
// Mock the executable process so we don't launch a new process when we
// create new profiles.
SelectableProfileService._getExecutableProcess =
SelectableProfileService.getExecutableProcess;
SelectableProfileService._execProcess = SelectableProfileService.execProcess;
registerCleanupFunction(() => {
SelectableProfileService.getExecutableProcess =
SelectableProfileService._getExecutableProcess;
SelectableProfileService.execProcess =
SelectableProfileService._execProcess;
});
SelectableProfileService.getExecutableProcess = () => {
return { runw: () => {} };
};
SelectableProfileService.execProcess = () => {};
// We need to create a second profile for the name to be shown in the app
// menu.

View File

@@ -10,9 +10,7 @@ add_task(async function test_dbLazilyCreated() {
);
// Mock the executable process so we doon't launch a new process
SelectableProfileService.getExecutableProcess = () => {
return { runw: () => {} };
};
SelectableProfileService.execProcess = () => {};
await SelectableProfileService.maybeSetupDataStore();
ok(

View File

@@ -66,13 +66,7 @@ add_task(async function test_selector_window() {
// mock() returns an object with a fake `runw` method that, when
// called, records its arguments.
let input = [];
let mock = () => {
return {
runw: (...args) => {
input.push(...args);
},
};
};
let mock = args => (input = args);
const profileSelector = dialog.document.querySelector("profile-selector");
await profileSelector.updateComplete;
@@ -130,7 +124,7 @@ add_task(async function test_selector_window() {
"Profile selector should be disabled"
);
profileSelector.selectableProfileService.getExecutableProcess = mock;
profileSelector.selectableProfileService.execProcess = mock;
const profiles = profileSelector.profileCards;
@@ -163,7 +157,7 @@ add_task(async function test_selector_window() {
expected = ["--profile", profile.path, "--profiles-activate"];
}
Assert.deepEqual(input[1], expected, "Expected runw arguments");
Assert.deepEqual(input, expected, "Expected runw arguments");
await assertGlean("profiles", "selector_window", "launch");

View File

@@ -9,18 +9,14 @@ add_task(async function test_launcher() {
// mock() returns an object with a fake `runw` method that, when
// called, records its arguments.
let input = [];
let mock = () => {
return {
runw: (...args) => {
input = args;
},
};
let mock = args => {
input = args;
};
let profile = await createTestProfile();
const SelectableProfileService = getSelectableProfileService();
SelectableProfileService.getExecutableProcess = mock;
SelectableProfileService.execProcess = mock;
SelectableProfileService.launchInstance(profile);
let expected;
@@ -35,7 +31,7 @@ add_task(async function test_launcher() {
expected = ["--profile", profile.path, "--profiles-activate"];
}
Assert.deepEqual(expected, input[1], "Expected runw arguments");
Assert.deepEqual(expected, input, "Expected runw arguments");
SelectableProfileService.launchInstance(profile, "about:profilemanager");
@@ -51,5 +47,5 @@ add_task(async function test_launcher() {
expected = ["--profile", profile.path, "-url", "about:profilemanager"];
}
Assert.deepEqual(expected, input[1], "Expected runw arguments");
Assert.deepEqual(expected, input, "Expected runw arguments");
});

View File

@@ -15,8 +15,10 @@
#include "nsString.h"
#include "imgLoader.h"
#include "MOZIconHelper.h"
#include "mozilla/MacStringHelpers.h"
#include "mozilla/SVGImageContext.h"
#include "nsISVGPaintContext.h"
#include "nsIFile.h"
NS_IMPL_ISUPPORTS(nsMacDockSupport, nsIMacDockSupport, nsITaskbarProgress)
@@ -508,3 +510,43 @@ nsresult nsMacDockSupport::EnsureAppIsPinnedToDock(
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}
nsresult nsMacDockSupport::LaunchAppBundle(
nsIFile* aAppBundle, const nsTArray<nsCString>& aArgs,
nsIAppBundleLaunchOptions* aLaunchOptions) {
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
nsString bundlePath;
nsresult rv = aAppBundle->GetPath(bundlePath);
NS_ENSURE_SUCCESS(rv, rv);
NSString* launchPath = mozilla::XPCOMStringToNSString(bundlePath);
NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:aArgs.Length()];
for (const auto& arg : aArgs) {
[arguments addObject:mozilla::XPCOMStringToNSString(arg)];
}
NSWorkspaceOpenConfiguration* config =
[NSWorkspaceOpenConfiguration configuration];
[config setArguments:arguments];
[config setCreatesNewApplicationInstance:YES];
[config setEnvironment:[[NSProcessInfo processInfo] environment]];
if (aLaunchOptions) {
bool val = false;
if (NS_SUCCEEDED(aLaunchOptions->GetAddsToRecentItems(&val))) {
[config setAddsToRecentItems:val];
}
}
[[NSWorkspace sharedWorkspace]
openApplicationAtURL:[NSURL fileURLWithPath:launchPath]
configuration:config
completionHandler:^(NSRunningApplication* aChild, NSError* aError){
}];
return NS_OK;
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}

View File

@@ -7,6 +7,16 @@
interface nsIStandaloneNativeMenu;
interface nsISVGPaintContext;
interface imgIContainer;
interface nsIFile;
[scriptable, uuid(2cbde069-b80d-4e63-b7c7-c4dfe4617028)]
interface nsIAppBundleLaunchOptions : nsISupports {
/**
* A Boolean value indicating whether to add the app or documents to the
* Recent Items menu.
*/
readonly attribute boolean addsToRecentItems;
};
/**
* Allow applications to interface with the Mac OS X Dock.
@@ -77,4 +87,12 @@ interface nsIMacDockSupport : nsISupports
*/
boolean ensureAppIsPinnedToDock([optional] in AString aAppPath,
[optional] in AString aAppToReplacePath);
/**
* Launches an application bundle passing the given command line arguments.
* The `aLaunchOptions` parameter can control aspects of how the bundle is
* launched.
*/
void launchAppBundle(in nsIFile aAppBundle, in Array<ACString> aArgs,
[optional] in nsIAppBundleLaunchOptions aLaunchOptions);
};