diff --git a/browser/base/content/nonbrowser-mac.js b/browser/base/content/nonbrowser-mac.js index 2a515b278bec..0a78eec1ff24 100644 --- a/browser/base/content/nonbrowser-mac.js +++ b/browser/base/content/nonbrowser-mac.js @@ -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); @@ -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; @@ -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 - diff --git a/browser/components/BrowserContentHandler.sys.mjs b/browser/components/BrowserContentHandler.sys.mjs index 6367f41adf42..9b8cf95be15a 100644 --- a/browser/components/BrowserContentHandler.sys.mjs +++ b/browser/components/BrowserContentHandler.sys.mjs @@ -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) @@ -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; } @@ -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) { @@ -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) { @@ -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; } } @@ -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, @@ -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, + }); } } } diff --git a/browser/components/FeltURLHandler.sys.mjs b/browser/components/FeltURLHandler.sys.mjs new file mode 100644 index 000000000000..5bc2f8d12a83 --- /dev/null +++ b/browser/components/FeltURLHandler.sys.mjs @@ -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 + } +} diff --git a/browser/components/moz.build b/browser/components/moz.build index a2d8b39044f3..3b95fe000ee5 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -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", diff --git a/browser/extensions/felt/api.js b/browser/extensions/felt/api.js index 163b39965a57..3889a3824256 100644 --- a/browser/extensions/felt/api.js +++ b/browser/extensions/felt/api.js @@ -9,8 +9,10 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { + FELT_OPEN_WINDOW_DISPOSITION: "resource:///modules/FeltURLHandler.sys.mjs", BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", FeltStorage: "resource:///modules/FeltStorage.sys.mjs", + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", }); this.felt = class extends ExtensionAPI { @@ -65,7 +67,17 @@ this.felt = class extends ExtensionAPI { } }, - async _handleFeltExternalUrl(url) { + async _handleFeltExternalUrl(data) { + let { url, disposition } = this._parseOpenURLData(data); + if ( + disposition === lazy.FELT_OPEN_WINDOW_DISPOSITION.NEW_WINDOW || + disposition === lazy.FELT_OPEN_WINDOW_DISPOSITION.NEW_PRIVATE_WINDOW + ) { + let wantsPrivate = + disposition === lazy.FELT_OPEN_WINDOW_DISPOSITION.NEW_PRIVATE_WINDOW; + this._openFeltWindow(url, wantsPrivate); + return; + } let win = lazy.BrowserWindowTracker.getTopWindow({ private: false, }); @@ -106,6 +118,38 @@ this.felt = class extends ExtensionAPI { console.error("FeltExtension: Failed to open forwarded URL", url, err); } }, + + _parseOpenURLData(data) { + let parsed = JSON.parse(data); + return { + url: parsed.url ?? "", + disposition: + parsed.disposition ?? lazy.FELT_OPEN_WINDOW_DISPOSITION.DEFAULT, + }; + }, + + _openFeltWindow(url, wantsPrivate) { + if (wantsPrivate && !lazy.PrivateBrowsingUtils.enabled) { + wantsPrivate = false; + url = "about:privatebrowsing"; + } + + try { + let args = null; + if (url) { + args = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + args.data = url; + } + lazy.BrowserWindowTracker.openWindow({ + private: wantsPrivate, + args, + }); + } catch (err) { + console.error("FeltExtension: Failed to open forwarded window", err); + } + }, }; async onStartup() { diff --git a/browser/extensions/felt/content/FeltProcessParent.sys.mjs b/browser/extensions/felt/content/FeltProcessParent.sys.mjs index c4db6711eb2e..7520be2e52c8 100644 --- a/browser/extensions/felt/content/FeltProcessParent.sys.mjs +++ b/browser/extensions/felt/content/FeltProcessParent.sys.mjs @@ -15,28 +15,54 @@ ChromeUtils.defineESModuleGetters(lazy, { console.debug(`FeltExtension: FeltParentProcess.sys.mjs`); -// Import the shared pending URLs queue from BrowserContentHandler -// This queue is shared between BrowserContentHandler (which fills it early during command-line -// processing) and FeltProcessParent (which forwards URLs after Firefox is ready) -import { gFeltPendingURLs } from "resource:///modules/BrowserContentHandler.sys.mjs"; +// Import the shared pending URLs queue +import { gFeltPendingURLs } from "resource:///modules/FeltURLHandler.sys.mjs"; -export function queueURL(url) { +export function queueURL(payload) { // If Firefox AND extension are both ready, forward immediately if ( gFeltProcessParentInstance?.firefoxReady && gFeltProcessParentInstance?.extensionReady ) { - gFeltProcessParentInstance.sendURLToFirefox(url); + gFeltProcessParentInstance.sendURLToFirefox(payload); // Ensure Felt launcher stays hidden when forwarding to running Firefox Services.felt.makeBackgroundProcess(true); } else { // Queue at module level until ready - gFeltPendingURLs.push(url); + gFeltPendingURLs.push(payload); } } let gFeltProcessParentInstance = null; +function extractURLPayload(payload) { + return { + url: payload.url ?? "", + disposition: payload.disposition ?? 0, + }; +} + +let gFeltFirefoxReadyNotified = false; + +export function isFeltFirefoxReady() { + return ( + gFeltProcessParentInstance?.firefoxReady && + gFeltProcessParentInstance?.extensionReady + ); +} + +function notifyFirefoxReady() { + if (gFeltFirefoxReadyNotified) { + return; + } + if (!isFeltFirefoxReady()) { + return; + } + gFeltFirefoxReadyNotified = true; + console.debug("FeltExtension: Notifying felt-firefox-window-ready"); + Services.obs.notifyObservers(null, "felt-firefox-window-ready"); +} + /** * Manages the SSO login and launching Firefox */ @@ -130,6 +156,7 @@ export class FeltProcessParent extends JSProcessActorParent { if (gFeltProcessParentInstance) { gFeltProcessParentInstance.extensionReady = true; gFeltProcessParentInstance.forwardPendingURLs(); + notifyFirefoxReady(); } break; } @@ -223,6 +250,7 @@ export class FeltProcessParent extends JSProcessActorParent { this.logoutReported = false; this.firefoxReady = false; this.extensionReady = false; + gFeltFirefoxReadyNotified = false; Services.cpmm.sendAsyncMessage("FeltParent:FirefoxStarting", {}); this.firefox = this.startFirefoxProcess(); this.firefox @@ -259,6 +287,7 @@ export class FeltProcessParent extends JSProcessActorParent { // Try to forward pending URLs now (will only forward if extension is also ready) this.forwardPendingURLs(); + notifyFirefoxReady(); }) .then(() => { console.debug( @@ -439,18 +468,19 @@ export class FeltProcessParent extends JSProcessActorParent { } /** - * Send a URL to Firefox via IPC (Firefox must be ready) + * Send a URL request to Firefox via IPC (Firefox must be ready) * - * @param {string} url + * @param {object} payload - Object with url and disposition properties */ - sendURLToFirefox(url) { + sendURLToFirefox(payload) { if (!this.firefoxReady || !Services.felt) { console.error(`FeltExtension: Cannot send URL, Firefox not ready`); return; } try { - Services.felt.openURL(url); + let { url, disposition } = extractURLPayload(payload); + Services.felt.openURL(url, disposition); } catch (err) { console.error(`FeltExtension: Failed to forward URL: ${err}`); } @@ -480,11 +510,12 @@ export class FeltProcessParent extends JSProcessActorParent { } // Forward all URLs directly via IPC (both Firefox and extension are ready) - for (const url of gFeltPendingURLs) { + for (const payload of gFeltPendingURLs) { try { - Services.felt.openURL(url); + let { url, disposition } = extractURLPayload(payload); + Services.felt.openURL(url, disposition); } catch (err) { - console.error(`FeltExtension: Failed to forward URL ${url}: ${err}`); + console.error(`FeltExtension: Failed to forward URL: ${err}`); } } diff --git a/browser/extensions/felt/rust/nsIFelt.idl b/browser/extensions/felt/rust/nsIFelt.idl index b41dd01c964d..57135afe14fc 100644 --- a/browser/extensions/felt/rust/nsIFelt.idl +++ b/browser/extensions/felt/rust/nsIFelt.idl @@ -63,7 +63,7 @@ interface nsIFelt : nsISupports { void sendExtensionReady(); [must_use] - void openURL(in ACString url); + void openURL(in ACString url, in long disposition); [must_use] ACString getConsoleUrl(); diff --git a/browser/extensions/felt/rust/src/client.rs b/browser/extensions/felt/rust/src/client.rs index a2c8bb5e3828..83471a563e25 100644 --- a/browser/extensions/felt/rust/src/client.rs +++ b/browser/extensions/felt/rust/src/client.rs @@ -368,9 +368,13 @@ impl FeltClientThread { trace!("FeltClientThread::felt_client::ipc_loop(): ERROR setting RefreshToken({})", refresh_token); } } - Ok(FeltMessage::OpenURL(url)) => { - trace!("FeltClientThread::felt_client::ipc_loop(): OpenURL({})", url); - utils::open_url_in_firefox(url); + Ok(FeltMessage::OpenURL((url, disposition))) => { + trace!( + "FeltClientThread::felt_client::ipc_loop(): OpenURL({}, {})", + url, + disposition + ); + utils::open_url_in_firefox(url, disposition); }, Ok(msg) => { trace!("FeltClientThread::felt_client::ipc_loop(): UNEXPECTED MSG {:?}", msg); diff --git a/browser/extensions/felt/rust/src/components.rs b/browser/extensions/felt/rust/src/components.rs index e4c62846673a..83c9961c9292 100644 --- a/browser/extensions/felt/rust/src/components.rs +++ b/browser/extensions/felt/rust/src/components.rs @@ -192,10 +192,10 @@ impl FeltXPCOM { } } - fn OpenURL(&self, url: *const nsACString) -> nserror::nsresult { + fn OpenURL(&self, url: *const nsACString, disposition: i32) -> nserror::nsresult { let url_s = unsafe { (*url).to_string() }; - trace!("FeltXPCOM::OpenURL: {}", url_s); - self.send(FeltMessage::OpenURL(url_s)) + trace!("FeltXPCOM::OpenURL: {} {}", url_s, disposition); + self.send(FeltMessage::OpenURL((url_s, disposition))) } fn GetConsoleUrl(&self, console_url: *mut nsACString) -> nserror::nsresult { diff --git a/browser/extensions/felt/rust/src/message.rs b/browser/extensions/felt/rust/src/message.rs index df7cb4f30a5b..e6ba0089e5fe 100644 --- a/browser/extensions/felt/rust/src/message.rs +++ b/browser/extensions/felt/rust/src/message.rs @@ -56,10 +56,10 @@ pub enum FeltMessage { StartupReady, Tokens((String, String, i64)), ExtensionReady, - OpenURL(String), + OpenURL((String, i32)), RestartForced, Restarting, LogoutShutdown, } -pub const FELT_IPC_VERSION: u32 = 3; +pub const FELT_IPC_VERSION: u32 = 4; diff --git a/browser/extensions/felt/rust/src/utils.rs b/browser/extensions/felt/rust/src/utils.rs index 4371aff9906c..1d339c0b8015 100644 --- a/browser/extensions/felt/rust/src/utils.rs +++ b/browser/extensions/felt/rust/src/utils.rs @@ -164,20 +164,24 @@ pub fn notify_observers(name: String) { }); } -pub fn open_url_in_firefox(url: String) { - trace!("open_url_in_firefox() url: {}", url); +pub fn open_url_in_firefox(url: String, disposition: i32) { + trace!( + "open_url_in_firefox() url: {} disposition: {}", + url, + disposition + ); + let payload = format!(r#"{{"url":"{}","disposition":{}}}"#, url, disposition); do_main_thread("felt_open_url", async move { let obssvc: RefPtr = xpcom::components::Observer::service().unwrap(); let topic = CString::new("felt-open-url").unwrap(); - let url_data = nsstring::nsString::from(&url); + let url_data = nsstring::nsString::from(&payload); let rv = unsafe { obssvc.NotifyObservers(std::ptr::null(), topic.as_ptr(), url_data.as_ptr()) }; if rv.succeeded() { trace!( - "open_url_in_firefox() successfully sent observer notification for URL: {}", - url + "open_url_in_firefox() successfully sent observer notification for URL request" ); } else { trace!("open_url_in_firefox() NotifyObservers failed: {:?}", rv); diff --git a/browser/modules/WindowsJumpLists.sys.mjs b/browser/modules/WindowsJumpLists.sys.mjs index 6d1ed02c7851..ab273c5abf74 100644 --- a/browser/modules/WindowsJumpLists.sys.mjs +++ b/browser/modules/WindowsJumpLists.sys.mjs @@ -359,6 +359,41 @@ export var WinTaskbarJumpList = { if (this._blocked) { this._builder._deleteActiveJumpList(); } + + // In Felt mode, block jump list until Firefox is ready + if (Services.felt?.isFeltUI()) { + this._setupFeltBlocking(); + } + }, + + _setupFeltBlocking: function WTBJL__setupFeltBlocking() { + // Check if Firefox is already ready + let isReady = false; + try { + const { isFeltFirefoxReady } = ChromeUtils.importESModule( + "chrome://felt/content/FeltProcessParent.sys.mjs" + ); + isReady = isFeltFirefoxReady(); + } catch { + // Extension not loaded yet + } + + if (isReady) { + return; + } + + // Create a promise that resolves when felt-firefox-window-ready fires + let feltReadyPromise = new Promise(resolve => { + let observer = { + observe: () => { + Services.obs.removeObserver(observer, "felt-firefox-window-ready"); + resolve(); + }, + }; + Services.obs.addObserver(observer, "felt-firefox-window-ready"); + }); + + this.blockJumpList(feltReadyPromise); }, update: function WTBJL_update() {