Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions browser/base/content/nonbrowser-mac.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,35 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

var { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);

function queueFeltDockAction(isPrivate) {
if (!AppConstants.MOZ_ENTERPRISE) {
return;
}
const { queueFeltURL, FELT_OPEN_WINDOW_DISPOSITION } =
ChromeUtils.importESModule("resource:///modules/FeltURLHandler.sys.mjs");
let payload = {
url: "",
disposition: isPrivate
? FELT_OPEN_WINDOW_DISPOSITION.NEW_PRIVATE_WINDOW
: FELT_OPEN_WINDOW_DISPOSITION.NEW_WINDOW,
};
queueFeltURL(payload);
}

var NonBrowserWindow = {
delayedStartupTimeoutId: null,
feltReadyObserver: null,
MAC_HIDDEN_WINDOW: "chrome://browser/content/hiddenWindowMac.xhtml",

openBrowserWindowFromDockMenu(options = {}) {
if (AppConstants.MOZ_ENTERPRISE && Services.felt.isFeltUI()) {
queueFeltDockAction(options.private);
return null;
}
let existingWindow = BrowserWindowTracker.getTopWindow();
options.openerWindow = existingWindow || window;
let win = OpenBrowserWindow(options);
Expand Down Expand Up @@ -108,11 +132,67 @@ var NonBrowserWindow = {
document.getElementById("key_quitApplication").remove();
document.getElementById("menu_FileQuitItem").removeAttribute("key");
}

// In Felt mode, disable dock menu items until Firefox is ready
if (AppConstants.MOZ_ENTERPRISE && Services.felt.isFeltUI()) {
this.setupFeltDockMenuState();
}
}

this.delayedStartupTimeoutId = setTimeout(() => this.delayedStartup(), 0);
},

setupFeltDockMenuState() {
let newWindowItem = document.getElementById("macDockMenuNewWindow");
let privateWindowItem = document.getElementById(
"macDockMenuNewPrivateWindow"
);

// Check if Firefox is already ready (e.g., after restart)
let isReady = false;
try {
const { isFeltFirefoxReady } = ChromeUtils.importESModule(
"chrome://felt/content/FeltProcessParent.sys.mjs"
);
isReady = isFeltFirefoxReady();
} catch {
// Extension not loaded yet
}

if (isReady) {
return;
}

// Disable dock menu items until Firefox is ready
if (newWindowItem && !newWindowItem.hidden) {
newWindowItem.setAttribute("disabled", "true");
}
if (privateWindowItem && !privateWindowItem.hidden) {
privateWindowItem.setAttribute("disabled", "true");
}

// Listen for Firefox ready notification
this.feltReadyObserver = {
observe: () => {
if (newWindowItem && !newWindowItem.hidden) {
newWindowItem.removeAttribute("disabled");
}
if (privateWindowItem && !privateWindowItem.hidden) {
privateWindowItem.removeAttribute("disabled");
}
Services.obs.removeObserver(
NonBrowserWindow.feltReadyObserver,
"felt-firefox-window-ready"
);
NonBrowserWindow.feltReadyObserver = null;
},
};
Services.obs.addObserver(
this.feltReadyObserver,
"felt-firefox-window-ready"
);
},

delayedStartup() {
this.delayedStartupTimeoutId = null;

Expand All @@ -130,6 +210,15 @@ var NonBrowserWindow = {
// the dock menu element to prevent leaks on shutdown
if (window.location.href == this.MAC_HIDDEN_WINDOW) {
this.dockSupport.dockMenu = null;

// Clean up Felt observer if still registered
if (AppConstants.MOZ_ENTERPRISE && this.feltReadyObserver) {
Services.obs.removeObserver(
this.feltReadyObserver,
"felt-firefox-window-ready"
);
this.feltReadyObserver = null;
}
}

// If nonBrowserWindowDelayedStartup hasn't run yet, we have no work to do -
Expand Down
122 changes: 78 additions & 44 deletions browser/components/BrowserContentHandler.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,9 @@ nsBrowserContentHandler.prototype = {

/* nsICommandLineHandler */
handle: function bch_handle(cmdLine) {
const isFeltUI = Services.felt.isFeltUI();
// XXX: The following flags are not forwarded to Firefox in Felt mode:
// --new-tab, --chrome, --search, --file, and Windows "? searchterm"
if (
cmdLine.handleFlag("kiosk", false) ||
cmdLine.handleFlagWithParam("kiosk-monitor", false)
Expand All @@ -469,7 +472,14 @@ nsBrowserContentHandler.prototype = {
Services.prefs.lockPref("browser.gesture.pinch.out.shift");
}
if (cmdLine.handleFlag("browser", false)) {
openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
if (isFeltUI) {
queueFeltURL({
url: "",
disposition: FELT_OPEN_WINDOW_DISPOSITION.NEW_WINDOW,
});
} else {
openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
}
cmdLine.preventDefault = true;
}

Expand All @@ -480,7 +490,14 @@ nsBrowserContentHandler.prototype = {
if (!shouldLoadURI(uri)) {
continue;
}
openBrowserWindow(cmdLine, principal, uri.spec);
if (isFeltUI) {
queueFeltURL({
url: uri.spec,
disposition: FELT_OPEN_WINDOW_DISPOSITION.NEW_WINDOW,
});
} else {
openBrowserWindow(cmdLine, principal, uri.spec);
}
cmdLine.preventDefault = true;
}
} catch (e) {
Expand Down Expand Up @@ -564,26 +581,36 @@ nsBrowserContentHandler.prototype = {
false
);
if (privateWindowParam) {
let forcePrivate = true;
let resolvedInfo;
if (!lazy.PrivateBrowsingUtils.enabled) {
// Load about:privatebrowsing in a normal tab, which will display an error indicating
// access to private browsing has been disabled.
forcePrivate = false;
resolvedInfo = {
uri: Services.io.newURI("about:privatebrowsing"),
principal: lazy.gSystemPrincipal,
};
if (isFeltUI) {
let resolvedInfo = resolveURIInternal(cmdLine, privateWindowParam);
if (shouldLoadURI(resolvedInfo.uri)) {
queueFeltURL({
url: resolvedInfo.uri.spec,
disposition: FELT_OPEN_WINDOW_DISPOSITION.NEW_PRIVATE_WINDOW,
});
}
} else {
resolvedInfo = resolveURIInternal(cmdLine, privateWindowParam);
let forcePrivate = true;
let resolvedInfo;
if (!lazy.PrivateBrowsingUtils.enabled) {
// Load about:privatebrowsing in a normal tab, which will display an error indicating
// access to private browsing has been disabled.
forcePrivate = false;
resolvedInfo = {
uri: Services.io.newURI("about:privatebrowsing"),
principal: lazy.gSystemPrincipal,
};
} else {
resolvedInfo = resolveURIInternal(cmdLine, privateWindowParam);
}
handURIToExistingBrowser(
resolvedInfo.uri,
Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
cmdLine,
forcePrivate,
resolvedInfo.principal
);
}
handURIToExistingBrowser(
resolvedInfo.uri,
Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
cmdLine,
forcePrivate,
resolvedInfo.principal
);
cmdLine.preventDefault = true;
}
} catch (e) {
Expand All @@ -592,13 +619,20 @@ nsBrowserContentHandler.prototype = {
}
// NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
if (cmdLine.handleFlag("private-window", false)) {
openBrowserWindow(
cmdLine,
lazy.gSystemPrincipal,
"about:privatebrowsing",
null,
lazy.PrivateBrowsingUtils.enabled
);
if (isFeltUI) {
queueFeltURL({
url: "",
disposition: FELT_OPEN_WINDOW_DISPOSITION.NEW_PRIVATE_WINDOW,
});
} else {
openBrowserWindow(
cmdLine,
lazy.gSystemPrincipal,
"about:privatebrowsing",
null,
lazy.PrivateBrowsingUtils.enabled
);
}
cmdLine.preventDefault = true;
}
}
Expand Down Expand Up @@ -1210,10 +1244,19 @@ nsBrowserContentHandler.prototype = {
};
var gBrowserContentHandler = new nsBrowserContentHandler();

// Module-level queue for Felt external link handling
// URLs are stored here when they arrive via command line (before Felt extension loads)
// FeltProcessParent imports this module and manages forwarding from this queue
// Re-export Felt URL handling from centralized module (MOZ_ENTERPRISE only)
export let gFeltPendingURLs = [];
export let FELT_OPEN_WINDOW_DISPOSITION = {};
export let queueFeltURL = () => {};

if (AppConstants.MOZ_ENTERPRISE) {
const FeltURLHandler = ChromeUtils.importESModule(
"resource:///modules/FeltURLHandler.sys.mjs"
);
gFeltPendingURLs = FeltURLHandler.gFeltPendingURLs;
FELT_OPEN_WINDOW_DISPOSITION = FeltURLHandler.FELT_OPEN_WINDOW_DISPOSITION;
queueFeltURL = FeltURLHandler.queueFeltURL;
}

function handURIToExistingBrowser(
uri,
Expand Down Expand Up @@ -1633,20 +1676,11 @@ nsDefaultCommandLineHandler.prototype = {
if (urilist.length) {
const urlSpecs = urilist.filter(shouldLoadURI).map(u => u.spec);
if (urlSpecs.length) {
// Try to import FeltProcessParent and call queueURL()
// This will work if the extension has loaded (chrome:// URLs registered)
// If not, URLs stay in gFeltPendingURLs for later retrieval
try {
const { queueURL } = ChromeUtils.importESModule(
"chrome://felt/content/FeltProcessParent.sys.mjs"
);
for (let urlSpec of urlSpecs) {
queueURL(urlSpec);
}
} catch (err) {
// Chrome:// URLs not registered yet (early startup)
// Add to queue for FeltProcessParent to retrieve later
gFeltPendingURLs.push(...urlSpecs);
for (let urlSpec of urlSpecs) {
queueFeltURL({
url: urlSpec,
disposition: FELT_OPEN_WINDOW_DISPOSITION.DEFAULT,
});
}
}
}
Expand Down
86 changes: 86 additions & 0 deletions browser/components/FeltURLHandler.sys.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "Cu", () => Components.utils);

export const FELT_OPEN_WINDOW_DISPOSITION = {
DEFAULT: 0,
NEW_WINDOW: 1,
NEW_PRIVATE_WINDOW: 2,
};

// Queue for Felt external link handling
// URL requests are stored here when they arrive via command line (before Felt extension loads)
// FeltProcessParent imports this module and manages forwarding from this queue
export let gFeltPendingURLs = [];

let lastNotificationShown = 0;

// Queue a URL to be opened in Firefox via Felt IPC.
// Note: We don't pass triggeringPrincipal because all command-line URLs use
// gSystemPrincipal (see resolveURIInternal), and the receiving Firefox side
// should use system principal for externally-triggered URLs.
export function queueFeltURL(payload) {
let isReady = false;
try {
const { queueURL, isFeltFirefoxReady } = ChromeUtils.importESModule(
"chrome://felt/content/FeltProcessParent.sys.mjs"
);
isReady = isFeltFirefoxReady();
queueURL(payload);
} catch {
console.warn(`Retrying to queue url ${payload.url} after initial failure.`);
gFeltPendingURLs.push(payload);
}

// On Linux (and as fallback for other platforms), show a notification
// if the action was queued before Firefox is ready
if (
!isReady &&
payload.disposition !== FELT_OPEN_WINDOW_DISPOSITION.DEFAULT
) {
showFeltPendingActionNotification();
}
}

function showFeltPendingActionNotification() {
try {
let now = lazy.Cu.now();
// Throttle notifications to avoid spam if user clicks multiple times
if (lastNotificationShown && now - lastNotificationShown < 5000) {
return;
}
lastNotificationShown = now;

let alertsService = Cc["@mozilla.org/alerts-service;1"]?.getService(
Ci.nsIAlertsService
);
if (!alertsService) {
return;
}

let alert = Cc["@mozilla.org/alert-notification;1"].createInstance(
Ci.nsIAlertNotification
);
alert.init(
"felt-pending-action",
"chrome://branding/content/icon64.png",
"Firefox",
"Please wait while Firefox starts...",
false,
"",
null,
null,
null,
null,
null,
false
);
alertsService.showAlert(alert);
} catch {
// Notification service may not be available on all platforms
}
}
3 changes: 3 additions & 0 deletions browser/components/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":

if CONFIG["MOZ_ENTERPRISE"]:
DIRS += ["enterprise"]
EXTRA_JS_MODULES += [
"FeltURLHandler.sys.mjs",
]

XPIDL_SOURCES += [
"nsIBrowserHandler.idl",
Expand Down
Loading