Bug 1750991: Automatically create shortcut if it's not already present when pinning to the Windows Taskbar. r=mhowell

There's some general improvements to CreateShortcut here: writing them out to a log, setting the working directory, allowing the icon to be customized. To support the shortcuts log, I had to restrict how shortcut locations are specified to be a KNOWNFOLDERID -- I couldn't think of any other way to make sure we accurately record them. Notably, this code has been unused since the Site Specific Browsing work was backed out - so there's no existing callers to worry about breaking.

This patch also enhances the pin to taskbar code to automatically create a shortcut if none exist (this is how we'll want things to work for the Private Browsing shortcuts, and it seemed like a generally useful thing to do).

Differential Revision: https://phabricator.services.mozilla.com/D138197
This commit is contained in:
Ben Hearsum
2022-03-17 16:33:34 +00:00
parent ea0e6b5694
commit 9c23b7557f
3 changed files with 163 additions and 19 deletions

View File

@@ -55,6 +55,7 @@ elif CONFIG["OS_ARCH"] == "WINNT":
]
LOCAL_INCLUDES += [
"../../../other-licenses/nsis/Contrib/CityHash/cityhash",
"/toolkit/xre",
]
OS_LIBS += [
"bcrypt",

View File

@@ -9,9 +9,47 @@ interface nsIFile;
[scriptable, uuid(fb9b59db-5a91-4e67-92b6-35e7d6e6d3fd)]
interface nsIWindowsShellService : nsISupports
{
void createShortcut(in nsIFile aBinary, in Array<AString> aArguments,
in AString aDescription, in nsIFile aIconFile, in AString aAppUserModelId,
in nsIFile aTarget);
/*
* Creates a new shortcut (.lnk) file. This shortcut will be recorded in
* a new shortcuts log file located in %PROGRAMDATA%\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38
* that is named after the currently running application and current user, eg:
* Firefox_user123_shortcuts.ini.
*
* For reasons that we haven't been able to pin down, these shortcuts get created with
* extra metadata on them (KnownFolderDataBlock, SpecialFolderDataBlock) that cause
* the Windows ShellLink classes to improperly read their target path with certain
* parameters. This causes any 32-bit programs that read the links (such as our
* installer and uninstaller) to think that 64-bit installs are located in the 32-bit
* Program Files directory.
* See https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/6f2e7920-50a9-459d-bfdd-316e459e87c0/ishelllink-getpath-returns-wrong-folder-for-64-bit-application-when-called-from-32-bit-application
* for some additional discussion of this.
*
* @param aBinary Target file of the shortcut.
* @param aArguments Arguments to set for the shortcut. May be empty.
* @param aDescription The description of the shortcut. The string used here
* shows up as the hover text of the shortcut in Explorer and on the
* Taskbar (if the shortcut is pinned there).
* @param aIconFile The file containing the desired icon for the shortcut. This
* can be the same file as aBinary.
* @param aIconIndex The index of the in aIconFile. Note that this is 0 based index
* that IShellLinkW requires, _not_ a Resource ID that is sometimes used
* for icons.
* @param aAppUserModelId The App User Model ID to set for the shortcut. This will
* affect which icon on the Taskbar the application groups with when first
* launched.
* @param aShortcutFolder The special Windows folder to create the shortcut in. One of:
* CommonStartMenu, StartMenu, PublicDesktop, Desktop, or QuickLaunch.
* @param aShortcutName The filename of the shortcut within aShortcutFolder.
* @return The full native path to the created shortcut.
*
* @throws NS_ERROR_INVALID_ARG if an invalid shortcut folder is passed
* @throws NS_ERROR_FILE_NOT_FOUND if the shortcut file or shortcuts log cannot be
* created or accessed
* @throws NS_ERROR_FAILURE for other types of failures
*/
AString createShortcut(in nsIFile aBinary, in Array<AString> aArguments,
in AString aDescription, in nsIFile aIconFile, in unsigned short aIconIndex,
in AString aAppUserModelId, in AString aShortcutFolder, in AString aShortcutName);
/*
* Pin the current app to the taskbar. If aPrivateBrowsing is true, the
@@ -20,10 +58,10 @@ interface nsIWindowsShellService : nsISupports
*
* This MUST only be used in response to an active request from the user.
*
* Uses an existing shortcut on the Desktop or Start Menu, which would have
* been created by the installer (for All Users or Current User), in order
* to ensure that the pin is associated with this executable and AUMID for
* proper launching and grouping.
* If it exists, uses an existing shortcut on the Desktop or Start Menu,
* which would have been created by the installer (for All Users or
* Current User). If none can be found, one will be created with the correct
* AUMID for proper launching and grouping.
*
* NOTE: This method probably shouldn't be used on the main thread, it
* performs blocking disk I/O.

View File

@@ -35,6 +35,7 @@
#include "nsLocalFile.h"
#include "nsIXULAppInfo.h"
#include "nsINIParser.h"
#include "nsNativeAppSupportWin.h"
#include <windows.h>
#include <shellapi.h>
@@ -730,15 +731,16 @@ static nsresult WriteShortcutToLog(KNOWNFOLDERID aFolderId,
return NS_OK;
}
NS_IMETHODIMP
nsWindowsShellService::CreateShortcut(nsIFile* aBinary,
const nsTArray<nsString>& aArguments,
const nsAString& aDescription,
nsIFile* aIconFile,
const nsAString& aAppUserModelId,
nsIFile* aTarget) {
static nsresult CreateShortcutImpl(
nsIFile* aBinary, const nsTArray<nsString>& aArguments,
const nsAString& aDescription, nsIFile* aIconFile, uint16_t aIconIndex,
const nsAString& aAppUserModelId, KNOWNFOLDERID aShortcutFolder,
const nsAString& aShortcutName, nsAString& aShortcutPath) {
NS_ENSURE_ARG(aBinary);
NS_ENSURE_ARG(aTarget);
NS_ENSURE_ARG(aIconFile);
nsresult rv = WriteShortcutToLog(aShortcutFolder, aShortcutName);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<IShellLinkW> link;
HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
@@ -748,6 +750,11 @@ nsWindowsShellService::CreateShortcut(nsIFile* aBinary,
nsString path(aBinary->NativePath());
link->SetPath(path.get());
wchar_t workingDir[MAX_PATH + 1];
wcscpy_s(workingDir, MAX_PATH + 1, aBinary->NativePath().get());
PathRemoveFileSpecW(workingDir);
link->SetWorkingDirectory(workingDir);
if (!aDescription.IsEmpty()) {
link->SetDescription(PromiseFlatString(aDescription).get());
}
@@ -762,7 +769,7 @@ nsWindowsShellService::CreateShortcut(nsIFile* aBinary,
if (aIconFile) {
nsString icon(aIconFile->NativePath());
link->SetIconLocation(icon.get(), 0);
link->SetIconLocation(icon.get(), aIconIndex);
}
if (!aAppUserModelId.IsEmpty()) {
@@ -788,13 +795,59 @@ nsWindowsShellService::CreateShortcut(nsIFile* aBinary,
hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(persist));
NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
nsString target(aTarget->NativePath());
nsCOMPtr<nsIProperties> directoryService =
do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> shortcutFile;
if (aShortcutFolder == FOLDERID_StartMenu) {
rv = directoryService->Get(NS_WIN_PROGRAMS_DIR, NS_GET_IID(nsIFile),
getter_AddRefs(shortcutFile));
} else if (aShortcutFolder == FOLDERID_Desktop) {
rv = directoryService->Get(NS_OS_DESKTOP_DIR, NS_GET_IID(nsIFile),
getter_AddRefs(shortcutFile));
} else {
return NS_ERROR_FILE_NOT_FOUND;
}
if (NS_FAILED(rv)) {
return NS_ERROR_FILE_NOT_FOUND;
}
shortcutFile->Append(aShortcutName);
nsString target(shortcutFile->NativePath());
hr = persist->Save(target.get(), TRUE);
NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
aShortcutPath.Assign(target);
return NS_OK;
}
NS_IMETHODIMP
nsWindowsShellService::CreateShortcut(
nsIFile* aBinary, const nsTArray<nsString>& aArguments,
const nsAString& aDescription, nsIFile* aIconFile, uint16_t aIconIndex,
const nsAString& aAppUserModelId, const nsAString& aShortcutFolder,
const nsAString& aShortcutName, nsAString& aShortcutPath) {
// In an ideal world we'd probably send along nsIFile pointers
// here, but it's easier to determine the needed shortcuts log
// entry with a KNOWNFOLDERID - so we pass this along instead
// and let CreateShortcutImpl take care of converting it to
// an nsIFile.
KNOWNFOLDERID folderId;
if (aShortcutFolder.Equals(L"StartMenu")) {
folderId = FOLDERID_StartMenu;
} else if (aShortcutFolder.Equals(L"Desktop")) {
folderId = FOLDERID_Desktop;
} else {
return NS_ERROR_INVALID_ARG;
}
return CreateShortcutImpl(aBinary, aArguments, aDescription, aIconFile,
aIconIndex, aAppUserModelId, folderId,
aShortcutName, aShortcutPath);
}
// Constructs a path to an installer-created shortcut, under a directory
// specified by a CSIDL.
static nsresult GetShortcutPath(int aCSIDL, bool aPrivateBrowsing,
@@ -824,7 +877,7 @@ static nsresult GetShortcutPath(int aCSIDL, bool aPrivateBrowsing,
//
// Private shortcuts are not created by the installer (they're created
// upon user request, ultimately by CreateShortcutImpl, and recorded in
// a separate shortcuts log. As with non-private shortcutsthey have a known
// a separate shortcuts log. As with non-private shortcuts they have a known
// name - so there's no need to look through logs to find them.
if (aPrivateBrowsing) {
// This is explicitly not localized until we finalize the English string.
@@ -1033,7 +1086,59 @@ static nsresult PinCurrentAppToTaskbarImpl(bool aCheckOnly,
}
}
if (shortcutPath.IsEmpty()) {
return NS_ERROR_FILE_NOT_FOUND;
if (aCheckOnly) {
// The other checks we do further down can only be done if
// a shortcut already exists. We don't want to do that in
// check only mode, so we just have to assume it will work...
return NS_OK;
}
nsAutoString desc;
nsTArray<nsString> arguments;
if (aPrivateBrowsing) {
nsAutoString arg;
// Localization disabled until we finalize the English string.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1758961 tracks following
// up on this before it becomes user visible.
/*nsAutoCString pbStr;
IgnoredErrorResult rv;
nsTArray<nsCString> resIds{
"branding/brand.ftl"_ns,
"browser/browser.ftl"_ns,
};
RefPtr<Localization> l10n = Localization::Create(resIds, true);
l10n->FormatValueSync("private-browsing-shortcut-text"_ns, {}, pbStr, rv);
desc.Assign(NS_ConvertUTF8toUTF16(pbStr));*/
desc.AssignLiteral(MOZ_APP_DISPLAYNAME " Private Browsing");
arg.AssignLiteral("-private-window");
arguments.AppendElement(arg);
} else {
desc.AssignLiteral(MOZ_APP_DISPLAYNAME);
}
nsAutoString linkName(desc);
linkName.AppendLiteral(".lnk");
nsAutoString exeStr;
nsCOMPtr<nsIFile> exeFile;
exeStr.Assign(exePath);
nsresult rv = NS_NewLocalFile(exeStr, true, getter_AddRefs(exeFile));
if (!NS_SUCCEEDED(rv)) {
return NS_ERROR_FILE_NOT_FOUND;
}
uint16_t iconIndex = aPrivateBrowsing ? IDI_PBMODE : IDI_APPICON;
// Icon indexes are defined as Resource IDs, but CreateShortcutImpl
// needs an index.
iconIndex--;
rv = CreateShortcutImpl(exeFile, arguments, desc, exeFile, iconIndex, aumid,
FOLDERID_StartMenu, linkName, shortcutPath);
if (!NS_SUCCEEDED(rv)) {
return NS_ERROR_FILE_NOT_FOUND;
}
}
mozilla::UniquePtr<__unaligned ITEMIDLIST, ILFreeDeleter> path(