Bug 1923448 - Generalized Windows Pinning, r=nrishel,gfx-reviewers,nical

Differential Revision: https://phabricator.services.mozilla.com/D224975
This commit is contained in:
Max Christian Pohle
2024-11-07 09:54:52 +00:00
parent 62c98a6507
commit 423ab686ff
2 changed files with 261 additions and 50 deletions

View File

@@ -2,6 +2,7 @@
* 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/. */
#include "imgIContainer.idl"
#include "nsIShellService.idl"
interface nsIFile;
@@ -286,18 +287,32 @@ interface nsIWindowsShellService : nsIShellService
*
* This function should not be called unless it is certain that it's
* necessary given how few checks there are within.
* @param aAppUserModelId
* The aumid to be contained in the .lnk file
* @param aShortcutPath
* A path to the .lnk file that should be pinned to the taskbar.
* @throws NS_ERROR_FAILURE
* If the COM service could not be initialized
* @throws NS_ERROR_FILE_NOT_FOUND
* If aShortcutPath cannot be found
* @throws NS_ERROR_NOT_AVAILABLE
* If the taskbar pinning service cannot be initialized
* @throws NS_ERROR_FILE_ACCESS_DENIED
* If the taskbar pins cannot be modified
*/
void pinShortcutToTaskbar(in AString aShortcutPath);
[implicit_jscontext]
Promise pinShortcutToTaskbar(
in AString aAppUserModelId,
in AString aShortcutPath
);
/* This function can be used to convert compatible image formats into .ico
* files to be used for .lnk files (see createShortcut).
*
* This function should not be called unless it is certain that it's
* necessary given how few checks there are within.
* @param aFile
* Output file to be written
* @param aContainer
* The graphics container
*/
[implicit_jscontext]
Promise createWindowsIcon(
in nsIFile aFile,
in imgIContainer aContainer
);
/*
* This function is a counterpart to pinShortcutToTaskbar and allows

View File

@@ -3,18 +3,23 @@
* 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/. */
#include "nsDebug.h"
#define UNICODE
#include "nsWindowsShellService.h"
#include "nsWindowsShellServiceInternal.h"
#include "BinaryPath.h"
#include "gfxUtils.h"
#include "imgIContainer.h"
#include "imgIRequest.h"
#include "imgITools.h"
#include "mozilla/RefPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsIContent.h"
#include "nsIImageLoadingContent.h"
#include "nsIFile.h"
#include "nsIFileStreams.h"
#include "nsIOutputStream.h"
#include "nsIPrefService.h"
#include "nsIStringBundle.h"
@@ -34,8 +39,10 @@
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/FileUtils.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/intl/Localization.h"
#include "mozilla/UniquePtrExtensions.h"
#include "WindowsDefaultBrowser.h"
#include "WindowsUserChoice.h"
#include "nsLocalFile.h"
@@ -123,6 +130,13 @@ using BStrPtr = mozilla::UniquePtr<OLECHAR, SysFreeStringDeleter>;
NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIToolkitShellService,
nsIShellService, nsIWindowsShellService)
/* Enable logging by setting MOZ_LOG to "nsWindowsShellService:5" for debugging
* purposes. */
static LazyLogModule sLog("nsWindowsShellService");
static nsresult WriteBitmap(nsIFile* aFile, imgIContainer* aImage);
static nsresult WriteIcon(nsIFile* aIcoFile, imgIContainer* aImage);
static nsresult OpenKeyForReading(HKEY aKeyRoot, const nsAString& aKeyName,
HKEY* aKey) {
const nsString& flatName = PromiseFlatString(aKeyName);
@@ -422,6 +436,120 @@ nsWindowsShellService::SetDefaultBrowser(bool aForAllUsers) {
return rv;
}
/*
* Asynchronious function to Write an ico file to the disk / in a nsIFile.
* Limitation: Only square images are supported as of now.
*/
NS_IMETHODIMP
nsWindowsShellService::CreateWindowsIcon(nsIFile* aIcoFile,
imgIContainer* aImage, JSContext* aCx,
dom::Promise** aPromise) {
NS_ENSURE_ARG_POINTER(aIcoFile);
NS_ENSURE_ARG_POINTER(aImage);
NS_ENSURE_ARG_POINTER(aCx);
NS_ENSURE_ARG_POINTER(aPromise);
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
// First available on 1809
if (!IsWin10Sep2018UpdateOrLater()) {
return NS_ERROR_NOT_AVAILABLE;
}
ErrorResult rv;
RefPtr<dom::Promise> promise =
dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv);
if (MOZ_UNLIKELY(rv.Failed())) {
return rv.StealNSResult();
}
auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>(
"CreateWindowsIcon promise", promise);
NS_DispatchBackgroundTask(
NS_NewRunnableFunction(
"CreateWindowsIcon",
[icoFile = aIcoFile, image = aImage,
promiseHolder = std::move(promiseHolder)] {
nsresult rv = WriteIcon(icoFile, image);
NS_DispatchToMainThread(NS_NewRunnableFunction(
"CreateWindowsIcon callback",
[rv, promiseHolder = std::move(promiseHolder)] {
dom::Promise* promise = promiseHolder.get()->get();
if (NS_SUCCEEDED(rv)) {
promise->MaybeResolveWithUndefined();
} else {
promise->MaybeReject(rv);
}
}));
}),
NS_DISPATCH_EVENT_MAY_BLOCK);
promise.forget(aPromise);
return NS_OK;
}
static nsresult WriteIcon(nsIFile* aIcoFile, imgIContainer* aImage) {
MOZ_LOG(sLog, LogLevel::Debug,
("%s:%d - Reading input image...\n", __FILE__, __LINE__));
RefPtr<gfx::SourceSurface> surface = aImage->GetFrame(
imgIContainer::FRAME_FIRST, imgIContainer::FLAG_SYNC_DECODE);
NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
MOZ_LOG(sLog, LogLevel::Debug,
("%s:%d - Surface found, writing icon... \n", __FILE__, __LINE__));
const gfx::IntSize size = surface->GetSize();
if (size.IsEmpty()) {
MOZ_LOG(sLog, LogLevel::Debug,
("%s:%d - The input image looks empty :(\n", __FILE__, __LINE__));
return NS_ERROR_FAILURE;
}
RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
int32_t width = dataSurface->GetSize().width;
int32_t height = dataSurface->GetSize().height;
MOZ_LOG(sLog, LogLevel::Debug,
("%s:%d - Input image dimensions are: %dx%d pixels\n", __FILE__,
__LINE__, width, height));
NS_ENSURE_TRUE(height > 0, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(width > 0, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(width == height, NS_ERROR_FAILURE);
MOZ_LOG(sLog, LogLevel::Debug,
("%s:%d - Opening file for writing...\n", __FILE__, __LINE__));
ScopedCloseFile file;
nsresult rv = aIcoFile->OpenANSIFileDesc("wb", getter_Transfers(file));
if (NS_FAILED(rv)) {
return rv;
}
MOZ_LOG(sLog, LogLevel::Debug,
("%s:%d - Writing icon...\n", __FILE__, __LINE__));
rv = gfxUtils::EncodeSourceSurface(surface, ImageType::ICO, u""_ns,
gfxUtils::eBinaryEncode, file.get());
if (NS_FAILED(rv)) {
MOZ_LOG(sLog, LogLevel::Debug,
("%s:%d - Could not write the icon!\n", __FILE__, __LINE__));
return rv;
}
MOZ_LOG(sLog, LogLevel::Debug,
("%s:%d - Icon written!\n", __FILE__, __LINE__));
return NS_OK;
}
static nsresult WriteBitmap(nsIFile* aFile, imgIContainer* aImage) {
nsresult rv;
@@ -1504,11 +1632,115 @@ static nsresult ManageShortcutTaskbarPins(bool aCheckOnly, bool aPinType,
return NS_OK;
}
NS_IMETHODIMP
nsWindowsShellService::PinShortcutToTaskbar(const nsAString& aShortcutPath) {
/* Application pinning utility: Checks if the application is already pinned to
* the taskbar and attempts to pin it only if not already.
*/
static nsresult PinCurrentAppToTaskbarWin10(bool aCheckOnly,
const nsAString& aAppUserModelId,
const nsAString& aShortcutPath) {
// The behavior here is identical if we're only checking or if we try to pin
// but the app is already pinned so we update the variable accordingly.
if (!aCheckOnly) {
aCheckOnly = IsCurrentAppPinnedToTaskbarSync(aAppUserModelId);
}
const bool pinType = true; // true means pin
const bool runInTestMode = false;
return ManageShortcutTaskbarPins(runInTestMode, pinType, aShortcutPath);
return ManageShortcutTaskbarPins(aCheckOnly, pinType, aShortcutPath);
}
static nsresult PinShortcutToTaskbarImpl(const nsAString& aAppUserModelId,
const nsAString& aShortcutPath) {
auto pinWithWin11TaskbarAPIResults =
PinCurrentAppToTaskbarWin11(false, aAppUserModelId);
switch (pinWithWin11TaskbarAPIResults.result) {
case Win11PinToTaskBarResultStatus::NotSupported:
// Fall through to the win 10 mechanism
break;
case Win11PinToTaskBarResultStatus::Success:
case Win11PinToTaskBarResultStatus::AlreadyPinned:
return NS_OK;
case Win11PinToTaskBarResultStatus::NotPinned:
case Win11PinToTaskBarResultStatus::NotCurrentlyAllowed:
case Win11PinToTaskBarResultStatus::Failed:
// return NS_ERROR_FAILURE;
// Fall through to the old mechanism for now
// In future, we should be sending telemetry for when
// an error occurs or for when pinning is not allowed
// with the Win 11 APIs.
break;
}
return PinCurrentAppToTaskbarWin10(false, aAppUserModelId, aShortcutPath);
}
/* This function pins a shortcut to the taskbar based on its location. While
* Windows 11 only needs the `aAppUserModelId`, `aShortcutPath` is required
* for pinning in Windows 10.
* @param aAppUserModelId
* The same string used to create an lnk file.
* @param aShortcutPaths
* Path for existing shortcuts (e.g., start menu)
*/
NS_IMETHODIMP
nsWindowsShellService::PinShortcutToTaskbar(const nsAString& aAppUserModelId,
const nsAString& aShortcutPath,
JSContext* aCx,
dom::Promise** aPromise) {
NS_ENSURE_ARG_POINTER(aCx);
NS_ENSURE_ARG_POINTER(aPromise);
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
// First available on 1809
if (!IsWin10Sep2018UpdateOrLater()) {
return NS_ERROR_NOT_AVAILABLE;
}
ErrorResult rv;
RefPtr<dom::Promise> promise =
dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv);
if (MOZ_UNLIKELY(rv.Failed())) {
return rv.StealNSResult();
}
auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>(
"pinShortcutToTaskbar promise", promise);
NS_DispatchBackgroundTask(
NS_NewRunnableFunction(
"pinShortcutToTaskbar",
[aumid = nsString{aAppUserModelId},
shortcutPath = nsString(aShortcutPath),
promiseHolder = std::move(promiseHolder)] {
nsresult rv = NS_ERROR_FAILURE;
HRESULT hr = CoInitialize(nullptr);
if (SUCCEEDED(hr)) {
rv = PinShortcutToTaskbarImpl(aumid, shortcutPath);
CoUninitialize();
}
NS_DispatchToMainThread(NS_NewRunnableFunction(
"pinShortcutToTaskbar callback",
[rv, promiseHolder = std::move(promiseHolder)] {
dom::Promise* promise = promiseHolder.get()->get();
if (NS_SUCCEEDED(rv)) {
promise->MaybeResolveWithUndefined();
} else {
promise->MaybeReject(rv);
}
}));
}),
NS_DISPATCH_EVENT_MAY_BLOCK);
promise.forget(aPromise);
return NS_OK;
}
NS_IMETHODIMP
@@ -1683,18 +1915,6 @@ nsWindowsShellService::GetTaskbarTabPins(nsTArray<nsString>& aShortcutPaths) {
#endif
}
static nsresult PinCurrentAppToTaskbarWin10(bool aCheckOnly,
const nsAString& aAppUserModelId,
const nsAString& aShortcutPath) {
// The behavior here is identical if we're only checking or if we try to pin
// but the app is already pinned so we update the variable accordingly.
if (!aCheckOnly) {
aCheckOnly = IsCurrentAppPinnedToTaskbarSync(aAppUserModelId);
}
const bool pinType = true; // true means pin
return ManageShortcutTaskbarPins(aCheckOnly, pinType, aShortcutPath);
}
static nsresult PinCurrentAppToTaskbarImpl(
bool aCheckOnly, bool aPrivateBrowsing, const nsAString& aAppUserModelId,
const nsAString& aShortcutName, const nsAString& aShortcutSubstring,
@@ -1753,31 +1973,7 @@ static nsresult PinCurrentAppToTaskbarImpl(
return NS_ERROR_FILE_NOT_FOUND;
}
}
auto pinWithWin11TaskbarAPIResults =
PinCurrentAppToTaskbarWin11(aCheckOnly, aAppUserModelId);
switch (pinWithWin11TaskbarAPIResults.result) {
case Win11PinToTaskBarResultStatus::NotSupported:
// Fall through to the win 10 mechanism
break;
case Win11PinToTaskBarResultStatus::Success:
case Win11PinToTaskBarResultStatus::AlreadyPinned:
return NS_OK;
case Win11PinToTaskBarResultStatus::NotPinned:
case Win11PinToTaskBarResultStatus::NotCurrentlyAllowed:
case Win11PinToTaskBarResultStatus::Failed:
// return NS_ERROR_FAILURE;
// Fall through to the old mechanism for now
// In future, we should be sending telemetry for when
// an error occurs or for when pinning is not allowed
// with the Win 11 APIs.
break;
}
return PinCurrentAppToTaskbarWin10(aCheckOnly, aAppUserModelId, shortcutPath);
return PinShortcutToTaskbarImpl(aAppUserModelId, shortcutPath);
}
static nsresult PinCurrentAppToTaskbarAsyncImpl(bool aCheckOnly,