Bug 1687562 - Part 1: Implement Taskbar pinning. r=mhowell

Differential Revision: https://phabricator.services.mozilla.com/D104779
This commit is contained in:
Adam Gashlin 2021-02-17 05:10:53 +00:00
Родитель 826df957d6
Коммит 1d35ccd566
4 изменённых файлов: 309 добавлений и 1 удалений

Просмотреть файл

@ -56,6 +56,9 @@ elif CONFIG["OS_ARCH"] == "WINNT":
LOCAL_INCLUDES += [
"../../../other-licenses/nsis/Contrib/CityHash/cityhash",
]
OS_LIBS += [
"propsys",
]
XPIDL_MODULE = "shellservice"
@ -68,7 +71,7 @@ EXTRA_JS_MODULES += [
"ShellService.jsm",
]
for var in ("MOZ_APP_NAME", "MOZ_APP_VERSION"):
for var in ("MOZ_APP_DISPLAYNAME", "MOZ_APP_NAME", "MOZ_APP_VERSION"):
DEFINES[var] = '"%s"' % CONFIG[var]
CXXFLAGS += CONFIG["TK_CFLAGS"]

Просмотреть файл

@ -12,4 +12,47 @@ interface nsIWindowsShellService : nsISupports
void createShortcut(in nsIFile aBinary, in Array<AString> aArguments,
in AString aDescription, in nsIFile aIconFile, in AString aAppUserModelId,
in nsIFile aTarget);
/*
* Pin the current app to the taskbar
*
* 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.
*
* NOTE: This method probably shouldn't be used on the main thread, it
* performs blocking disk I/O.
*
* NOTE: It is possible for the shortcut match to fail even when a
* shortcut refers to the current executable, if the paths differ due
* to e.g. symlinks. This should be rare.
*
* This will definitely fail on an OS before Windows 10 build 1809
* (October 2018 Update).
*
* @throws NS_ERROR_NOT_AVAILABLE
* if OS is not at least Windows 10 build 1809, or if creating the
* Taskband Pin object fails
* @throws NS_ERROR_FILE_NOT_FOUND
* if a shortcut matching this app's AUMID and exe path wasn't found
* @throws NS_ERROR_FAILURE
* for unexpected errors
*/
void pinCurrentAppToTaskbar();
/*
* Do a dry run of pinCurrentAppToTaskbar()
*
* This does all the same checks and setup, throws the same errors, but doesn't
* do the final step of creating the pin.
*
* NOTE: This method probably shouldn't be used on the main thread, it
* performs blocking disk I/O.
*
* @throws same as pinCurrentAppToTaskbar()
*/
void checkPinCurrentAppToTaskbar();
};

Просмотреть файл

@ -35,8 +35,16 @@
#include <propvarutil.h>
#include <propkey.h>
#ifdef __MINGW32__
// MinGW-w64 headers are missing PropVariantToString.
# include <propsys.h>
PSSTDAPI PropVariantToString(REFPROPVARIANT propvar, PWSTR psz, UINT cch);
#endif
#include <objbase.h>
#include <shlobj.h>
#include "WinUtils.h"
#include "mozilla/widget/WinTaskbar.h"
#include <mbstring.h>
@ -621,6 +629,259 @@ nsWindowsShellService::CreateShortcut(nsIFile* aBinary,
return NS_OK;
}
// Constructs a path to an installer-created shortcut, under a directory
// specified by a CSIDL.
static nsresult GetShortcutPath(int aCSIDL, /* out */ nsAutoString& aPath) {
wchar_t folderPath[MAX_PATH] = {};
HRESULT hr = SHGetFolderPathW(nullptr, aCSIDL, nullptr, SHGFP_TYPE_CURRENT,
folderPath);
if (NS_WARN_IF(FAILED(hr))) {
return NS_ERROR_FAILURE;
}
aPath.Assign(folderPath);
if (NS_WARN_IF(aPath.IsEmpty())) {
return NS_ERROR_FAILURE;
}
if (aPath[aPath.Length() - 1] != '\\') {
aPath.AppendLiteral("\\");
}
// NOTE: In the installer, shortcuts are named "${BrandShortName}.lnk".
// This is set from MOZ_APP_DISPLAYNAME in defines.nsi.in. (Except in dev
// edition where it's explicitly set to "Firefox Developer Edition" in
// branding.nsi, which matches MOZ_APP_DISPLAYNAME in aurora/configure.sh.)
//
// If this changes, we could expand this to check shortcuts_log.ini,
// which records the name of the shortcuts as created by the installer.
aPath.AppendLiteral(MOZ_APP_DISPLAYNAME ".lnk");
return NS_OK;
}
// Check if the instaler-created shortcut in the given location matches,
// if so output its path
//
// NOTE: DO NOT USE if a false negative (mismatch) is unacceptable.
// aExePath is compared directly to the path retrieved from the shortcut.
// Due to the presence of symlinks or other filesystem issues, it's possible
// for different paths to refer to the same file, which would cause the check
// to fail.
// This should rarely be an issue as we are most likely to be run from a path
// written by the installer (shortcut, association, launch from installer),
// which also wrote the shortcuts. But it is possible.
//
// aCSIDL the CSIDL of the directory containing the shortcut to check.
// aAUMID the AUMID to check for
// aExePath the target exe path to check for, should be a long path where
// possible
// aShortcutPath outparam, set to matching shortcut path if NS_OK is returned.
//
// Returns
// NS_ERROR_FAILURE on errors before the shortcut was loaded
// NS_ERROR_FILE_NOT_FOUND if the shortcut doesn't exist
// NS_ERROR_FILE_ALREADY_EXISTS if the shortcut exists but doesn't match the
// current app
// NS_OK if the shortcut matches
static nsresult GetMatchingShortcut(int aCSIDL, const nsAutoString& aAUMID,
const wchar_t aExePath[MAXPATHLEN],
/* out */ nsAutoString& aShortcutPath) {
nsresult result = NS_ERROR_FAILURE;
nsAutoString path;
nsresult rv = GetShortcutPath(aCSIDL, path);
if (NS_WARN_IF(NS_FAILED(rv))) {
return result;
}
// Create a shell link object for loading the shortcut
RefPtr<IShellLinkW> link;
HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
IID_IShellLinkW, getter_AddRefs(link));
if (NS_WARN_IF(FAILED(hr))) {
return result;
}
// Load
RefPtr<IPersistFile> persist;
hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(persist));
if (NS_WARN_IF(FAILED(hr))) {
return result;
}
hr = persist->Load(path.get(), STGM_READ);
if (FAILED(hr)) {
if (NS_WARN_IF(hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))) {
// empty branch, result unchanged but warning issued
} else {
result = NS_ERROR_FILE_NOT_FOUND;
}
return result;
}
result = NS_ERROR_FILE_ALREADY_EXISTS;
// Check the AUMID
RefPtr<IPropertyStore> propStore;
hr = link->QueryInterface(IID_IPropertyStore, getter_AddRefs(propStore));
if (NS_WARN_IF(FAILED(hr))) {
return result;
}
PROPVARIANT pv;
hr = propStore->GetValue(PKEY_AppUserModel_ID, &pv);
if (NS_WARN_IF(FAILED(hr))) {
return result;
}
wchar_t storedAUMID[MAX_PATH];
hr = PropVariantToString(pv, storedAUMID, MAX_PATH);
PropVariantClear(&pv);
if (NS_WARN_IF(FAILED(hr))) {
return result;
}
if (!aAUMID.Equals(storedAUMID)) {
return result;
}
// Check the exe path
static_assert(MAXPATHLEN == MAX_PATH);
wchar_t storedExePath[MAX_PATH] = {};
// With no flags GetPath gets a long path
hr = link->GetPath(storedExePath, ArrayLength(storedExePath), nullptr, 0);
if (FAILED(hr) || hr == S_FALSE) {
return result;
}
// Case insensitive path comparison
if (wcsnicmp(storedExePath, aExePath, MAXPATHLEN) != 0) {
return result;
}
// Success, report the shortcut path
aShortcutPath.Assign(path);
result = NS_OK;
return result;
}
static nsresult PinCurrentAppToTaskbarImpl(bool aCheckOnly) {
// This enum is likely only used for Windows telemetry, INT_MAX is chosen to
// avoid confusion with existing uses.
enum PINNEDLISTMODIFYCALLER { PLMC_INT_MAX = INT_MAX };
// The types below, and the idea of using IPinnedList3::Modify,
// are thanks to Gee Law <https://geelaw.blog/entries/msedge-pins/>
static constexpr GUID CLSID_TaskbandPin = {
0x90aa3a4e,
0x1cba,
0x4233,
{0xb8, 0xbb, 0x53, 0x57, 0x73, 0xd4, 0x84, 0x49}};
static constexpr GUID IID_IPinnedList3 = {
0x0dd79ae2,
0xd156,
0x45d4,
{0x9e, 0xeb, 0x3b, 0x54, 0x97, 0x69, 0xe9, 0x40}};
struct IPinnedList3Vtbl;
struct IPinnedList3 {
IPinnedList3Vtbl* vtbl;
};
typedef ULONG STDMETHODCALLTYPE ReleaseFunc(IPinnedList3 * that);
typedef HRESULT STDMETHODCALLTYPE ModifyFunc(
IPinnedList3 * that, PCIDLIST_ABSOLUTE unpin, PCIDLIST_ABSOLUTE pin,
PINNEDLISTMODIFYCALLER caller);
struct IPinnedList3Vtbl {
void* QueryInterface; // 0
void* AddRef; // 1
ReleaseFunc* Release; // 2
void* Other[13]; // 3-15
ModifyFunc* Modify; // 16
};
struct ILFreeDeleter {
void operator()(LPITEMIDLIST aPtr) {
if (aPtr) {
ILFree(aPtr);
}
}
};
// First available on 1809
if (!IsWin10Sep2018UpdateOrLater()) {
return NS_ERROR_NOT_AVAILABLE;
}
nsAutoString aumid;
if (NS_WARN_IF(!mozilla::widget::WinTaskbar::GetAppUserModelID(aumid))) {
return NS_ERROR_FAILURE;
}
wchar_t exePath[MAXPATHLEN] = {};
if (NS_WARN_IF(NS_FAILED(BinaryPath::GetLong(exePath)))) {
return NS_ERROR_FAILURE;
}
// Try to find a shortcut matching the running app
nsAutoString shortcutPath;
int shortcutCSIDLs[] = {CSIDL_COMMON_PROGRAMS, CSIDL_PROGRAMS,
CSIDL_COMMON_DESKTOPDIRECTORY,
CSIDL_DESKTOPDIRECTORY};
for (int i = 0; i < ArrayLength(shortcutCSIDLs); ++i) {
// GetMatchingShortcut may fail when the exe path doesn't match, even
// if it refers to the same file. This should be rare, and the worst
// outcome would be failure to pin, so the risk is acceptable.
nsresult rv =
GetMatchingShortcut(shortcutCSIDLs[i], aumid, exePath, shortcutPath);
if (NS_SUCCEEDED(rv)) {
break;
} else {
shortcutPath.Truncate();
}
}
if (shortcutPath.IsEmpty()) {
return NS_ERROR_FILE_NOT_FOUND;
}
mozilla::UniquePtr<__unaligned ITEMIDLIST, ILFreeDeleter> path(
ILCreateFromPathW(shortcutPath.get()));
if (NS_WARN_IF(!path)) {
return NS_ERROR_FAILURE;
}
IPinnedList3* pinnedList = nullptr;
HRESULT hr =
CoCreateInstance(CLSID_TaskbandPin, nullptr, CLSCTX_INPROC_SERVER,
IID_IPinnedList3, (void**)&pinnedList);
if (FAILED(hr) || !pinnedList) {
return NS_ERROR_NOT_AVAILABLE;
}
if (!aCheckOnly) {
hr =
pinnedList->vtbl->Modify(pinnedList, nullptr, path.get(), PLMC_INT_MAX);
}
pinnedList->vtbl->Release(pinnedList);
if (FAILED(hr)) {
return NS_ERROR_FAILURE;
} else {
return NS_OK;
}
}
NS_IMETHODIMP
nsWindowsShellService::PinCurrentAppToTaskbar() {
return PinCurrentAppToTaskbarImpl(/* aCheckOnly */ false);
}
NS_IMETHODIMP
nsWindowsShellService::CheckPinCurrentAppToTaskbar() {
return PinCurrentAppToTaskbarImpl(/* aCheckOnly */ true);
}
nsWindowsShellService::nsWindowsShellService() {}
nsWindowsShellService::~nsWindowsShellService() {}

Просмотреть файл

@ -53,6 +53,7 @@ EXPORTS.mozilla.widget += [
"WindowsSMTCProvider.h",
"WinMessages.h",
"WinModifierKeyState.h",
"WinTaskbar.h",
]
UNIFIED_SOURCES += [