зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1337530 - Fix Windows default browser detection. r=rstrong
In addition to fixing the bug, this patch takes the opportunity to remove a bunch of registry handling code specific to XP and Vista, leaving only the one officially supported default detection method for all versions above Vista. MozReview-Commit-ID: 1I77ECJaOFk --HG-- extra : rebase_source : d1a22f966df218e5a9030389dc2305f349d9bbd7
This commit is contained in:
Родитель
486efeca4f
Коммит
4766e72aba
|
@ -67,6 +67,8 @@
|
|||
|
||||
#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
|
||||
|
||||
#define APP_REG_NAME_BASE L"Firefox-"
|
||||
|
||||
using mozilla::IsWin8OrLater;
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
|
@ -91,152 +93,6 @@ OpenKeyForReading(HKEY aKeyRoot, const nsAString& aKeyName, HKEY* aKey)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Default Browser Registry Settings
|
||||
//
|
||||
// The setting of these values are made by an external binary since writing
|
||||
// these values may require elevation.
|
||||
//
|
||||
// To allow multiple installations to coexist, identifiers written to the
|
||||
// registry include a hash of the installation path. This is referred to as
|
||||
// <PathHash> in the tables below.
|
||||
//
|
||||
// - File Extension Mappings
|
||||
// -----------------------
|
||||
// The following file extensions:
|
||||
// .htm .html .shtml .xht .xhtml
|
||||
// are mapped like so:
|
||||
//
|
||||
// HKCU\SOFTWARE\Classes\.<ext>\ (default) REG_SZ FirefoxHTML-<PathHash>
|
||||
//
|
||||
// as aliases to the class:
|
||||
//
|
||||
// HKCU\SOFTWARE\Classes\FirefoxHTML-<PathHash>\
|
||||
// DefaultIcon (default) REG_SZ <apppath>,1
|
||||
// shell\open\command (default) REG_SZ <apppath> -osint -url "%1"
|
||||
// shell\open\ddeexec (default) REG_SZ <empty string>
|
||||
//
|
||||
// - Windows Vista and above Protocol Handler
|
||||
//
|
||||
// HKCU\SOFTWARE\Classes\FirefoxURL-<PathHash>\
|
||||
// (default) REG_SZ <appname> URL
|
||||
// EditFlags REG_DWORD 2
|
||||
// FriendlyTypeName REG_SZ <appname> URL
|
||||
// DefaultIcon (default) REG_SZ <apppath>,1
|
||||
// shell\open\command (default) REG_SZ <apppath> -osint -url "%1"
|
||||
// shell\open\ddeexec (default) REG_SZ <empty string>
|
||||
//
|
||||
// - Protocol Mappings
|
||||
// -----------------
|
||||
// The following protocols:
|
||||
// HTTP, HTTPS, FTP
|
||||
// are mapped like so:
|
||||
//
|
||||
// HKCU\SOFTWARE\Classes\<protocol>\
|
||||
// DefaultIcon (default) REG_SZ <apppath>,1
|
||||
// shell\open\command (default) REG_SZ <apppath> -osint -url "%1"
|
||||
// shell\open\ddeexec (default) REG_SZ <empty string>
|
||||
//
|
||||
// - Windows Start Menu (XP SP1 and newer)
|
||||
// -------------------------------------------------
|
||||
// The following keys are set to make Firefox appear in the Start Menu as the
|
||||
// browser:
|
||||
//
|
||||
// HKCU\SOFTWARE\Clients\StartMenuInternet\<appname>-<PathHash>\
|
||||
// (default) REG_SZ <appname>
|
||||
// DefaultIcon (default) REG_SZ <apppath>,0
|
||||
// InstallInfo HideIconsCommand REG_SZ <uninstpath> /HideShortcuts
|
||||
// InstallInfo IconsVisible REG_DWORD 1
|
||||
// InstallInfo ReinstallCommand REG_SZ <uninstpath> /SetAsDefaultAppGlobal
|
||||
// InstallInfo ShowIconsCommand REG_SZ <uninstpath> /ShowShortcuts
|
||||
// shell\open\command (default) REG_SZ <apppath>
|
||||
// shell\properties (default) REG_SZ <appname> &Options
|
||||
// shell\properties\command (default) REG_SZ <apppath> -preferences
|
||||
// shell\safemode (default) REG_SZ <appname> &Safe Mode
|
||||
// shell\safemode\command (default) REG_SZ <apppath> -safe-mode
|
||||
//
|
||||
// - RegisteredApplications
|
||||
// -------------------------------------------------
|
||||
// This entry creates the listing in Default Apps for Windows 8 and up and in
|
||||
// Set Program Access and Defaults (SPAD) on older versions.
|
||||
//
|
||||
// HKCU\Software\RegisteredApplications\
|
||||
// Firefox-<PathHash> REG_SZ "Software\Clients\StartMenuInternet\<appname>-<PathHash>\Capabilities"
|
||||
// HKCU\Software\Clients\StartMenuInternet\<appname>-<PathHash>\Capabilities\
|
||||
// ApplicationDescription REG_SZ <branding description>
|
||||
// ApplicationIcon REG_SZ <apppath>,0
|
||||
// ApplicationName REG_SZ <appname>
|
||||
// FileAssociations .htm REG_SZ FirefoxHTML-<PathHash>
|
||||
// FileAssociations .html REG_SZ FirefoxHTML-<PathHash>
|
||||
// FileAssociations .shtml REG_SZ FirefoxHTML-<PathHash>
|
||||
// FileAssociations .xht REG_SZ FirefoxHTML-<PathHash>
|
||||
// FileAssociations .xhtml REG_SZ FirefoxHTML-<PathHash>
|
||||
// StartMenu StartMenuInternet REG_SZ <appname>-<PathHash>
|
||||
// URLAssociations ftp REG_SZ FirefoxURL-<PathHash>
|
||||
// URLAssociations http REG_SZ FirefoxURL-<PathHash>
|
||||
// URLAssociations https REG_SZ FirefoxURL-<PathHash>
|
||||
|
||||
// The values checked are all default values so the value name is not needed.
|
||||
typedef struct {
|
||||
const char* keyName;
|
||||
const char* valueData;
|
||||
const char* oldValueData;
|
||||
} SETTING;
|
||||
|
||||
#define APP_REG_NAME_BASE L"Firefox-"
|
||||
#define VAL_FILE_ICON "%APPPATH%,1"
|
||||
#define VAL_OPEN "\"%APPPATH%\" -osint -url \"%1\""
|
||||
#define OLD_VAL_OPEN "\"%APPPATH%\" -requestPending -osint -url \"%1\""
|
||||
#define DI "\\DefaultIcon"
|
||||
#define SOC "\\shell\\open\\command"
|
||||
#define SOD "\\shell\\open\\ddeexec"
|
||||
// Used for updating the FTP protocol handler's shell open command under HKCU.
|
||||
#define FTP_SOC L"Software\\Classes\\ftp\\shell\\open\\command"
|
||||
|
||||
#define MAKE_KEY_NAME1(PREFIX, MID) \
|
||||
PREFIX MID
|
||||
|
||||
// The DefaultIcon registry key value should never be used when checking if
|
||||
// Firefox is the default browser for file handlers since other applications
|
||||
// (e.g. MS Office) may modify the DefaultIcon registry key value to add Icon
|
||||
// Handlers. see http://msdn2.microsoft.com/en-us/library/aa969357.aspx for
|
||||
// more info. The FTP protocol is not checked so advanced users can set the FTP
|
||||
// handler to another application and still have Firefox check if it is the
|
||||
// default HTTP and HTTPS handler.
|
||||
// *** Do not add additional checks here unless you skip them when aForAllTypes
|
||||
// is false below***.
|
||||
static SETTING gSettings[] = {
|
||||
// File Handler Class
|
||||
// ***keep this as the first entry because when aForAllTypes is not set below
|
||||
// it will skip over this check.***
|
||||
{ MAKE_KEY_NAME1("FirefoxHTML", SOC), VAL_OPEN, OLD_VAL_OPEN },
|
||||
|
||||
// Protocol Handler Class - for Vista and above
|
||||
{ MAKE_KEY_NAME1("FirefoxURL", SOC), VAL_OPEN, OLD_VAL_OPEN },
|
||||
|
||||
// Protocol Handlers
|
||||
{ MAKE_KEY_NAME1("HTTP", DI), VAL_FILE_ICON },
|
||||
{ MAKE_KEY_NAME1("HTTP", SOC), VAL_OPEN, OLD_VAL_OPEN },
|
||||
{ MAKE_KEY_NAME1("HTTPS", DI), VAL_FILE_ICON },
|
||||
{ MAKE_KEY_NAME1("HTTPS", SOC), VAL_OPEN, OLD_VAL_OPEN }
|
||||
};
|
||||
|
||||
// The settings to disable DDE are separate from the default browser settings
|
||||
// since they are only checked when Firefox is the default browser and if they
|
||||
// are incorrect they are fixed without notifying the user.
|
||||
static SETTING gDDESettings[] = {
|
||||
// File Handler Class
|
||||
{ MAKE_KEY_NAME1("Software\\Classes\\FirefoxHTML", SOD) },
|
||||
|
||||
// Protocol Handler Class - for Vista and above
|
||||
{ MAKE_KEY_NAME1("Software\\Classes\\FirefoxURL", SOD) },
|
||||
|
||||
// Protocol Handlers
|
||||
{ MAKE_KEY_NAME1("Software\\Classes\\FTP", SOD) },
|
||||
{ MAKE_KEY_NAME1("Software\\Classes\\HTTP", SOD) },
|
||||
{ MAKE_KEY_NAME1("Software\\Classes\\HTTPS", SOD) }
|
||||
};
|
||||
|
||||
nsresult
|
||||
GetHelperPath(nsAutoString& aPath)
|
||||
{
|
||||
|
@ -373,29 +229,6 @@ IsAARDefault(const RefPtr<IApplicationAssociationRegistration>& pAAR,
|
|||
return isDefault;
|
||||
}
|
||||
|
||||
static void
|
||||
IsDefaultBrowserWin8(bool aCheckAllTypes, bool* aIsDefaultBrowser)
|
||||
{
|
||||
RefPtr<IApplicationAssociationRegistration> pAAR;
|
||||
HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
|
||||
nullptr,
|
||||
CLSCTX_INPROC,
|
||||
IID_IApplicationAssociationRegistration,
|
||||
getter_AddRefs(pAAR));
|
||||
if (FAILED(hr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool res = IsAARDefault(pAAR, L"http");
|
||||
if (*aIsDefaultBrowser) {
|
||||
*aIsDefaultBrowser = res;
|
||||
}
|
||||
res = IsAARDefault(pAAR, L".html");
|
||||
if (*aIsDefaultBrowser && aCheckAllTypes) {
|
||||
*aIsDefaultBrowser = res;
|
||||
}
|
||||
}
|
||||
|
||||
static nsresult
|
||||
GetAppRegName(nsAutoString &aAppRegName)
|
||||
{
|
||||
|
@ -414,247 +247,42 @@ GetAppRegName(nsAutoString &aAppRegName)
|
|||
rv = exeFile->GetParent(getter_AddRefs(appDir));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoString path;
|
||||
rv = appDir->GetPath(path);
|
||||
nsAutoString appDirStr;
|
||||
rv = appDir->GetPath(appDirStr);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
uint64_t hash = CityHash64(static_cast<const char *>(path.get()),
|
||||
path.Length() * sizeof(nsAutoString::char_type));
|
||||
|
||||
aAppRegName = APP_REG_NAME_BASE;
|
||||
aAppRegName.AppendInt((int)hash, 16);
|
||||
uint64_t hash = CityHash64(static_cast<const char *>(appDirStr.get()),
|
||||
appDirStr.Length() * sizeof(nsAutoString::char_type));
|
||||
aAppRegName.AppendInt((int)(hash >> 32), 16);
|
||||
aAppRegName.AppendInt((int)hash, 16);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* Query's the AAR for the default status.
|
||||
* This only checks for FirefoxURL and if aCheckAllTypes is set, then
|
||||
* it also checks for FirefoxHTML. Note that those ProgIDs are shared
|
||||
* by all Firefox browsers.
|
||||
*/
|
||||
bool
|
||||
nsWindowsShellService::IsDefaultBrowserVista(bool aCheckAllTypes,
|
||||
bool* aIsDefaultBrowser)
|
||||
{
|
||||
RefPtr<IApplicationAssociationRegistration> pAAR;
|
||||
HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
|
||||
nullptr,
|
||||
CLSCTX_INPROC,
|
||||
IID_IApplicationAssociationRegistration,
|
||||
getter_AddRefs(pAAR));
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aCheckAllTypes) {
|
||||
BOOL res;
|
||||
nsAutoString appRegName;
|
||||
GetAppRegName(appRegName);
|
||||
hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE,
|
||||
appRegName.get(),
|
||||
&res);
|
||||
*aIsDefaultBrowser = res;
|
||||
} else if (!IsWin8OrLater()) {
|
||||
*aIsDefaultBrowser = IsAARDefault(pAAR, L"http");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWindowsShellService::IsDefaultBrowser(bool aStartupCheck,
|
||||
bool aForAllTypes,
|
||||
bool* aIsDefaultBrowser)
|
||||
{
|
||||
// Assume we're the default unless one of the several checks below tell us
|
||||
// otherwise.
|
||||
*aIsDefaultBrowser = true;
|
||||
mozilla::Unused << aStartupCheck;
|
||||
|
||||
wchar_t exePath[MAX_BUF];
|
||||
if (!::GetModuleFileNameW(0, exePath, MAX_BUF))
|
||||
return NS_ERROR_FAILURE;
|
||||
*aIsDefaultBrowser = false;
|
||||
|
||||
// Convert the path to a long path since GetModuleFileNameW returns the path
|
||||
// that was used to launch Firefox which is not necessarily a long path.
|
||||
if (!::GetLongPathNameW(exePath, exePath, MAX_BUF))
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
nsAutoString appLongPath(exePath);
|
||||
|
||||
HKEY theKey;
|
||||
DWORD res;
|
||||
nsresult rv;
|
||||
wchar_t currValue[MAX_BUF];
|
||||
|
||||
SETTING* settings = gSettings;
|
||||
if (!aForAllTypes && IsWin8OrLater()) {
|
||||
// Skip over the file handler check
|
||||
settings++;
|
||||
RefPtr<IApplicationAssociationRegistration> pAAR;
|
||||
HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
|
||||
nullptr,
|
||||
CLSCTX_INPROC,
|
||||
IID_IApplicationAssociationRegistration,
|
||||
getter_AddRefs(pAAR));
|
||||
if (FAILED(hr)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
SETTING* end = gSettings + sizeof(gSettings) / sizeof(SETTING);
|
||||
|
||||
for (; settings < end; ++settings) {
|
||||
NS_ConvertUTF8toUTF16 keyName(settings->keyName);
|
||||
NS_ConvertUTF8toUTF16 valueData(settings->valueData);
|
||||
int32_t offset = valueData.Find("%APPPATH%");
|
||||
valueData.Replace(offset, 9, appLongPath);
|
||||
|
||||
rv = OpenKeyForReading(HKEY_CLASSES_ROOT, keyName, &theKey);
|
||||
if (NS_FAILED(rv)) {
|
||||
*aIsDefaultBrowser = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
::ZeroMemory(currValue, sizeof(currValue));
|
||||
DWORD len = sizeof currValue;
|
||||
res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr,
|
||||
(LPBYTE)currValue, &len);
|
||||
// Close the key that was opened.
|
||||
::RegCloseKey(theKey);
|
||||
if (REG_FAILED(res) ||
|
||||
_wcsicmp(valueData.get(), currValue)) {
|
||||
// Key wasn't set or was set to something other than our registry entry.
|
||||
NS_ConvertUTF8toUTF16 oldValueData(settings->oldValueData);
|
||||
offset = oldValueData.Find("%APPPATH%");
|
||||
oldValueData.Replace(offset, 9, appLongPath);
|
||||
// The current registry value doesn't match the current or the old format.
|
||||
if (_wcsicmp(oldValueData.get(), currValue)) {
|
||||
*aIsDefaultBrowser = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
res = ::RegOpenKeyExW(HKEY_CLASSES_ROOT, keyName.get(),
|
||||
0, KEY_SET_VALUE, &theKey);
|
||||
if (REG_FAILED(res)) {
|
||||
// If updating the open command fails try to update it using the helper
|
||||
// application when setting Firefox as the default browser.
|
||||
*aIsDefaultBrowser = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
res = ::RegSetValueExW(theKey, L"", 0, REG_SZ,
|
||||
(const BYTE *) valueData.get(),
|
||||
(valueData.Length() + 1) * sizeof(char16_t));
|
||||
// Close the key that was created.
|
||||
::RegCloseKey(theKey);
|
||||
if (REG_FAILED(res)) {
|
||||
// If updating the open command fails try to update it using the helper
|
||||
// application when setting Firefox as the default browser.
|
||||
*aIsDefaultBrowser = false;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only check if Firefox is the default browser on Vista and above if the
|
||||
// previous checks show that Firefox is the default browser.
|
||||
if (*aIsDefaultBrowser) {
|
||||
IsDefaultBrowserVista(aForAllTypes, aIsDefaultBrowser);
|
||||
if (IsWin8OrLater()) {
|
||||
IsDefaultBrowserWin8(aForAllTypes, aIsDefaultBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
// To handle the case where DDE isn't disabled due for a user because there
|
||||
// account didn't perform a Firefox update this will check if Firefox is the
|
||||
// default browser and if dde is disabled for each handler
|
||||
// and if it isn't disable it. When Firefox is not the default browser the
|
||||
// helper application will disable dde for each handler.
|
||||
*aIsDefaultBrowser = IsAARDefault(pAAR, L"http");
|
||||
if (*aIsDefaultBrowser && aForAllTypes) {
|
||||
// Check ftp settings
|
||||
|
||||
end = gDDESettings + sizeof(gDDESettings) / sizeof(SETTING);
|
||||
|
||||
for (settings = gDDESettings; settings < end; ++settings) {
|
||||
NS_ConvertUTF8toUTF16 keyName(settings->keyName);
|
||||
|
||||
rv = OpenKeyForReading(HKEY_CURRENT_USER, keyName, &theKey);
|
||||
if (NS_FAILED(rv)) {
|
||||
::RegCloseKey(theKey);
|
||||
// If disabling DDE fails try to disable it using the helper
|
||||
// application when setting Firefox as the default browser.
|
||||
*aIsDefaultBrowser = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
::ZeroMemory(currValue, sizeof(currValue));
|
||||
DWORD len = sizeof currValue;
|
||||
res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr,
|
||||
(LPBYTE)currValue, &len);
|
||||
// Close the key that was opened.
|
||||
::RegCloseKey(theKey);
|
||||
if (REG_FAILED(res) || char16_t('\0') != *currValue) {
|
||||
// Key wasn't set or was set to something other than our registry entry.
|
||||
// Delete the key along with all of its childrean and then recreate it.
|
||||
::SHDeleteKeyW(HKEY_CURRENT_USER, keyName.get());
|
||||
res = ::RegCreateKeyExW(HKEY_CURRENT_USER, keyName.get(), 0, nullptr,
|
||||
REG_OPTION_NON_VOLATILE, KEY_SET_VALUE,
|
||||
nullptr, &theKey, nullptr);
|
||||
if (REG_FAILED(res)) {
|
||||
// If disabling DDE fails try to disable it using the helper
|
||||
// application when setting Firefox as the default browser.
|
||||
*aIsDefaultBrowser = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
res = ::RegSetValueExW(theKey, L"", 0, REG_SZ, (const BYTE *) L"",
|
||||
sizeof(char16_t));
|
||||
// Close the key that was created.
|
||||
::RegCloseKey(theKey);
|
||||
if (REG_FAILED(res)) {
|
||||
// If disabling DDE fails try to disable it using the helper
|
||||
// application when setting Firefox as the default browser.
|
||||
*aIsDefaultBrowser = false;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the FTP protocol handler's shell open command if it is the old
|
||||
// format.
|
||||
res = ::RegOpenKeyExW(HKEY_CURRENT_USER, FTP_SOC, 0, KEY_ALL_ACCESS,
|
||||
&theKey);
|
||||
// Don't update the FTP protocol handler's shell open command when opening
|
||||
// its registry key fails under HKCU since it most likely doesn't exist.
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_ConvertUTF8toUTF16 oldValueOpen(OLD_VAL_OPEN);
|
||||
int32_t offset = oldValueOpen.Find("%APPPATH%");
|
||||
oldValueOpen.Replace(offset, 9, appLongPath);
|
||||
|
||||
::ZeroMemory(currValue, sizeof(currValue));
|
||||
DWORD len = sizeof currValue;
|
||||
res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr, (LPBYTE)currValue,
|
||||
&len);
|
||||
|
||||
// Don't update the FTP protocol handler's shell open command when the
|
||||
// current registry value doesn't exist or matches the old format.
|
||||
if (REG_FAILED(res) ||
|
||||
_wcsicmp(oldValueOpen.get(), currValue)) {
|
||||
::RegCloseKey(theKey);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_ConvertUTF8toUTF16 valueData(VAL_OPEN);
|
||||
valueData.Replace(offset, 9, appLongPath);
|
||||
res = ::RegSetValueExW(theKey, L"", 0, REG_SZ,
|
||||
(const BYTE *) valueData.get(),
|
||||
(valueData.Length() + 1) * sizeof(char16_t));
|
||||
// Close the key that was created.
|
||||
::RegCloseKey(theKey);
|
||||
// If updating the FTP protocol handlers shell open command fails try to
|
||||
// update it using the helper application when setting Firefox as the
|
||||
// default browser.
|
||||
if (REG_FAILED(res)) {
|
||||
*aIsDefaultBrowser = false;
|
||||
}
|
||||
*aIsDefaultBrowser = IsAARDefault(pAAR, L".html");
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ public:
|
|||
NS_DECL_NSIWINDOWSSHELLSERVICE
|
||||
|
||||
protected:
|
||||
bool IsDefaultBrowserVista(bool aCheckAllTypes, bool* aIsDefaultBrowser);
|
||||
nsresult LaunchControlPanelDefaultsSelectionUI();
|
||||
nsresult LaunchControlPanelDefaultPrograms();
|
||||
nsresult LaunchModernSettingsDialogDefaultApps();
|
||||
|
|
Загрузка…
Ссылка в новой задаче