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
19 changes: 19 additions & 0 deletions browser/base/content/nonbrowser-mac.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,30 @@
* 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/. */

function queueFeltDockAction(isPrivate) {
const FELT_OPEN_DISPOSITION_NEW_WINDOW = 1;
const FELT_OPEN_DISPOSITION_NEW_PRIVATE_WINDOW = 2;
let payload = {
url: "",
disposition: isPrivate
? FELT_OPEN_DISPOSITION_NEW_PRIVATE_WINDOW
: FELT_OPEN_DISPOSITION_NEW_WINDOW,
};
const { queueFeltURL } = ChromeUtils.importESModule(
"resource:///modules/BrowserContentHandler.sys.mjs"
);
queueFeltURL(payload);
}

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

openBrowserWindowFromDockMenu(options = {}) {
if (Services.felt.isFeltUI()) {
queueFeltDockAction(options.private);
return null;
}
let existingWindow = BrowserWindowTracker.getTopWindow();
options.openerWindow = existingWindow || window;
let win = OpenBrowserWindow(options);
Expand Down
124 changes: 81 additions & 43 deletions browser/components/BrowserContentHandler.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ nsBrowserContentHandler.prototype = {

/* nsICommandLineHandler */
handle: function bch_handle(cmdLine) {
const isFeltUI = Services.felt.isFeltUI();
if (
cmdLine.handleFlag("kiosk", false) ||
cmdLine.handleFlagWithParam("kiosk-monitor", false)
Expand All @@ -469,7 +470,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_DISPOSITION_NEW_WINDOW,
});
} else {
openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
}
cmdLine.preventDefault = true;
}

Expand All @@ -480,7 +488,14 @@ nsBrowserContentHandler.prototype = {
if (!shouldLoadURI(uri)) {
continue;
}
openBrowserWindow(cmdLine, principal, uri.spec);
if (isFeltUI) {
queueFeltURL({
url: uri.spec,
disposition: FELT_OPEN_DISPOSITION_NEW_WINDOW,
});
} else {
openBrowserWindow(cmdLine, principal, uri.spec);
}
cmdLine.preventDefault = true;
}
} catch (e) {
Expand Down Expand Up @@ -564,26 +579,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_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 +617,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_DISPOSITION_NEW_PRIVATE_WINDOW,
});
} else {
openBrowserWindow(
cmdLine,
lazy.gSystemPrincipal,
"about:privatebrowsing",
null,
lazy.PrivateBrowsingUtils.enabled
);
}
cmdLine.preventDefault = true;
}
}
Expand Down Expand Up @@ -1211,9 +1243,23 @@ 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)
// 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 = [];
const FELT_OPEN_DISPOSITION_DEFAULT = 0;
const FELT_OPEN_DISPOSITION_NEW_WINDOW = 1;
const FELT_OPEN_DISPOSITION_NEW_PRIVATE_WINDOW = 2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's export these flags here and import them wherever needed (e.g. in browser/base/content/nonbrowser-mac.js and browser/extensions/felt/api.js). Maybe also wrap them in an enum?

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


export function queueFeltURL(payload) {
try {
const { queueURL } = ChromeUtils.importESModule(
"chrome://felt/content/FeltProcessParent.sys.mjs"
);
queueURL(payload);
} catch (err) {
gFeltPendingURLs.push(payload);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also leave a log message console.warn(`Retrying to queue url ${payload.url} after initial failure.`);

}
}

function handURIToExistingBrowser(
uri,
Expand Down Expand Up @@ -1498,6 +1544,7 @@ nsDefaultCommandLineHandler.prototype = {

/* nsICommandLineHandler */
handle: function dch_handle(cmdLine) {
const isFeltUI = Services.felt.isFeltUI();
var urilist = [];
var principalList = [];

Expand Down Expand Up @@ -1595,7 +1642,7 @@ nsDefaultCommandLineHandler.prototype = {

// Make sure that when FeltUI is requested, we do not try to open another
// window. Instead, forward any URLs to be opened in the real Firefox.
if (Services.felt.isFeltUI()) {
if (isFeltUI) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we delcare isFeltUI closer to its usage? Or actually, since it's only used once, we could generally drop the variable and call Services.felt.isFeltUI(); directly.

console.debug(`Felt: Found FeltUI in BrowserContentHandler.`);
cmdLine.preventDefault = true;

Expand Down Expand Up @@ -1633,20 +1680,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_DISPOSITION_DEFAULT,
});
}
}
}
Expand Down
47 changes: 46 additions & 1 deletion browser/extensions/felt/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
/* globals ExtensionAPI, Services, XPCOMUtils */

const lazy = {};
const FELT_OPEN_DISPOSITION_DEFAULT = 0;
const FELT_OPEN_DISPOSITION_NEW_WINDOW = 1;
const FELT_OPEN_DISPOSITION_NEW_PRIVATE_WINDOW = 2;

ChromeUtils.defineESModuleGetters(lazy, {
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
FeltStorage: "resource:///modules/FeltStorage.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

this.felt = class extends ExtensionAPI {
Expand Down Expand Up @@ -65,7 +69,17 @@ this.felt = class extends ExtensionAPI {
}
},

async _handleFeltExternalUrl(url) {
async _handleFeltExternalUrl(data) {
let { url, disposition } = this._parseOpenURLData(data);
if (
disposition === FELT_OPEN_DISPOSITION_NEW_WINDOW ||
disposition === FELT_OPEN_DISPOSITION_NEW_PRIVATE_WINDOW
) {
let wantsPrivate =
disposition === FELT_OPEN_DISPOSITION_NEW_PRIVATE_WINDOW;
this._openFeltWindow(url, wantsPrivate);
return;
}
let win = lazy.BrowserWindowTracker.getTopWindow({
private: false,
});
Expand Down Expand Up @@ -106,6 +120,37 @@ 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 ?? FELT_OPEN_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() {
Expand Down
29 changes: 19 additions & 10 deletions browser/extensions/felt/content/FeltProcessParent.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,30 @@ console.debug(`FeltExtension: FeltParentProcess.sys.mjs`);
// processing) and FeltProcessParent (which forwards URLs after Firefox is ready)
import { gFeltPendingURLs } from "resource:///modules/BrowserContentHandler.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,
};
}

/**
* Manages the SSO login and launching Firefox
*/
Expand Down Expand Up @@ -375,18 +382,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}`);
}
Expand Down Expand Up @@ -416,11 +424,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}`);
}
}

Expand Down
2 changes: 1 addition & 1 deletion browser/extensions/felt/rust/nsIFelt.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
10 changes: 7 additions & 3 deletions browser/extensions/felt/rust/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,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);
Expand Down
Loading