forked from mirrors/gecko-dev
Bug 1818418 - Use reg.exe to implement MSIX 1-click set-to-default. r=mhughes
This approach uses `reg.exe` to delete and recreate the relevant HKCU registry key-value pairs. Differential Revision: https://phabricator.services.mozilla.com/D170717
This commit is contained in:
parent
86aef92687
commit
cfde427e23
5 changed files with 300 additions and 130 deletions
|
|
@ -20,11 +20,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <appmodel.h> // for GetPackageFamilyName
|
||||||
#include <sddl.h> // for ConvertSidToStringSidW
|
#include <sddl.h> // for ConvertSidToStringSidW
|
||||||
#include <wincrypt.h> // for CryptoAPI base64
|
#include <wincrypt.h> // for CryptoAPI base64
|
||||||
#include <bcrypt.h> // for CNG MD5
|
#include <bcrypt.h> // for CNG MD5
|
||||||
#include <winternl.h> // for NT_SUCCESS()
|
#include <winternl.h> // for NT_SUCCESS()
|
||||||
|
|
||||||
|
#include "nsDebug.h"
|
||||||
#include "mozilla/ArrayUtils.h"
|
#include "mozilla/ArrayUtils.h"
|
||||||
#include "mozilla/UniquePtr.h"
|
#include "mozilla/UniquePtr.h"
|
||||||
#include "nsWindowsHelpers.h"
|
#include "nsWindowsHelpers.h"
|
||||||
|
|
@ -420,3 +422,53 @@ bool CheckProgIDExists(const wchar_t* aProgID) {
|
||||||
::RegCloseKey(key);
|
::RegCloseKey(key);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsresult GetMsixProgId(const wchar_t* assoc, UniquePtr<wchar_t[]>& aProgId) {
|
||||||
|
// Retrieve the registry path to the package from registry path:
|
||||||
|
// clang-format off
|
||||||
|
// HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages\[Package Full Name]\App\Capabilities\[FileAssociations | URLAssociations]\[File | URL]
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
UINT32 pfnLen = 0;
|
||||||
|
LONG rv = GetCurrentPackageFullName(&pfnLen, nullptr);
|
||||||
|
NS_ENSURE_TRUE(rv != APPMODEL_ERROR_NO_PACKAGE, NS_ERROR_FAILURE);
|
||||||
|
|
||||||
|
auto pfn = mozilla::MakeUnique<wchar_t[]>(pfnLen);
|
||||||
|
rv = GetCurrentPackageFullName(&pfnLen, pfn.get());
|
||||||
|
NS_ENSURE_TRUE(rv == ERROR_SUCCESS, NS_ERROR_FAILURE);
|
||||||
|
|
||||||
|
const wchar_t* assocSuffix;
|
||||||
|
if (assoc[0] == L'.') {
|
||||||
|
// File association.
|
||||||
|
assocSuffix = LR"(App\Capabilities\FileAssociations)";
|
||||||
|
} else {
|
||||||
|
// URL association.
|
||||||
|
assocSuffix = LR"(App\Capabilities\URLAssociations)";
|
||||||
|
}
|
||||||
|
|
||||||
|
const wchar_t* assocPathFmt =
|
||||||
|
LR"(Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages\%s\%s)";
|
||||||
|
int assocPathLen = _scwprintf(assocPathFmt, pfn.get(), assocSuffix);
|
||||||
|
assocPathLen += 1; // _scwprintf does not include the terminator
|
||||||
|
|
||||||
|
auto assocPath = MakeUnique<wchar_t[]>(assocPathLen);
|
||||||
|
_snwprintf_s(assocPath.get(), assocPathLen, _TRUNCATE, assocPathFmt,
|
||||||
|
pfn.get(), assocSuffix);
|
||||||
|
|
||||||
|
LSTATUS ls;
|
||||||
|
|
||||||
|
// Retrieve the package association's ProgID, always in the form `AppX[32 hash
|
||||||
|
// characters]`.
|
||||||
|
const size_t appxProgIdLen = 37;
|
||||||
|
auto progId = MakeUnique<wchar_t[]>(appxProgIdLen);
|
||||||
|
DWORD progIdLen = appxProgIdLen * sizeof(wchar_t);
|
||||||
|
ls = ::RegGetValueW(HKEY_CLASSES_ROOT, assocPath.get(), assoc, RRF_RT_REG_SZ,
|
||||||
|
nullptr, (LPBYTE)progId.get(), &progIdLen);
|
||||||
|
if (ls != ERROR_SUCCESS) {
|
||||||
|
return NS_ERROR_WDBA_NO_PROGID;
|
||||||
|
}
|
||||||
|
|
||||||
|
aProgId.swap(progId);
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "ErrorList.h" // for nsresult
|
||||||
#include "mozilla/UniquePtr.h"
|
#include "mozilla/UniquePtr.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -100,4 +101,17 @@ mozilla::UniquePtr<wchar_t[]> FormatProgID(const wchar_t* aProgIDBase,
|
||||||
*/
|
*/
|
||||||
bool CheckProgIDExists(const wchar_t* aProgID);
|
bool CheckProgIDExists(const wchar_t* aProgID);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the ProgID registered by Windows for the given association.
|
||||||
|
*
|
||||||
|
* The MSIX `AppManifest.xml` declares supported protocols and file
|
||||||
|
* type associations. Upon installation, Windows generates
|
||||||
|
* corresponding ProgIDs for them, of the form `AppX*`. This function
|
||||||
|
* retrieves those generated ProgIDs (from the Windows registry).
|
||||||
|
*
|
||||||
|
* @return ProgID.
|
||||||
|
*/
|
||||||
|
nsresult GetMsixProgId(const wchar_t* assoc,
|
||||||
|
mozilla::UniquePtr<wchar_t[]>& aProgId);
|
||||||
|
|
||||||
#endif // SHELL_WINDOWSUSERCHOICE_H__
|
#endif // SHELL_WINDOWSUSERCHOICE_H__
|
||||||
|
|
|
||||||
|
|
@ -300,10 +300,41 @@ nsWindowsShellService::CheckAllProgIDsExist(bool* aResult) {
|
||||||
if (!mozilla::widget::WinTaskbar::GetAppUserModelID(aumid)) {
|
if (!mozilla::widget::WinTaskbar::GetAppUserModelID(aumid)) {
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
*aResult =
|
|
||||||
CheckProgIDExists(FormatProgID(L"FirefoxURL", aumid.get()).get()) &&
|
if (widget::WinUtils::HasPackageIdentity()) {
|
||||||
CheckProgIDExists(FormatProgID(L"FirefoxHTML", aumid.get()).get()) &&
|
UniquePtr<wchar_t[]> extraProgID;
|
||||||
CheckProgIDExists(FormatProgID(L"FirefoxPDF", aumid.get()).get());
|
nsresult rv;
|
||||||
|
bool result = true;
|
||||||
|
|
||||||
|
// "FirefoxURL".
|
||||||
|
rv = GetMsixProgId(L"https", extraProgID);
|
||||||
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
result = result && CheckProgIDExists(extraProgID.get());
|
||||||
|
|
||||||
|
// "FirefoxHTML".
|
||||||
|
rv = GetMsixProgId(L".htm", extraProgID);
|
||||||
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
result = result && CheckProgIDExists(extraProgID.get());
|
||||||
|
|
||||||
|
// "FirefoxPDF".
|
||||||
|
rv = GetMsixProgId(L".pdf", extraProgID);
|
||||||
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
result = result && CheckProgIDExists(extraProgID.get());
|
||||||
|
|
||||||
|
*aResult = result;
|
||||||
|
} else {
|
||||||
|
*aResult =
|
||||||
|
CheckProgIDExists(FormatProgID(L"FirefoxURL", aumid.get()).get()) &&
|
||||||
|
CheckProgIDExists(FormatProgID(L"FirefoxHTML", aumid.get()).get()) &&
|
||||||
|
CheckProgIDExists(FormatProgID(L"FirefoxPDF", aumid.get()).get());
|
||||||
|
}
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -198,9 +198,8 @@ def get_branding(use_official, topsrcdir, build_app, finder, log=None):
|
||||||
conf_vars = mozpath.join(topsrcdir, build_app, "confvars.sh")
|
conf_vars = mozpath.join(topsrcdir, build_app, "confvars.sh")
|
||||||
|
|
||||||
def conf_vars_value(key):
|
def conf_vars_value(key):
|
||||||
lines = open(conf_vars).readlines()
|
lines = [line.strip() for line in open(conf_vars).readlines()]
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line = line.strip()
|
|
||||||
if line and line[0] == "#":
|
if line and line[0] == "#":
|
||||||
continue
|
continue
|
||||||
if key not in line:
|
if key not in line:
|
||||||
|
|
@ -833,7 +832,7 @@ def _sign_msix_win(output, force, log, verbose):
|
||||||
else:
|
else:
|
||||||
thumbprint = None
|
thumbprint = None
|
||||||
|
|
||||||
if not thumbprint:
|
if force or not thumbprint:
|
||||||
thumbprint = (
|
thumbprint = (
|
||||||
powershell(
|
powershell(
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <appmodel.h>
|
||||||
#include <shlobj.h> // for SHChangeNotify and IApplicationAssociationRegistration
|
#include <shlobj.h> // for SHChangeNotify and IApplicationAssociationRegistration
|
||||||
|
|
||||||
#include "mozilla/ArrayUtils.h"
|
#include "mozilla/ArrayUtils.h"
|
||||||
|
#include "mozilla/CmdLineAndEnvUtils.h"
|
||||||
#include "mozilla/RefPtr.h"
|
#include "mozilla/RefPtr.h"
|
||||||
#include "mozilla/UniquePtr.h"
|
#include "mozilla/UniquePtr.h"
|
||||||
#include "mozilla/WindowsVersion.h"
|
#include "mozilla/WindowsVersion.h"
|
||||||
|
|
@ -86,50 +88,8 @@ static bool CheckEqualMinutes(SYSTEMTIME aSystemTime1,
|
||||||
(fileTime1.dwHighDateTime == fileTime2.dwHighDateTime);
|
(fileTime1.dwHighDateTime == fileTime2.dwHighDateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static bool SetUserChoiceRegistry(const wchar_t* aExt, const wchar_t* aProgID,
|
||||||
* Set an association with a UserChoice key
|
mozilla::UniquePtr<wchar_t[]> aHash) {
|
||||||
*
|
|
||||||
* Removes the old key, creates a new one with ProgID and Hash set to
|
|
||||||
* enable a new asociation.
|
|
||||||
*
|
|
||||||
* @param aExt File type or protocol to associate
|
|
||||||
* @param aSid Current user's string SID
|
|
||||||
* @param aProgID ProgID to use for the asociation
|
|
||||||
*
|
|
||||||
* @return true if successful, false on error.
|
|
||||||
*/
|
|
||||||
static bool SetUserChoice(const wchar_t* aExt, const wchar_t* aSid,
|
|
||||||
const wchar_t* aProgID) {
|
|
||||||
SYSTEMTIME hashTimestamp;
|
|
||||||
::GetSystemTime(&hashTimestamp);
|
|
||||||
auto hash = GenerateUserChoiceHash(aExt, aSid, aProgID, hashTimestamp);
|
|
||||||
if (!hash) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The hash changes at the end of each minute, so check that the hash should
|
|
||||||
// be the same by the time we're done writing.
|
|
||||||
const ULONGLONG kWriteTimingThresholdMilliseconds = 100;
|
|
||||||
// Generating the hash could have taken some time, so start from now.
|
|
||||||
SYSTEMTIME writeEndTimestamp;
|
|
||||||
::GetSystemTime(&writeEndTimestamp);
|
|
||||||
if (!AddMillisecondsToSystemTime(writeEndTimestamp,
|
|
||||||
kWriteTimingThresholdMilliseconds)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!CheckEqualMinutes(hashTimestamp, writeEndTimestamp)) {
|
|
||||||
LOG_ERROR_MESSAGE(
|
|
||||||
L"Hash is too close to expiration, sleeping until next hash.");
|
|
||||||
::Sleep(kWriteTimingThresholdMilliseconds * 2);
|
|
||||||
|
|
||||||
// For consistency, use the current time.
|
|
||||||
::GetSystemTime(&hashTimestamp);
|
|
||||||
hash = GenerateUserChoiceHash(aExt, aSid, aProgID, hashTimestamp);
|
|
||||||
if (!hash) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto assocKeyPath = GetAssociationKeyPath(aExt);
|
auto assocKeyPath = GetAssociationKeyPath(aExt);
|
||||||
if (!assocKeyPath) {
|
if (!assocKeyPath) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -174,9 +134,9 @@ static bool SetUserChoice(const wchar_t* aExt, const wchar_t* aSid,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DWORD hashByteCount = (::lstrlenW(hash.get()) + 1) * sizeof(wchar_t);
|
DWORD hashByteCount = (::lstrlenW(aHash.get()) + 1) * sizeof(wchar_t);
|
||||||
ls = ::RegSetValueExW(userChoiceKey.get(), L"Hash", 0, REG_SZ,
|
ls = ::RegSetValueExW(userChoiceKey.get(), L"Hash", 0, REG_SZ,
|
||||||
reinterpret_cast<const unsigned char*>(hash.get()),
|
reinterpret_cast<const unsigned char*>(aHash.get()),
|
||||||
hashByteCount);
|
hashByteCount);
|
||||||
if (ls != ERROR_SUCCESS) {
|
if (ls != ERROR_SUCCESS) {
|
||||||
LOG_ERROR(HRESULT_FROM_WIN32(ls));
|
LOG_ERROR(HRESULT_FROM_WIN32(ls));
|
||||||
|
|
@ -186,6 +146,162 @@ static bool SetUserChoice(const wchar_t* aExt, const wchar_t* aSid,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool LaunchReg(int aArgsLength, const wchar_t* const* aArgs) {
|
||||||
|
mozilla::UniquePtr<wchar_t[]> regPath =
|
||||||
|
mozilla::MakeUnique<wchar_t[]>(MAX_PATH + 1);
|
||||||
|
if (!ConstructSystem32Path(L"reg.exe", regPath.get(), MAX_PATH + 1)) {
|
||||||
|
LOG_ERROR_MESSAGE(L"Failed to construct path to reg.exe");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wchar_t* regArgs[] = {regPath.get()};
|
||||||
|
mozilla::UniquePtr<wchar_t[]> regCmdLine(mozilla::MakeCommandLine(
|
||||||
|
mozilla::ArrayLength(regArgs), const_cast<wchar_t**>(regArgs),
|
||||||
|
aArgsLength, const_cast<wchar_t**>(aArgs)));
|
||||||
|
|
||||||
|
PROCESS_INFORMATION pi;
|
||||||
|
STARTUPINFOW si = {sizeof(si)};
|
||||||
|
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||||
|
si.wShowWindow = SW_HIDE;
|
||||||
|
if (!::CreateProcessW(regPath.get(), regCmdLine.get(), nullptr, nullptr,
|
||||||
|
FALSE, 0, nullptr, nullptr, &si, &pi)) {
|
||||||
|
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
|
||||||
|
LOG_ERROR(hr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsAutoHandle process(pi.hProcess);
|
||||||
|
nsAutoHandle mainThread(pi.hThread);
|
||||||
|
|
||||||
|
DWORD exitCode;
|
||||||
|
if (::WaitForSingleObject(process.get(), INFINITE) == WAIT_OBJECT_0 &&
|
||||||
|
::GetExitCodeProcess(process.get(), &exitCode)) {
|
||||||
|
// N.b.: `reg.exe` returns 0 (unchanged) or 2 (changed) on success.
|
||||||
|
bool success = (exitCode == 0 || exitCode == 2);
|
||||||
|
if (!success) {
|
||||||
|
LOG_ERROR_MESSAGE(L"%s returned failure exitCode %d", regCmdLine.get(),
|
||||||
|
exitCode);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool SetUserChoiceCommand(const wchar_t* aExt, const wchar_t* aProgID,
|
||||||
|
const wchar_t* aHash) {
|
||||||
|
auto assocKeyPath = GetAssociationKeyPath(aExt);
|
||||||
|
if (!assocKeyPath) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wchar_t* formatString = L"HKCU\\%s\\UserChoice";
|
||||||
|
int bufferSize = _scwprintf(formatString, assocKeyPath.get());
|
||||||
|
++bufferSize; // Extra character for terminating null
|
||||||
|
mozilla::UniquePtr<wchar_t[]> userChoiceKeyPath =
|
||||||
|
mozilla::MakeUnique<wchar_t[]>(bufferSize);
|
||||||
|
_snwprintf_s(userChoiceKeyPath.get(), bufferSize, _TRUNCATE, formatString,
|
||||||
|
assocKeyPath.get());
|
||||||
|
|
||||||
|
const wchar_t* deleteArgs[] = {
|
||||||
|
L"DELETE",
|
||||||
|
userChoiceKeyPath.get(),
|
||||||
|
L"/F",
|
||||||
|
};
|
||||||
|
if (!LaunchReg(mozilla::ArrayLength(deleteArgs), deleteArgs)) {
|
||||||
|
LOG_ERROR_MESSAGE(L"Failed to reg.exe DELETE; ignoring and continuing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like REG ADD [ROOT\]RegKey /V ValueName [/T DataType] [/S Separator] [/D
|
||||||
|
// Data] [/F] [/reg:32] [/reg:64]
|
||||||
|
|
||||||
|
const wchar_t* progIDArgs[] = {
|
||||||
|
L"ADD", userChoiceKeyPath.get(),
|
||||||
|
L"/F", L"/V",
|
||||||
|
L"ProgID", L"/T",
|
||||||
|
L"REG_SZ", L"/D",
|
||||||
|
aProgID,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!LaunchReg(mozilla::ArrayLength(progIDArgs), progIDArgs)) {
|
||||||
|
// LaunchReg will have logged an error message already.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wchar_t* hashArgs[] = {
|
||||||
|
L"ADD", userChoiceKeyPath.get(),
|
||||||
|
L"/F", L"/V",
|
||||||
|
L"Hash", L"/T",
|
||||||
|
L"REG_SZ", L"/D",
|
||||||
|
aHash,
|
||||||
|
};
|
||||||
|
if (!LaunchReg(mozilla::ArrayLength(hashArgs), hashArgs)) {
|
||||||
|
// LaunchReg will have logged an error message already.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set an association with a UserChoice key
|
||||||
|
*
|
||||||
|
* Removes the old key, creates a new one with ProgID and Hash set to
|
||||||
|
* enable a new asociation.
|
||||||
|
*
|
||||||
|
* @param aExt File type or protocol to associate
|
||||||
|
* @param aSid Current user's string SID
|
||||||
|
* @param aProgID ProgID to use for the asociation
|
||||||
|
*
|
||||||
|
* @return true if successful, false on error.
|
||||||
|
*/
|
||||||
|
static bool SetUserChoice(const wchar_t* aExt, const wchar_t* aSid,
|
||||||
|
const wchar_t* aProgID) {
|
||||||
|
// This might be slow to query, so do it before generating timestamps and
|
||||||
|
// hashes.
|
||||||
|
UINT32 pfnLen = 0;
|
||||||
|
bool inMsix =
|
||||||
|
GetCurrentPackageFullName(&pfnLen, nullptr) != APPMODEL_ERROR_NO_PACKAGE;
|
||||||
|
|
||||||
|
SYSTEMTIME hashTimestamp;
|
||||||
|
::GetSystemTime(&hashTimestamp);
|
||||||
|
auto hash = GenerateUserChoiceHash(aExt, aSid, aProgID, hashTimestamp);
|
||||||
|
if (!hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The hash changes at the end of each minute, so check that the hash should
|
||||||
|
// be the same by the time we're done writing.
|
||||||
|
const ULONGLONG kWriteTimingThresholdMilliseconds = 1000;
|
||||||
|
// Generating the hash could have taken some time, so start from now.
|
||||||
|
SYSTEMTIME writeEndTimestamp;
|
||||||
|
::GetSystemTime(&writeEndTimestamp);
|
||||||
|
if (!AddMillisecondsToSystemTime(writeEndTimestamp,
|
||||||
|
kWriteTimingThresholdMilliseconds)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!CheckEqualMinutes(hashTimestamp, writeEndTimestamp)) {
|
||||||
|
LOG_ERROR_MESSAGE(
|
||||||
|
L"Hash is too close to expiration, sleeping until next hash.");
|
||||||
|
::Sleep(kWriteTimingThresholdMilliseconds * 2);
|
||||||
|
|
||||||
|
// For consistency, use the current time.
|
||||||
|
::GetSystemTime(&hashTimestamp);
|
||||||
|
hash = GenerateUserChoiceHash(aExt, aSid, aProgID, hashTimestamp);
|
||||||
|
if (!hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inMsix) {
|
||||||
|
// We're in an MSIX package, thus need to use reg.exe.
|
||||||
|
return SetUserChoiceCommand(aExt, aProgID, hash.get());
|
||||||
|
} else {
|
||||||
|
// We're outside of an MSIX package and can use the Win32 Registry API.
|
||||||
|
return SetUserChoiceRegistry(aExt, aProgID, std::move(hash));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static bool VerifyUserDefault(const wchar_t* aExt, const wchar_t* aProgID) {
|
static bool VerifyUserDefault(const wchar_t* aExt, const wchar_t* aProgID) {
|
||||||
RefPtr<IApplicationAssociationRegistration> pAAR;
|
RefPtr<IApplicationAssociationRegistration> pAAR;
|
||||||
HRESULT hr = ::CoCreateInstance(
|
HRESULT hr = ::CoCreateInstance(
|
||||||
|
|
@ -227,29 +343,19 @@ static bool VerifyUserDefault(const wchar_t* aExt, const wchar_t* aProgID) {
|
||||||
|
|
||||||
nsresult SetDefaultBrowserUserChoice(
|
nsresult SetDefaultBrowserUserChoice(
|
||||||
const wchar_t* aAumi, const nsTArray<nsString>& aExtraFileExtensions) {
|
const wchar_t* aAumi, const nsTArray<nsString>& aExtraFileExtensions) {
|
||||||
auto urlProgID = FormatProgID(L"FirefoxURL", aAumi);
|
// Verify that the implementation of UserChoice hashing has not changed by
|
||||||
if (!CheckProgIDExists(urlProgID.get())) {
|
// computing the current default hash and comparing with the existing value.
|
||||||
LOG_ERROR_MESSAGE(L"ProgID %s not found", urlProgID.get());
|
|
||||||
return NS_ERROR_WDBA_NO_PROGID;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto htmlProgID = FormatProgID(L"FirefoxHTML", aAumi);
|
|
||||||
if (!CheckProgIDExists(htmlProgID.get())) {
|
|
||||||
LOG_ERROR_MESSAGE(L"ProgID %s not found", htmlProgID.get());
|
|
||||||
return NS_ERROR_WDBA_NO_PROGID;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto pdfProgID = FormatProgID(L"FirefoxPDF", aAumi);
|
|
||||||
if (!CheckProgIDExists(pdfProgID.get())) {
|
|
||||||
LOG_ERROR_MESSAGE(L"ProgID %s not found", pdfProgID.get());
|
|
||||||
return NS_ERROR_WDBA_NO_PROGID;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CheckBrowserUserChoiceHashes()) {
|
if (!CheckBrowserUserChoiceHashes()) {
|
||||||
LOG_ERROR_MESSAGE(L"UserChoice Hash mismatch");
|
LOG_ERROR_MESSAGE(L"UserChoice Hash mismatch");
|
||||||
return NS_ERROR_WDBA_HASH_CHECK;
|
return NS_ERROR_WDBA_HASH_CHECK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsTArray<nsString> browserDefaults = {
|
||||||
|
u"https"_ns, u"FirefoxURL"_ns, u"http"_ns, u"FirefoxURL"_ns,
|
||||||
|
u".html"_ns, u"FirefoxHTML"_ns, u".htm"_ns, u"FirefoxHTML"_ns};
|
||||||
|
|
||||||
|
browserDefaults.AppendElements(aExtraFileExtensions);
|
||||||
|
|
||||||
if (!mozilla::IsWin10CreatorsUpdateOrLater()) {
|
if (!mozilla::IsWin10CreatorsUpdateOrLater()) {
|
||||||
LOG_ERROR_MESSAGE(L"UserChoice hash matched, but Windows build is too old");
|
LOG_ERROR_MESSAGE(L"UserChoice hash matched, but Windows build is too old");
|
||||||
return NS_ERROR_WDBA_BUILD;
|
return NS_ERROR_WDBA_BUILD;
|
||||||
|
|
@ -260,52 +366,16 @@ nsresult SetDefaultBrowserUserChoice(
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = true;
|
nsresult rv = SetDefaultExtensionHandlersUserChoiceImpl(aAumi, sid.get(),
|
||||||
bool defaultRejected = false;
|
browserDefaults);
|
||||||
|
if (!NS_SUCCEEDED(rv)) {
|
||||||
struct {
|
LOG_ERROR_MESSAGE(L"Failed setting default with %s", aAumi);
|
||||||
const wchar_t* ext;
|
|
||||||
const wchar_t* progID;
|
|
||||||
} associations[] = {{L"https", urlProgID.get()},
|
|
||||||
{L"http", urlProgID.get()},
|
|
||||||
{L".html", htmlProgID.get()},
|
|
||||||
{L".htm", htmlProgID.get()}};
|
|
||||||
for (size_t i = 0; i < mozilla::ArrayLength(associations); ++i) {
|
|
||||||
if (!SetUserChoice(associations[i].ext, sid.get(),
|
|
||||||
associations[i].progID)) {
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
} else if (!VerifyUserDefault(associations[i].ext,
|
|
||||||
associations[i].progID)) {
|
|
||||||
defaultRejected = true;
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
nsresult rv = SetDefaultExtensionHandlersUserChoiceImpl(
|
|
||||||
aAumi, sid.get(), aExtraFileExtensions);
|
|
||||||
if (rv == NS_ERROR_WDBA_REJECTED) {
|
|
||||||
ok = false;
|
|
||||||
defaultRejected = true;
|
|
||||||
} else if (rv == NS_ERROR_FAILURE) {
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify shell to refresh icons
|
// Notify shell to refresh icons
|
||||||
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||||
|
|
||||||
if (!ok) {
|
return rv;
|
||||||
LOG_ERROR_MESSAGE(L"Failed setting default with %s", aAumi);
|
|
||||||
if (defaultRejected) {
|
|
||||||
return NS_ERROR_WDBA_REJECTED;
|
|
||||||
}
|
|
||||||
return NS_ERROR_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult SetDefaultExtensionHandlersUserChoice(
|
nsresult SetDefaultExtensionHandlersUserChoice(
|
||||||
|
|
@ -315,35 +385,25 @@ nsresult SetDefaultExtensionHandlersUserChoice(
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = true;
|
|
||||||
bool defaultRejected = false;
|
|
||||||
|
|
||||||
nsresult rv = SetDefaultExtensionHandlersUserChoiceImpl(aAumi, sid.get(),
|
nsresult rv = SetDefaultExtensionHandlersUserChoiceImpl(aAumi, sid.get(),
|
||||||
aFileExtensions);
|
aFileExtensions);
|
||||||
if (rv == NS_ERROR_WDBA_REJECTED) {
|
if (!NS_SUCCEEDED(rv)) {
|
||||||
ok = false;
|
LOG_ERROR_MESSAGE(L"Failed setting default with %s", aAumi);
|
||||||
defaultRejected = true;
|
|
||||||
} else if (rv == NS_ERROR_FAILURE) {
|
|
||||||
ok = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify shell to refresh icons
|
// Notify shell to refresh icons
|
||||||
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||||
|
|
||||||
if (!ok) {
|
return rv;
|
||||||
LOG_ERROR_MESSAGE(L"Failed setting default with %s", aAumi);
|
|
||||||
if (defaultRejected) {
|
|
||||||
return NS_ERROR_WDBA_REJECTED;
|
|
||||||
}
|
|
||||||
return NS_ERROR_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult SetDefaultExtensionHandlersUserChoiceImpl(
|
nsresult SetDefaultExtensionHandlersUserChoiceImpl(
|
||||||
const wchar_t* aAumi, const wchar_t* const aSid,
|
const wchar_t* aAumi, const wchar_t* const aSid,
|
||||||
const nsTArray<nsString>& aFileExtensions) {
|
const nsTArray<nsString>& aFileExtensions) {
|
||||||
|
UINT32 pfnLen = 0;
|
||||||
|
bool inMsix =
|
||||||
|
GetCurrentPackageFullName(&pfnLen, nullptr) != APPMODEL_ERROR_NO_PACKAGE;
|
||||||
|
|
||||||
for (size_t i = 0; i + 1 < aFileExtensions.Length(); i += 2) {
|
for (size_t i = 0; i + 1 < aFileExtensions.Length(); i += 2) {
|
||||||
const wchar_t* extraFileExtension =
|
const wchar_t* extraFileExtension =
|
||||||
PromiseFlatString(aFileExtensions[i]).get();
|
PromiseFlatString(aFileExtensions[i]).get();
|
||||||
|
|
@ -351,7 +411,21 @@ nsresult SetDefaultExtensionHandlersUserChoiceImpl(
|
||||||
PromiseFlatString(aFileExtensions[i + 1]).get();
|
PromiseFlatString(aFileExtensions[i + 1]).get();
|
||||||
// Formatting the ProgID here prevents using this helper to target arbitrary
|
// Formatting the ProgID here prevents using this helper to target arbitrary
|
||||||
// ProgIDs.
|
// ProgIDs.
|
||||||
auto extraProgID = FormatProgID(extraProgIDRoot, aAumi);
|
UniquePtr<wchar_t[]> extraProgID;
|
||||||
|
if (inMsix) {
|
||||||
|
nsresult rv = GetMsixProgId(extraFileExtension, extraProgID);
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
LOG_ERROR_MESSAGE(L"Failed to retrieve MSIX progID for %s",
|
||||||
|
extraFileExtension);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
extraProgID = FormatProgID(extraProgIDRoot, aAumi);
|
||||||
|
if (!CheckProgIDExists(extraProgID.get())) {
|
||||||
|
LOG_ERROR_MESSAGE(L"ProgID %s not found", extraProgID.get());
|
||||||
|
return NS_ERROR_WDBA_NO_PROGID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!SetUserChoice(extraFileExtension, aSid, extraProgID.get())) {
|
if (!SetUserChoice(extraFileExtension, aSid, extraProgID.get())) {
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue