Bug 1880368 - change firefox and firefox-private protocols to firefox-bridge and firefox-private-bridge protocols r=nshukla,mossop

Differential Revision: https://phabricator.services.mozilla.com/D201879
This commit is contained in:
Michael Hughes 2024-02-29 19:03:17 +00:00
parent 98aecb3ad4
commit 09024f2525
13 changed files with 694 additions and 195 deletions

View file

@ -237,7 +237,7 @@
<string>Firefox Protocol</string> <string>Firefox Protocol</string>
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>firefox</string> <string>firefox-bridge</string>
</array> </array>
</dict> </dict>
<dict> <dict>
@ -245,7 +245,7 @@
<string>Firefox Private Browsing Protocol</string> <string>Firefox Private Browsing Protocol</string>
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>firefox-private</string> <string>firefox-private-bridge</string>
</array> </array>
</dict> </dict>
</array> </array>

View file

@ -298,9 +298,6 @@ pref("browser.shell.checkDefaultPDF.silencedByUser", false);
// URL to navigate to when launching Firefox after accepting the Windows Default // URL to navigate to when launching Firefox after accepting the Windows Default
// Browser Agent "Set Firefox as default" call to action. // Browser Agent "Set Firefox as default" call to action.
pref("browser.shell.defaultBrowserAgent.thanksURL", "https://www.mozilla.org/%LOCALE%/firefox/set-as-default/thanks/"); pref("browser.shell.defaultBrowserAgent.thanksURL", "https://www.mozilla.org/%LOCALE%/firefox/set-as-default/thanks/");
// Whether or not we want to run through the early startup idle task
// which registers the firefox and firefox-private registry keys.
pref("browser.shell.customProtocolsRegistered", false);
#endif #endif

View file

@ -90,7 +90,7 @@ function resolveURIInternal(
if (aArgument.startsWith(protocolWithColon)) { if (aArgument.startsWith(protocolWithColon)) {
if (!validateFirefoxProtocol(aCmdLine, launchedWithArg_osint)) { if (!validateFirefoxProtocol(aCmdLine, launchedWithArg_osint)) {
throw new Error( throw new Error(
"Invalid use of Firefox and Firefox-private protocols." "Invalid use of Firefox-bridge and Firefox-private-bridge protocols."
); );
} }
aArgument = aArgument.substring(protocolWithColon.length); aArgument = aArgument.substring(protocolWithColon.length);
@ -100,7 +100,7 @@ function resolveURIInternal(
!aArgument.startsWith("https://") !aArgument.startsWith("https://")
) { ) {
throw new Error( throw new Error(
"Firefox and Firefox-private protocols can only be used in conjunction with http and https urls." "Firefox-bridge and Firefox-private-bridge protocols can only be used in conjunction with http and https urls."
); );
} }
@ -113,8 +113,8 @@ function resolveURIInternal(
} }
}; };
handleFirefoxProtocol("firefox"); handleFirefoxProtocol("firefox-bridge");
handleFirefoxProtocol("firefox-private"); handleFirefoxProtocol("firefox-private-bridge");
var uri = aCmdLine.resolveURI(aArgument); var uri = aCmdLine.resolveURI(aArgument);
var uriFixup = Services.uriFixup; var uriFixup = Services.uriFixup;
@ -599,7 +599,7 @@ nsBrowserContentHandler.prototype = {
if (urlFlagIdx > -1 && cmdLine.length > 1) { if (urlFlagIdx > -1 && cmdLine.length > 1) {
url = cmdLine.getArgument(urlFlagIdx + 1); url = cmdLine.getArgument(urlFlagIdx + 1);
} }
if (privateWindowParam || url?.startsWith("firefox-private:")) { if (privateWindowParam || url?.startsWith("firefox-private-bridge:")) {
// Check if the osint flag is present on Windows // Check if the osint flag is present on Windows
let launchedWithArg_osint = let launchedWithArg_osint =
AppConstants.platform == "win" && AppConstants.platform == "win" &&
@ -614,7 +614,7 @@ nsBrowserContentHandler.prototype = {
uri: Services.io.newURI("about:privatebrowsing"), uri: Services.io.newURI("about:privatebrowsing"),
principal: lazy.gSystemPrincipal, principal: lazy.gSystemPrincipal,
}; };
} else if (url?.startsWith("firefox-private:")) { } else if (url?.startsWith("firefox-private-bridge:")) {
cmdLine.removeArguments(urlFlagIdx, urlFlagIdx + 1); cmdLine.removeArguments(urlFlagIdx, urlFlagIdx + 1);
resolvedInfo = resolveURIInternal( resolvedInfo = resolveURIInternal(
cmdLine, cmdLine,
@ -1463,7 +1463,7 @@ nsDefaultCommandLineHandler.prototype = {
} }
// Can't open multiple URLs without using system principal. // Can't open multiple URLs without using system principal.
// The firefox and firefox-private protocols should only // The firefox-bridge and firefox-private-bridge protocols should only
// accept a single URL due to using the -osint option // accept a single URL due to using the -osint option
// so this isn't very relevant. // so this isn't very relevant.
var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec); var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);

View file

@ -38,6 +38,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs", E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
ExtensionsUI: "resource:///modules/ExtensionsUI.sys.mjs", ExtensionsUI: "resource:///modules/ExtensionsUI.sys.mjs",
FeatureGate: "resource://featuregates/FeatureGate.sys.mjs", FeatureGate: "resource://featuregates/FeatureGate.sys.mjs",
FirefoxBridgeExtensionUtils:
"resource:///modules/FirefoxBridgeExtensionUtils.sys.mjs",
FxAccounts: "resource://gre/modules/FxAccounts.sys.mjs", FxAccounts: "resource://gre/modules/FxAccounts.sys.mjs",
HomePage: "resource:///modules/HomePage.sys.mjs", HomePage: "resource:///modules/HomePage.sys.mjs",
Integration: "resource://gre/modules/Integration.sys.mjs", Integration: "resource://gre/modules/Integration.sys.mjs",
@ -2641,126 +2643,6 @@ BrowserGlue.prototype = {
}, },
}, },
{
name: "dualBrowserProtocolHandler",
condition:
AppConstants.platform == "win" &&
!Services.prefs.getBoolPref(
"browser.shell.customProtocolsRegistered"
),
task: async () => {
Services.prefs.setBoolPref(
"browser.shell.customProtocolsRegistered",
true
);
const FIREFOX_HANDLER_NAME = "firefox";
const FIREFOX_PRIVATE_HANDLER_NAME = "firefox-private";
const path = Services.dirsvc.get("XREExeF", Ci.nsIFile).path;
let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
Ci.nsIWindowsRegKey
);
try {
wrk.open(wrk.ROOT_KEY_CLASSES_ROOT, "", wrk.ACCESS_READ);
let FxSet = wrk.hasChild(FIREFOX_HANDLER_NAME);
let FxPrivateSet = wrk.hasChild(FIREFOX_PRIVATE_HANDLER_NAME);
wrk.close();
if (FxSet && FxPrivateSet) {
return;
}
wrk.open(
wrk.ROOT_KEY_CURRENT_USER,
"Software\\Classes",
wrk.ACCESS_ALL
);
const maybeUpdateRegistry = (
isSetAlready,
handler,
protocolName
) => {
if (isSetAlready) {
return;
}
let FxKey = wrk.createChild(handler, wrk.ACCESS_ALL);
try {
// Write URL protocol key
FxKey.writeStringValue("", protocolName);
FxKey.writeStringValue("URL Protocol", "");
FxKey.close();
// Write defaultIcon key
FxKey.create(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\DefaultIcon",
FxKey.ACCESS_ALL
);
FxKey.open(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\DefaultIcon",
FxKey.ACCESS_ALL
);
FxKey.writeStringValue("", `\"${path}\",1`);
FxKey.close();
// Write shell\\open\\command key
FxKey.create(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\shell",
FxKey.ACCESS_ALL
);
FxKey.create(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\shell\\open",
FxKey.ACCESS_ALL
);
FxKey.create(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\shell\\open\\command",
FxKey.ACCESS_ALL
);
FxKey.open(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\shell\\open\\command",
FxKey.ACCESS_ALL
);
if (handler == FIREFOX_PRIVATE_HANDLER_NAME) {
FxKey.writeStringValue(
"",
`\"${path}\" -osint -private-window \"%1\"`
);
} else {
FxKey.writeStringValue("", `\"${path}\" -osint -url \"%1\"`);
}
} catch (ex) {
console.log(ex);
} finally {
FxKey.close();
}
};
try {
maybeUpdateRegistry(
FxSet,
FIREFOX_HANDLER_NAME,
"URL:Firefox Protocol"
);
} catch (ex) {
console.log(ex);
}
try {
maybeUpdateRegistry(
FxPrivateSet,
FIREFOX_PRIVATE_HANDLER_NAME,
"URL:Firefox Private Browsing Protocol"
);
} catch (ex) {
console.log(ex);
}
} catch (ex) {
console.log(ex);
} finally {
wrk.close();
}
},
timeout: 5000,
},
// Ensure a Private Browsing Shortcut exists. This is needed in case // Ensure a Private Browsing Shortcut exists. This is needed in case
// a user tries to use Windows functionality to pin our Private Browsing // a user tries to use Windows functionality to pin our Private Browsing
// mode icon to the Taskbar (eg: the "Pin to Taskbar" context menu item). // mode icon to the Taskbar (eg: the "Pin to Taskbar" context menu item).
@ -3821,13 +3703,20 @@ BrowserGlue.prototype = {
_migrateUI() { _migrateUI() {
// Use an increasing number to keep track of the current migration state. // Use an increasing number to keep track of the current migration state.
// Completely unrelated to the current Firefox release number. // Completely unrelated to the current Firefox release number.
const UI_VERSION = 142; const UI_VERSION = 143;
const BROWSER_DOCURL = AppConstants.BROWSER_CHROME_URL; const BROWSER_DOCURL = AppConstants.BROWSER_CHROME_URL;
if (!Services.prefs.prefHasUserValue("browser.migration.version")) { if (!Services.prefs.prefHasUserValue("browser.migration.version")) {
// This is a new profile, nothing to migrate. // This is a new profile, nothing to migrate.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION); Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
this._isNewProfile = true; this._isNewProfile = true;
if (AppConstants.platform == "win") {
// Ensure that the Firefox Bridge protocols are registered for the new profile.
// No-op if they are registered for the user or the local machine already.
lazy.FirefoxBridgeExtensionUtils.maybeRegisterFirefoxBridgeProtocols();
}
return; return;
} }
@ -4427,6 +4316,27 @@ BrowserGlue.prototype = {
} }
} }
if (currentUIVersion < 143) {
if (AppConstants.platform == "win") {
// In Firefox 122, we enabled the firefox and firefox-private protocols.
// We switched over to using firefox-bridge and firefox-private-bridge,
// but we want to clean up the use of the other protocols.
lazy.FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries();
// Register the new firefox bridge related protocols now
lazy.FirefoxBridgeExtensionUtils.maybeRegisterFirefoxBridgeProtocols();
// Clean up the old user prefs from FX 122
Services.prefs.clearUserPref(
"network.protocol-handler.external.firefox"
);
Services.prefs.clearUserPref(
"network.protocol-handler.external.firefox-private"
);
Services.prefs.clearUserPref("browser.shell.customProtocolsRegistered");
}
}
// Update the migration version. // Update the migration version.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION); Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
}, },

View file

@ -92,14 +92,14 @@
</uap3:Protocol> </uap3:Protocol>
</uap3:Extension> </uap3:Extension>
<uap3:Extension Category="windows.protocol"> <uap3:Extension Category="windows.protocol">
<uap3:Protocol Name="firefox" Parameters="-osint -url &quot;%1&quot;"> <uap3:Protocol Name="firefox-bridge" Parameters="-osint -url &quot;%1&quot;">
<uap:DisplayName>Firefox Protocol</uap:DisplayName> <uap:DisplayName>Firefox Bridge Protocol</uap:DisplayName>
<uap:Logo>Assets\Document44x44.png</uap:Logo> <uap:Logo>Assets\Document44x44.png</uap:Logo>
</uap3:Protocol> </uap3:Protocol>
</uap3:Extension> </uap3:Extension>
<uap3:Extension Category="windows.protocol"> <uap3:Extension Category="windows.protocol">
<uap3:Protocol Name="firefox-private" Parameters="-osint -private-window &quot;%1&quot;"> <uap3:Protocol Name="firefox-private-bridge" Parameters="-osint -private-window &quot;%1&quot;">
<uap:DisplayName>Firefox Private Browsing Protocol</uap:DisplayName> <uap:DisplayName>Firefox Private Bridge Protocol</uap:DisplayName>
<uap:Logo>Assets\Document44x44.png</uap:Logo> <uap:Logo>Assets\Document44x44.png</uap:Logo>
</uap3:Protocol> </uap3:Protocol>
</uap3:Extension> </uap3:Extension>

View file

@ -474,22 +474,22 @@ Section "-Application" APP_IDX
${AddDisabledDDEHandlerValues} "FirefoxURL-$AppUserModelID" "$2" "$8,${IDI_DOCUMENT_ZERO_BASED}" \ ${AddDisabledDDEHandlerValues} "FirefoxURL-$AppUserModelID" "$2" "$8,${IDI_DOCUMENT_ZERO_BASED}" \
"${AppRegName} URL" "true" "${AppRegName} URL" "true"
; Create protocol registry keys for dual browser extensions - only if not already set ; Create protocol registry keys for FirefoxBridge extensions - only if not already set
SetShellVarContext current ; Set SHCTX to HKCU SetShellVarContext current ; Set SHCTX to HKCU
!define FIREFOX_PROTOCOL "firefox" !define FIREFOX_PROTOCOL "firefox-bridge"
ClearErrors ClearErrors
ReadRegStr $0 SHCTX "Software\Classes\${FIREFOX_PROTOCOL}" "" ReadRegStr $0 SHCTX "Software\Classes\${FIREFOX_PROTOCOL}" ""
${If} $0 == "" ${If} $0 == ""
${AddDisabledDDEHandlerValues} "${FIREFOX_PROTOCOL}" "$2" "$8,${IDI_APPICON_ZERO_BASED}" \ ${AddDisabledDDEHandlerValues} "${FIREFOX_PROTOCOL}" "$2" "$8,${IDI_APPICON_ZERO_BASED}" \
"Firefox Browsing Protocol" "true" "Firefox Bridge Protocol" "true"
${EndIf} ${EndIf}
!define FIREFOX_PRIVATE_PROTOCOL "firefox-private" !define FIREFOX_PRIVATE_PROTOCOL "firefox-private-bridge"
ClearErrors ClearErrors
ReadRegStr $0 SHCTX "Software\Classes\${FIREFOX_PRIVATE_PROTOCOL}" "" ReadRegStr $0 SHCTX "Software\Classes\${FIREFOX_PRIVATE_PROTOCOL}" ""
${If} $0 == "" ${If} $0 == ""
${AddDisabledDDEHandlerValues} "${FIREFOX_PRIVATE_PROTOCOL}" "$\"$8$\" -osint -private-window $\"%1$\"" \ ${AddDisabledDDEHandlerValues} "${FIREFOX_PRIVATE_PROTOCOL}" "$\"$8$\" -osint -private-window $\"%1$\"" \
"$8,${IDI_PBICON_PB_EXE_ZERO_BASED}" "Firefox Private Browsing Protocol" "true" "$8,${IDI_PBICON_PB_EXE_ZERO_BASED}" "Firefox Private Bridge Protocol" "true"
${EndIf} ${EndIf}
SetShellVarContext all ; Set SHCTX to HKLM SetShellVarContext all ; Set SHCTX to HKLM
@ -794,6 +794,15 @@ Section "-InstallEndCleanup"
; Refresh desktop icons ; Refresh desktop icons
${RefreshShellIcons} ${RefreshShellIcons}
; Remove old unsupported firefox and firefox-private extension protocol
; handlers which were added in FX122 for the dual browser extension, since
; renamed to FirefoxBridge
Push $1
${GetLongPath} "$INSTDIR\${FileMainEXE}" $1
${DeleteProtocolRegistryIfSetToInstallation} "$1" "firefox"
${DeleteProtocolRegistryIfSetToInstallation} "$1" "firefox-private"
Pop $1
${InstallEndCleanupCommon} ${InstallEndCleanupCommon}
${If} $PreventRebootRequired == "true" ${If} $PreventRebootRequired == "true"

View file

@ -1824,3 +1824,49 @@ FunctionEnd
${WriteRegStr2} ${RegKey} "Software\Classes\CLSID\$0\InProcServer32" "" "$INSTDIR\notificationserver.dll" 0 ${WriteRegStr2} ${RegKey} "Software\Classes\CLSID\$0\InProcServer32" "" "$INSTDIR\notificationserver.dll" 0
!macroend !macroend
!define WriteToastNotificationRegistration "!insertmacro WriteToastNotificationRegistration" !define WriteToastNotificationRegistration "!insertmacro WriteToastNotificationRegistration"
/**
* Deletes the registry keys for a protocol handler but only if those registry
* keys were pointed to the installation being uninstalled.
* Does this with both the HKLM and the HKCU registry entries.
*
* @param _PROTOCOL
* The protocol to delete the registry keys for
*/
!macro DeleteProtocolRegistryIfSetToInstallation INSTALL_PATH _PROTOCOL
Push $0
; Check if there is a protocol handler registered by fetching the DefaultIcon value
; in the registry.
; If there is something registered for the icon, it will be the path to the executable,
; plus a comma and a number for the id of the resource for the icon.
; Use StrCpy with -2 to remove the comma and the resource id so that
; the whole path to the executable can be compared against what's being
; uninstalled.
; Do all of that twice, once for the local machine and once for the current user
; Remove protocol handlers
ClearErrors
ReadRegStr $0 HKLM "Software\Classes\${_PROTOCOL}\DefaultIcon" ""
${If} $0 != ""
StrCpy $0 $0 -2
${If} $0 == '"${INSTALL_PATH}"'
DeleteRegKey HKLM "Software\Classes\${_PROTOCOL}"
${EndIf}
${EndIf}
ClearErrors
ReadRegStr $0 HKCU "Software\Classes\${_PROTOCOL}\DefaultIcon" ""
${If} $0 != ""
StrCpy $0 $0 -2
${If} $0 == '"${INSTALL_PATH}"'
DeleteRegKey HKCU "Software\Classes\${_PROTOCOL}"
${EndIf}
${EndIf}
ClearErrors
Pop $0
!macroend
!define DeleteProtocolRegistryIfSetToInstallation '!insertmacro DeleteProtocolRegistryIfSetToInstallation'

View file

@ -411,54 +411,6 @@ SectionEnd
################################################################################ ################################################################################
# Uninstall Sections # Uninstall Sections
/**
* Deletes the registry keys for a protocol handler but only if those registry
* keys were pointed to the installation being uninstalled.
* Does this with both the HKLM and the HKCU registry entries.
*
* @param _PROTOCOL
* The protocol to delete the registry keys for
*/
!macro DeleteProtocolRegistryIfSetToInstallation _PROTOCOL
Push $0
Push $1
; Check if there is a protocol handler registered by fetching the DefaultIcon value
; in the registry.
; If there is something registered for the icon, it will be the path to the executable,
; plus a comma and a number for the id of the resource for the icon.
; Use StrCpy with -2 to remove the comma and the resource id so that
; the whole path to the executable can be compared against what's being
; uninstalled.
; Do all of that twice, once for the local machine and once for the current user
; Remove protocol handlers
ClearErrors
${un.GetLongPath} "$INSTDIR\${FileMainEXE}" $1
ReadRegStr $0 HKLM "Software\Classes\${_PROTOCOL}\DefaultIcon" ""
${If} $0 != ""
StrCpy $0 $0 -2
${If} $0 == $1
DeleteRegKey HKLM "Software\Classes\${_PROTOCOL}"
${EndIf}
${EndIf}
ClearErrors
ReadRegStr $0 HKCU "Software\Classes\${_PROTOCOL}\DefaultIcon" ""
${If} $0 != ""
StrCpy $0 $0 -2
${If} $0 == $1
DeleteRegKey HKCU "Software\Classes\${_PROTOCOL}"
${EndIf}
${EndIf}
ClearErrors
Pop $0
Pop $1
!macroend
!define DeleteProtocolRegistryIfSetToInstallation '!insertmacro DeleteProtocolRegistryIfSetToInstallation'
Section "Uninstall" Section "Uninstall"
SetDetailsPrint textonly SetDetailsPrint textonly
DetailPrint $(STATUS_UNINSTALL_MAIN) DetailPrint $(STATUS_UNINSTALL_MAIN)
@ -571,9 +523,12 @@ Section "Uninstall"
; Clean up "launch on login" registry key for this installation. ; Clean up "launch on login" registry key for this installation.
DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "Mozilla-${AppName}-$AppUserModelID" DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "Mozilla-${AppName}-$AppUserModelID"
; Remove dual browser extension protocol handlers ; Remove FirefoxBridge extension protocol handlers
${DeleteProtocolRegistryIfSetToInstallation} "firefox" Push $1
${DeleteProtocolRegistryIfSetToInstallation} "firefox-private" ${un.GetLongPath} "$INSTDIR\${FileMainEXE}" $1
${DeleteProtocolRegistryIfSetToInstallation} "$1" "firefox-bridge"
${DeleteProtocolRegistryIfSetToInstallation} "$1" "firefox-private-bridge"
Pop $1
; Remove old protocol handler and StartMenuInternet keys without install path ; Remove old protocol handler and StartMenuInternet keys without install path
; hashes, but only if they're for this installation. We've never supported ; hashes, but only if they're for this installation. We've never supported

View file

@ -0,0 +1,222 @@
/* 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/. */
/**
* Default implementation of the helper class to assist in deleting the firefox protocols.
* See maybeDeleteBridgeProtocolRegistryEntries for more info.
*/
class DeleteBridgeProtocolRegistryEntryHelperImplementation {
getApplicationPath() {
return Services.dirsvc.get("XREExeF", Ci.nsIFile).path;
}
openRegistryRoot() {
const wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
Ci.nsIWindowsRegKey
);
wrk.open(wrk.ROOT_KEY_CURRENT_USER, "Software\\Classes", wrk.ACCESS_ALL);
return wrk;
}
deleteChildren(start) {
// Recursively delete all of the children of the children
// Go through the list in reverse order, so that shrinking
// the list doesn't rearrange things while iterating
for (let i = start.childCount; i > 0; i--) {
const childName = start.getChildName(i - 1);
const child = start.openChild(childName, start.ACCESS_ALL);
this.deleteChildren(child);
child.close();
start.removeChild(childName);
}
}
deleteRegistryTree(root, toDeletePath) {
var start = root.openChild(toDeletePath, root.ACCESS_ALL);
this.deleteChildren(start);
start.close();
root.removeChild(toDeletePath);
}
}
export const FirefoxBridgeExtensionUtils = {
/**
* In Firefox 122, we enabled the firefox and firefox-private protocols.
* We switched over to using firefox-bridge and firefox-private-bridge,
*
* but we want to clean up the use of the other protocols.
*
* deleteBridgeProtocolRegistryEntryHelper handles everything outside of the logic needed for
* this method so that the logic in maybeDeleteBridgeProtocolRegistryEntries can be unit tested
*
* We only delete the entries for the firefox and firefox-private protocols if
* they were set up to use this install and in the format that Firefox installed
* them with. If the entries are changed in any way, it is assumed that the user
* mucked with them manually and knows what they are doing.
*/
maybeDeleteBridgeProtocolRegistryEntries(
deleteBridgeProtocolRegistryEntryHelper = new DeleteBridgeProtocolRegistryEntryHelperImplementation()
) {
try {
var wrk = deleteBridgeProtocolRegistryEntryHelper.openRegistryRoot();
const path = deleteBridgeProtocolRegistryEntryHelper.getApplicationPath();
const maybeDeleteRegistryKey = (protocol, protocolCommand) => {
const openCommandPath = protocol + "\\shell\\open\\command";
if (wrk.hasChild(openCommandPath)) {
let deleteProtocolEntry = false;
try {
var openCommandKey = wrk.openChild(
openCommandPath,
wrk.ACCESS_READ
);
if (openCommandKey.valueCount == 1) {
const defaultKeyName = "";
if (openCommandKey.getValueName(0) == defaultKeyName) {
if (
openCommandKey.getValueType(defaultKeyName) ==
Ci.nsIWindowsRegKey.TYPE_STRING
) {
const val = openCommandKey.readStringValue(defaultKeyName);
if (val == protocolCommand) {
deleteProtocolEntry = true;
}
}
}
}
} finally {
openCommandKey.close();
}
if (deleteProtocolEntry) {
deleteBridgeProtocolRegistryEntryHelper.deleteRegistryTree(
wrk,
protocol
);
}
}
};
maybeDeleteRegistryKey("firefox", `\"${path}\" -osint -url \"%1\"`);
maybeDeleteRegistryKey(
"firefox-private",
`\"${path}\" -osint -private-window \"%1\"`
);
} catch (err) {
console.error(err);
} finally {
wrk.close();
}
},
/**
* Registers the firefox-bridge and firefox-private-bridge protocols
* on the Windows platform.
*/
maybeRegisterFirefoxBridgeProtocols() {
const FIREFOX_BRIDGE_HANDLER_NAME = "firefox-bridge";
const FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME = "firefox-private-bridge";
const path = Services.dirsvc.get("XREExeF", Ci.nsIFile).path;
let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
Ci.nsIWindowsRegKey
);
try {
wrk.open(wrk.ROOT_KEY_CLASSES_ROOT, "", wrk.ACCESS_READ);
let FxSet = wrk.hasChild(FIREFOX_BRIDGE_HANDLER_NAME);
let FxPrivateSet = wrk.hasChild(FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME);
wrk.close();
if (FxSet && FxPrivateSet) {
return;
}
wrk.open(wrk.ROOT_KEY_CURRENT_USER, "Software\\Classes", wrk.ACCESS_ALL);
const maybeUpdateRegistry = (isSetAlready, handler, protocolName) => {
if (isSetAlready) {
return;
}
let FxKey = wrk.createChild(handler, wrk.ACCESS_ALL);
try {
// Write URL protocol key
FxKey.writeStringValue("", protocolName);
FxKey.writeStringValue("URL Protocol", "");
FxKey.close();
// Write defaultIcon key
FxKey.create(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\DefaultIcon",
FxKey.ACCESS_ALL
);
FxKey.open(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\DefaultIcon",
FxKey.ACCESS_ALL
);
FxKey.writeStringValue("", `\"${path}\",1`);
FxKey.close();
// Write shell\\open\\command key
FxKey.create(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\shell",
FxKey.ACCESS_ALL
);
FxKey.create(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\shell\\open",
FxKey.ACCESS_ALL
);
FxKey.create(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\shell\\open\\command",
FxKey.ACCESS_ALL
);
FxKey.open(
FxKey.ROOT_KEY_CURRENT_USER,
"Software\\Classes\\" + handler + "\\shell\\open\\command",
FxKey.ACCESS_ALL
);
if (handler == FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME) {
FxKey.writeStringValue(
"",
`\"${path}\" -osint -private-window \"%1\"`
);
} else {
FxKey.writeStringValue("", `\"${path}\" -osint -url \"%1\"`);
}
} catch (ex) {
console.error(ex);
} finally {
FxKey.close();
}
};
try {
maybeUpdateRegistry(
FxSet,
FIREFOX_BRIDGE_HANDLER_NAME,
"URL:Firefox Bridge Protocol"
);
} catch (ex) {
console.error(ex);
}
try {
maybeUpdateRegistry(
FxPrivateSet,
FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME,
"URL:Firefox Private Bridge Protocol"
);
} catch (ex) {
console.error(ex);
}
} catch (ex) {
console.error(ex);
} finally {
wrk.close();
}
},
};

View file

@ -7,6 +7,12 @@
with Files("**"): with Files("**"):
BUG_COMPONENT = ("Firefox", "General") BUG_COMPONENT = ("Firefox", "General")
with Files("FirefoxBridgeExtensionUtils.sys.mjs"):
BUG_COMPONENT = ("Firefox", "Shell Integration")
with Files("test/unit/test_FirefoxBridgeExtensionUtils.sys.mjs"):
BUG_COMPONENT = ("Firefox", "Shell Integration")
with Files("test/browser/*Telemetry*"): with Files("test/browser/*Telemetry*"):
BUG_COMPONENT = ("Toolkit", "Telemetry") BUG_COMPONENT = ("Toolkit", "Telemetry")
@ -128,6 +134,7 @@ EXTRA_JS_MODULES += [
"EveryWindow.sys.mjs", "EveryWindow.sys.mjs",
"ExtensionsUI.sys.mjs", "ExtensionsUI.sys.mjs",
"FaviconLoader.sys.mjs", "FaviconLoader.sys.mjs",
"FirefoxBridgeExtensionUtils.sys.mjs",
"HomePage.sys.mjs", "HomePage.sys.mjs",
"LaterRun.sys.mjs", "LaterRun.sys.mjs",
"NewTabPagePreloading.sys.mjs", "NewTabPagePreloading.sys.mjs",

View file

@ -0,0 +1,350 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { FirefoxBridgeExtensionUtils } = ChromeUtils.importESModule(
"resource:///modules/FirefoxBridgeExtensionUtils.sys.mjs"
);
const FIREFOX_SHELL_OPEN_COMMAND_PATH = "firefox\\shell\\open\\command";
const FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH =
"firefox-private\\shell\\open\\command";
class StubbedRegistryKey {
#children;
#originalChildren;
#closeCalled;
#deletedChildren;
#openedForRead;
#values;
constructor(children, values) {
this.#children = children;
this.#values = values;
this.#originalChildren = new Map(children);
this.#closeCalled = false;
this.#openedForRead = false;
this.#deletedChildren = new Set([]);
}
get ACCESS_READ() {
return 0;
}
reset() {
this.#closeCalled = false;
this.#deletedChildren = new Set([]);
this.#children = new Map(this.#originalChildren);
}
open(accessLevel) {
this.#openedForRead = true;
}
get wasOpenedForRead() {
return this.#openedForRead;
}
openChild(path, accessLevel) {
const result = this.#children.get(path);
result?.open(accessLevel);
return result;
}
hasChild(path) {
return this.#children.has(path);
}
close() {
this.#closeCalled = true;
}
removeChild(path) {
this.#deletedChildren.add(path);
// delete the actual child if it's in there
this.#children.delete(path);
}
isChildDeleted(path) {
return this.#deletedChildren.has(path);
}
getChildName(index) {
let i = 0;
for (const [key] of this.#children) {
if (i == index) {
return key;
}
i++;
}
return undefined;
}
readStringValue(name) {
return this.#values.get(name);
}
get childCount() {
return this.#children.size;
}
getValueType(entryName) {
if (typeof this.readStringValue(entryName) == "string") {
return Ci.nsIWindowsRegKey.TYPE_STRING;
}
throw new Error(`${entryName} not found in registry`);
}
get wasCloseCalled() {
return this.#closeCalled;
}
getValueName(index) {
let i = 0;
for (const [key] of this.#values) {
if (i == index) {
return key;
}
i++;
}
return undefined;
}
get valueCount() {
return this.#values.size;
}
}
class StubbedDeleteBridgeProtocolRegistryEntryHelper {
#applicationPath;
#registryRootKey;
constructor({ applicationPath, registryRootKey }) {
this.#applicationPath = applicationPath;
this.#registryRootKey = registryRootKey;
}
getApplicationPath() {
return this.#applicationPath;
}
openRegistryRoot() {
return this.#registryRootKey;
}
deleteRegistryTree(root, toDeletePath) {
// simplify this for tests
root.removeChild(toDeletePath);
}
}
add_task(async function test_DeleteWhenSameFirefoxInstall() {
const applicationPath = "testPath";
const firefoxEntries = new Map();
firefoxEntries.set("", `\"${applicationPath}\" -osint -url \"%1\"`);
const firefoxProtocolRegKey = new StubbedRegistryKey(
new Map(),
firefoxEntries
);
const firefoxPrivateEntries = new Map();
firefoxPrivateEntries.set(
"",
`\"${applicationPath}\" -osint -private-window \"%1\"`
);
const firefoxPrivateProtocolRegKey = new StubbedRegistryKey(
new Map(),
firefoxPrivateEntries
);
const children = new Map();
children.set(FIREFOX_SHELL_OPEN_COMMAND_PATH, firefoxProtocolRegKey);
children.set(
FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
firefoxPrivateProtocolRegKey
);
const registryRootKey = new StubbedRegistryKey(children, new Map());
const stubbedDeleteBridgeProtocolRegistryHelper =
new StubbedDeleteBridgeProtocolRegistryEntryHelper({
applicationPath,
registryRootKey,
});
FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
stubbedDeleteBridgeProtocolRegistryHelper
);
ok(registryRootKey.wasCloseCalled, "Root key closed");
ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened");
ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed");
ok(
registryRootKey.isChildDeleted("firefox"),
"Firefox protocol registry entry deleted"
);
ok(
firefoxPrivateProtocolRegKey.wasOpenedForRead,
"Firefox private key opened"
);
ok(firefoxPrivateProtocolRegKey.wasCloseCalled, "Firefox private key closed");
ok(
registryRootKey.isChildDeleted("firefox-private"),
"Firefox private protocol registry entry deleted"
);
});
add_task(async function test_DeleteWhenDifferentFirefoxInstall() {
const applicationPath = "testPath";
const badApplicationPath = "testPath2";
const firefoxEntries = new Map();
firefoxEntries.set("", `\"${badApplicationPath}\" -osint -url \"%1\"`);
const firefoxProtocolRegKey = new StubbedRegistryKey(
new Map(),
firefoxEntries
);
const firefoxPrivateEntries = new Map();
firefoxPrivateEntries.set(
"",
`\"${badApplicationPath}\" -osint -private-window \"%1\"`
);
const firefoxPrivateProtocolRegKey = new StubbedRegistryKey(
new Map(),
firefoxPrivateEntries
);
const children = new Map();
children.set(FIREFOX_SHELL_OPEN_COMMAND_PATH, firefoxProtocolRegKey);
children.set(
FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
firefoxPrivateProtocolRegKey
);
const registryRootKey = new StubbedRegistryKey(children, new Map());
const stubbedDeleteBridgeProtocolRegistryHelper =
new StubbedDeleteBridgeProtocolRegistryEntryHelper({
applicationPath,
registryRootKey,
});
FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
stubbedDeleteBridgeProtocolRegistryHelper
);
ok(registryRootKey.wasCloseCalled, "Root key closed");
ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened");
ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed");
ok(
!registryRootKey.isChildDeleted("firefox"),
"Firefox protocol registry entry not deleted"
);
ok(
firefoxPrivateProtocolRegKey.wasOpenedForRead,
"Firefox private key opened"
);
ok(firefoxPrivateProtocolRegKey.wasCloseCalled, "Firefox private key closed");
ok(
!registryRootKey.isChildDeleted("firefox-private"),
"Firefox private protocol registry entry not deleted"
);
});
add_task(async function test_DeleteWhenNoRegistryEntries() {
const applicationPath = "testPath";
const firefoxPrivateEntries = new Map();
const firefoxPrivateProtocolRegKey = new StubbedRegistryKey(
new Map(),
firefoxPrivateEntries
);
const children = new Map();
children.set(
FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH,
firefoxPrivateProtocolRegKey
);
const registryRootKey = new StubbedRegistryKey(children, new Map());
const stubbedDeleteBridgeProtocolRegistryHelper =
new StubbedDeleteBridgeProtocolRegistryEntryHelper({
applicationPath,
registryRootKey,
});
FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
stubbedDeleteBridgeProtocolRegistryHelper
);
ok(registryRootKey.wasCloseCalled, "Root key closed");
ok(
firefoxPrivateProtocolRegKey.wasOpenedForRead,
"Firefox private key opened"
);
ok(firefoxPrivateProtocolRegKey.wasCloseCalled, "Firefox private key closed");
ok(
!registryRootKey.isChildDeleted("firefox"),
"Firefox protocol registry entry deleted when it shouldn't be"
);
ok(
!registryRootKey.isChildDeleted("firefox-private"),
"Firefox private protocol registry deleted when it shouldn't be"
);
});
add_task(async function test_DeleteWhenUnexpectedRegistryEntries() {
const applicationPath = "testPath";
const firefoxEntries = new Map();
firefoxEntries.set("", `\"${applicationPath}\" -osint -url \"%1\"`);
firefoxEntries.set("extraEntry", "extraValue");
const firefoxProtocolRegKey = new StubbedRegistryKey(
new Map(),
firefoxEntries
);
const children = new Map();
children.set(FIREFOX_SHELL_OPEN_COMMAND_PATH, firefoxProtocolRegKey);
const registryRootKey = new StubbedRegistryKey(children, new Map());
const stubbedDeleteBridgeProtocolRegistryHelper =
new StubbedDeleteBridgeProtocolRegistryEntryHelper({
applicationPath,
registryRootKey,
});
FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries(
stubbedDeleteBridgeProtocolRegistryHelper
);
ok(registryRootKey.wasCloseCalled, "Root key closed");
ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened");
ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed");
ok(
!registryRootKey.isChildDeleted("firefox"),
"Firefox protocol registry entry deleted when it shouldn't be"
);
ok(
!registryRootKey.isChildDeleted("firefox-private"),
"Firefox private protocol registry deleted when it shouldn't be"
);
});

View file

@ -5,6 +5,9 @@ skip-if = ["os == 'android'"] # bug 1730213
["test_E10SUtils_nested_URIs.js"] ["test_E10SUtils_nested_URIs.js"]
["test_FirefoxBridgeExtensionUtils.js"]
run-if = ["os == 'win'"] # Test of a Windows-specific feature
["test_HomePage.js"] ["test_HomePage.js"]
["test_HomePage_ignore.js"] ["test_HomePage_ignore.js"]

View file

@ -1074,8 +1074,8 @@ pref("network.protocol-handler.external.disk", false);
pref("network.protocol-handler.external.disks", false); pref("network.protocol-handler.external.disks", false);
pref("network.protocol-handler.external.afp", false); pref("network.protocol-handler.external.afp", false);
pref("network.protocol-handler.external.moz-icon", false); pref("network.protocol-handler.external.moz-icon", false);
pref("network.protocol-handler.external.firefox", false); pref("network.protocol-handler.external.firefox-bridge", false);
pref("network.protocol-handler.external.firefox-private", false); pref("network.protocol-handler.external.firefox-private-bridge", false);
// Don't allow external protocol handlers for common typos // Don't allow external protocol handlers for common typos
pref("network.protocol-handler.external.ttp", false); // http pref("network.protocol-handler.external.ttp", false); // http