tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Windows11TaskbarPinning.cpp (20296B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "Windows11TaskbarPinning.h"
      7 #include "Windows11LimitedAccessFeatures.h"
      8 
      9 #include "nsWindowsHelpers.h"
     10 #include "MainThreadUtils.h"
     11 #include "nsThreadUtils.h"
     12 #include <shobjidl.h>
     13 #include <strsafe.h>
     14 
     15 #include "mozilla/Result.h"
     16 #include "mozilla/ResultVariant.h"
     17 #include "mozilla/WinHeaderOnlyUtils.h"
     18 #include "mozilla/widget/WinTaskbar.h"
     19 #include "WinUtils.h"
     20 
     21 #include "mozilla/Logging.h"
     22 
     23 static mozilla::LazyLogModule sLog("Windows11TaskbarPinning");
     24 
     25 #define TASKBAR_PINNING_LOG(level, msg, ...) \
     26  MOZ_LOG(sLog, level, (msg, ##__VA_ARGS__))
     27 
     28 #ifndef __MINGW32__  // WinRT headers not yet supported by MinGW
     29 
     30 #  include <wrl.h>
     31 
     32 #  include <inspectable.h>
     33 #  include <roapi.h>
     34 #  include <shlobj_core.h>
     35 #  include <windows.services.store.h>
     36 #  include <windows.foundation.h>
     37 #  include <windows.ui.shell.h>
     38 
     39 using namespace mozilla;
     40 
     41 /**
     42 * The Win32 SetEvent and WaitForSingleObject functions take HANDLE parameters
     43 * which are typedefs of void*. When using nsAutoHandle, that means if you
     44 * forget to call .get() first, everything still compiles and then doesn't work
     45 * at runtime. For instance, calling SetEvent(mEvent) below would compile but
     46 * not work at runtime and the waits would block forever.
     47 * To ensure this isn't an issue, we wrap the event in a custom class here
     48 * with the simple methods that we want on an event.
     49 */
     50 class EventWrapper {
     51 public:
     52  EventWrapper() : mEvent(CreateEventW(nullptr, true, false, nullptr)) {}
     53 
     54  void Set() { SetEvent(mEvent.get()); }
     55 
     56  void Reset() { ResetEvent(mEvent.get()); }
     57 
     58  void Wait() { WaitForSingleObject(mEvent.get(), INFINITE); }
     59 
     60 private:
     61  nsAutoHandle mEvent;
     62 };
     63 
     64 using namespace Microsoft::WRL;
     65 using namespace Microsoft::WRL::Wrappers;
     66 using namespace ABI::Windows;
     67 using namespace ABI::Windows::UI::Shell;
     68 using namespace ABI::Windows::Foundation;
     69 using namespace ABI::Windows::ApplicationModel;
     70 
     71 static Win11PinToTaskBarResult UnlockLimitedAccessFeature(
     72    Win11LimitedAccessFeatureType featureType) {
     73  RefPtr<Win11LimitedAccessFeaturesInterface> limitedAccessFeatures =
     74      CreateWin11LimitedAccessFeaturesInterface();
     75  auto result = limitedAccessFeatures->Unlock(featureType);
     76  if (result.isErr()) {
     77    auto hr = result.unwrapErr();
     78    TASKBAR_PINNING_LOG(LogLevel::Debug,
     79                        "Taskbar unlock: Error. HRESULT = 0x%lx", hr);
     80    return {hr, Win11PinToTaskBarResultStatus::NotSupported};
     81  }
     82 
     83  if (result.unwrap() == false) {
     84    TASKBAR_PINNING_LOG(
     85        LogLevel::Debug,
     86        "Taskbar unlock: failed. Not supported on this version of Windows.");
     87    return {S_OK, Win11PinToTaskBarResultStatus::NotSupported};
     88  }
     89  return {S_OK, Win11PinToTaskBarResultStatus::Success};
     90 }
     91 
     92 static Result<ComPtr<ITaskbarManager>, HRESULT> InitializeTaskbar() {
     93  ComPtr<IInspectable> taskbarStaticsInspectable;
     94 
     95  TASKBAR_PINNING_LOG(LogLevel::Debug, "Initializing taskbar");
     96 
     97  HRESULT hr = RoGetActivationFactory(
     98      HStringReference(RuntimeClass_Windows_UI_Shell_TaskbarManager).Get(),
     99      IID_ITaskbarManagerStatics, &taskbarStaticsInspectable);
    100  if (FAILED(hr)) {
    101    TASKBAR_PINNING_LOG(LogLevel::Debug,
    102                        "Taskbar: Failed to activate. HRESULT = 0x%lx", hr);
    103    return Err(hr);
    104  }
    105 
    106  ComPtr<ITaskbarManagerStatics> taskbarStatics;
    107 
    108  hr = taskbarStaticsInspectable.As(&taskbarStatics);
    109  if (FAILED(hr)) {
    110    TASKBAR_PINNING_LOG(LogLevel::Debug, "Failed statistics. HRESULT = 0x%lx",
    111                        hr);
    112    return Err(hr);
    113  }
    114 
    115  ComPtr<ITaskbarManager> taskbarManager;
    116 
    117  hr = taskbarStatics->GetDefault(&taskbarManager);
    118  if (FAILED(hr)) {
    119    TASKBAR_PINNING_LOG(LogLevel::Debug,
    120                        "Error getting TaskbarManager. HRESULT = 0x%lx", hr);
    121    return Err(hr);
    122  }
    123 
    124  TASKBAR_PINNING_LOG(LogLevel::Debug,
    125                      "TaskbarManager retrieved successfully!");
    126  return taskbarManager;
    127 }
    128 
    129 static Win11PinToTaskBarResultStatus IsTaskbarPinningAllowed(
    130    bool aCheckOnly, ComPtr<ITaskbarManager>& taskbar) {
    131  HRESULT hr;
    132  auto result = InitializeTaskbar();
    133  if (result.isErr()) {
    134    hr = result.unwrapErr();
    135    return Win11PinToTaskBarResultStatus::NotSupported;
    136  }
    137 
    138  taskbar = result.unwrap();
    139  boolean supported;
    140  hr = taskbar->get_IsSupported(&supported);
    141  if (FAILED(hr) || !supported) {
    142    if (FAILED(hr)) {
    143      TASKBAR_PINNING_LOG(
    144          LogLevel::Debug,
    145          "Taskbar: error checking if supported. HRESULT = 0x%lx", hr);
    146    } else {
    147      TASKBAR_PINNING_LOG(LogLevel::Debug, "Taskbar: not supported.");
    148    }
    149    return Win11PinToTaskBarResultStatus::NotSupported;
    150  }
    151 
    152  if (aCheckOnly) {
    153    TASKBAR_PINNING_LOG(LogLevel::Debug, "Taskbar: check succeeded.");
    154    return Win11PinToTaskBarResultStatus::Success;
    155  }
    156 
    157  boolean isAllowed = false;
    158  hr = taskbar->get_IsPinningAllowed(&isAllowed);
    159  if (FAILED(hr) || !isAllowed) {
    160    if (FAILED(hr)) {
    161      TASKBAR_PINNING_LOG(
    162          LogLevel::Debug,
    163          "Taskbar: error checking if pinning is allowed. HRESULT = "
    164          "0x%lx",
    165          hr);
    166    } else {
    167      TASKBAR_PINNING_LOG(
    168          LogLevel::Debug,
    169          "Taskbar: is pinning allowed error or isn't allowed right now. "
    170          "It's not clear when it will be allowed. Possibly after a "
    171          "reboot.");
    172    }
    173    return Win11PinToTaskBarResultStatus::NotCurrentlyAllowed;
    174  }
    175  return Win11PinToTaskBarResultStatus::Success;
    176 }
    177 
    178 Win11PinToTaskBarResult PinCurrentAppToTaskbarWin11(
    179    bool aCheckOnly, const nsAString& aAppUserModelId) {
    180  MOZ_DIAGNOSTIC_ASSERT(!NS_IsMainThread(),
    181                        "PinCurrentAppToTaskbarWin11 should be called off main "
    182                        "thread only. It blocks, waiting on things to execute "
    183                        "asynchronously on the main thread.");
    184 
    185  Win11PinToTaskBarResult unlockStatus =
    186      UnlockLimitedAccessFeature(Win11LimitedAccessFeatureType::Taskbar);
    187  if (unlockStatus.result != Win11PinToTaskBarResultStatus::Success) {
    188    return unlockStatus;
    189  }
    190 
    191  HRESULT hr;
    192  Win11PinToTaskBarResultStatus resultStatus =
    193      Win11PinToTaskBarResultStatus::NotSupported;
    194 
    195  EventWrapper event;
    196 
    197  // Everything related to the taskbar and pinning must be done on the main /
    198  // user interface thread or Windows will cause them to fail.
    199  NS_DispatchToMainThread(NS_NewRunnableFunction(
    200      "PinCurrentAppToTaskbarWin11", [&event, &hr, &resultStatus, aCheckOnly,
    201                                      aumid = nsString(aAppUserModelId)] {
    202        // We eventualy want to call SetCurrentProcessExplicitAppUserModelID()
    203        // on the main thread as it is not thread safe and pinning is called
    204        // numerous times in many different places. This is a hack used
    205        // explicitly for the purpose of re-enabling private browser pinning
    206        // as a stopgap and should not be replicated elsewhere.
    207        // GenerateAppUserModelId needs to be called on the main thread as
    208        // it checks against preferences.
    209        nsAutoString primaryAumid;
    210        mozilla::widget::WinTaskbar::GenerateAppUserModelID(primaryAumid,
    211                                                            false);
    212        auto CompletedOperations = [&event, &resultStatus,
    213                                    primaryAumid = nsString(primaryAumid)](
    214                                       Win11PinToTaskBarResultStatus status) {
    215          // Set AUMID back and ensure the icon is set correctly
    216          if (!widget::WinUtils::HasPackageIdentity()) {
    217            HRESULT hr =
    218                SetCurrentProcessExplicitAppUserModelID(primaryAumid.get());
    219            if (FAILED(hr)) {
    220              TASKBAR_PINNING_LOG(LogLevel::Debug,
    221                                  "Taskbar: reverting AUMID after pinning "
    222                                  "operation failed. HRESULT = 0x%lx",
    223                                  hr);
    224            }
    225          }
    226          resultStatus = status;
    227          event.Set();
    228        };
    229 
    230        // Set the process to have the AUMID of the shortcut we want to pin,
    231        // this is only necessary for Win32 builds
    232        if (!widget::WinUtils::HasPackageIdentity()) {
    233          hr = SetCurrentProcessExplicitAppUserModelID(aumid.get());
    234          if (FAILED(hr)) {
    235            return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    236          }
    237        }
    238 
    239        ComPtr<ITaskbarManager> taskbar;
    240        Win11PinToTaskBarResultStatus allowed =
    241            IsTaskbarPinningAllowed(aCheckOnly, taskbar);
    242        if ((aCheckOnly && allowed == Win11PinToTaskBarResultStatus::Success) ||
    243            allowed != Win11PinToTaskBarResultStatus::Success) {
    244          return CompletedOperations(allowed);
    245        }
    246 
    247        ComPtr<IAsyncOperation<bool>> isPinnedOperation = nullptr;
    248        hr = taskbar->IsCurrentAppPinnedAsync(&isPinnedOperation);
    249        if (FAILED(hr)) {
    250          TASKBAR_PINNING_LOG(
    251              LogLevel::Debug,
    252              "Taskbar: is current app pinned operation failed. HRESULT = "
    253              "0x%lx",
    254              hr);
    255          return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    256        }
    257 
    258        // Copy the taskbar; don't use it as a reference.
    259        // With the async calls, it's not guaranteed to still be valid
    260        // if sent as a reference.
    261        // resultStatus and event are not defined on the main thread and will
    262        // be alive until the async functions complete, so they can be used as
    263        // references.
    264        auto isPinnedCallback = Callback<IAsyncOperationCompletedHandler<
    265            bool>>([taskbar, &event, &resultStatus, &hr,
    266                    primaryAumid = nsString(primaryAumid)](
    267                       IAsyncOperation<bool>* asyncInfo,
    268                       AsyncStatus status) mutable -> HRESULT {
    269          auto CompletedOperations =
    270              [&event, &resultStatus,
    271               primaryAumid](Win11PinToTaskBarResultStatus status) -> HRESULT {
    272            // Set AUMID back and ensure the icon is set correctly
    273            if (!widget::WinUtils::HasPackageIdentity()) {
    274              HRESULT hr =
    275                  SetCurrentProcessExplicitAppUserModelID(primaryAumid.get());
    276              if (FAILED(hr)) {
    277                TASKBAR_PINNING_LOG(LogLevel::Debug,
    278                                    "Taskbar: reverting AUMID after pinning "
    279                                    "operation failed. HRESULT = 0x%lx",
    280                                    hr);
    281              }
    282            }
    283            resultStatus = status;
    284            event.Set();
    285            return S_OK;
    286          };
    287 
    288          bool asyncOpSucceeded = status == AsyncStatus::Completed;
    289          if (!asyncOpSucceeded) {
    290            TASKBAR_PINNING_LOG(
    291                LogLevel::Debug,
    292                "Taskbar: is pinned operation failed to complete.");
    293            return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    294          }
    295 
    296          unsigned char isCurrentAppPinned = false;
    297          hr = asyncInfo->GetResults(&isCurrentAppPinned);
    298          if (FAILED(hr)) {
    299            TASKBAR_PINNING_LOG(
    300                LogLevel::Debug,
    301                "Taskbar: is current app pinned check failed. HRESULT = 0x%lx",
    302                hr);
    303            return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    304          }
    305 
    306          if (isCurrentAppPinned) {
    307            TASKBAR_PINNING_LOG(LogLevel::Debug,
    308                                "Taskbar: current app is already pinned.");
    309            return CompletedOperations(
    310                Win11PinToTaskBarResultStatus::AlreadyPinned);
    311          }
    312 
    313          ComPtr<IAsyncOperation<bool>> requestPinOperation = nullptr;
    314          hr = taskbar->RequestPinCurrentAppAsync(&requestPinOperation);
    315          if (FAILED(hr)) {
    316            TASKBAR_PINNING_LOG(
    317                LogLevel::Debug,
    318                "Taskbar: request pin current app operation creation failed. "
    319                "HRESULT = 0x%lx",
    320                hr);
    321            return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    322          }
    323 
    324          auto pinAppCallback = Callback<IAsyncOperationCompletedHandler<
    325              bool>>([CompletedOperations, &hr](
    326                         IAsyncOperation<bool>* asyncInfo,
    327                         AsyncStatus status) -> HRESULT {
    328            bool asyncOpSucceeded = status == AsyncStatus::Completed;
    329            if (!asyncOpSucceeded) {
    330              TASKBAR_PINNING_LOG(
    331                  LogLevel::Debug,
    332                  "Taskbar: request pin current app operation did not "
    333                  "complete.");
    334              return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    335            }
    336 
    337            unsigned char userAffirmedPin = 0;
    338            hr = asyncInfo->GetResults(&userAffirmedPin);
    339            if (FAILED(hr)) {
    340              TASKBAR_PINNING_LOG(
    341                  LogLevel::Debug,
    342                  "Taskbar: request pin current app operation failed to pin "
    343                  "due to error. HRESULT = 0x%lx",
    344                  hr);
    345              return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    346            }
    347 
    348            // Bug 1890634: Record pinning success rate telemetry
    349            TASKBAR_PINNING_LOG(
    350                LogLevel::Debug,
    351                userAffirmedPin
    352                    ? "Taskbar: request pin current app operation succeeded"
    353                    : "Taskbar: user rejected Windows pin prompt");
    354 
    355            return CompletedOperations(Win11PinToTaskBarResultStatus::Success);
    356          });
    357 
    358          HRESULT pinOperationHR =
    359              requestPinOperation->put_Completed(pinAppCallback.Get());
    360          if (FAILED(pinOperationHR)) {
    361            TASKBAR_PINNING_LOG(
    362                LogLevel::Debug,
    363                "Taskbar: request pin operation failed when setting completion "
    364                "callback. HRESULT = 0x%lx",
    365                hr);
    366            hr = pinOperationHR;
    367            return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    368          }
    369 
    370          // DO NOT SET event HERE. It will be set in the pin operation
    371          // callback As in, operations are not completed, so don't call
    372          // CompletedOperations
    373          return S_OK;
    374        });
    375 
    376        HRESULT isPinnedOperationHR =
    377            isPinnedOperation->put_Completed(isPinnedCallback.Get());
    378        if (FAILED(isPinnedOperationHR)) {
    379          hr = isPinnedOperationHR;
    380          TASKBAR_PINNING_LOG(
    381              LogLevel::Debug,
    382              "Taskbar: is pinned operation failed when setting completion "
    383              "callback. HRESULT = 0x%lx",
    384              hr);
    385          return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    386        }
    387 
    388        // DO NOT SET event HERE. It will be set in the is pin operation
    389        // callback As in, operations are not completed, so don't call
    390        // CompletedOperations
    391      }));
    392 
    393  // block until the pinning is completed on the main thread
    394  event.Wait();
    395 
    396  return {hr, resultStatus};
    397 }
    398 
    399 Win11PinToTaskBarResult IsCurrentAppPinnedToTaskbarWin11(bool aCheckOnly) {
    400  MOZ_DIAGNOSTIC_ASSERT(
    401      !NS_IsMainThread(),
    402      "IsCurrentAppPinnedToTaskbarWin11 should be called off main "
    403      "thread only. It blocks, waiting on things to execute "
    404      "asynchronously on the main thread.");
    405 
    406  Win11PinToTaskBarResult unlockStatus =
    407      UnlockLimitedAccessFeature(Win11LimitedAccessFeatureType::Taskbar);
    408  if (unlockStatus.result != Win11PinToTaskBarResultStatus::Success) {
    409    return unlockStatus;
    410  }
    411 
    412  HRESULT hr;
    413  Win11PinToTaskBarResultStatus resultStatus =
    414      Win11PinToTaskBarResultStatus::NotSupported;
    415 
    416  EventWrapper event;
    417 
    418  // Everything related to the taskbar and pinning must be done on the main /
    419  // user interface thread or Windows will cause them to fail.
    420  NS_DispatchToMainThread(NS_NewRunnableFunction(
    421      "IsCurrentAppPinnedToTaskbarWin11",
    422      [&event, &hr, &resultStatus, aCheckOnly] {
    423        auto CompletedOperations =
    424            [&event, &resultStatus](Win11PinToTaskBarResultStatus status) {
    425              resultStatus = status;
    426              event.Set();
    427            };
    428 
    429        ComPtr<ITaskbarManager> taskbar;
    430        Win11PinToTaskBarResultStatus allowed =
    431            IsTaskbarPinningAllowed(aCheckOnly, taskbar);
    432        if ((aCheckOnly && allowed == Win11PinToTaskBarResultStatus::Success) ||
    433            allowed != Win11PinToTaskBarResultStatus::Success) {
    434          return CompletedOperations(allowed);
    435        }
    436 
    437        ComPtr<IAsyncOperation<bool>> isPinnedOperation = nullptr;
    438        hr = taskbar->IsCurrentAppPinnedAsync(&isPinnedOperation);
    439        if (FAILED(hr)) {
    440          TASKBAR_PINNING_LOG(
    441              LogLevel::Debug,
    442              "Taskbar: is current app pinned operation failed. HRESULT = "
    443              "0x%lx",
    444              hr);
    445          return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    446        }
    447 
    448        // Copy the taskbar; don't use it as a reference.
    449        // With the async calls, it's not guaranteed to still be valid
    450        // if sent as a reference.
    451        // resultStatus and event are not defined on the main thread and will
    452        // be alive until the async functions complete, so they can be used as
    453        // references.
    454        auto isPinnedCallback = Callback<IAsyncOperationCompletedHandler<
    455            bool>>([taskbar, &event, &resultStatus, &hr](
    456                       IAsyncOperation<bool>* asyncInfo,
    457                       AsyncStatus status) mutable -> HRESULT {
    458          auto CompletedOperations =
    459              [&event,
    460               &resultStatus](Win11PinToTaskBarResultStatus status) -> HRESULT {
    461            resultStatus = status;
    462            event.Set();
    463            return S_OK;
    464          };
    465 
    466          bool asyncOpSucceeded = status == AsyncStatus::Completed;
    467          if (!asyncOpSucceeded) {
    468            TASKBAR_PINNING_LOG(
    469                LogLevel::Debug,
    470                "Taskbar: is pinned operation failed to complete.");
    471            return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    472          }
    473 
    474          unsigned char isCurrentAppPinned = false;
    475          hr = asyncInfo->GetResults(&isCurrentAppPinned);
    476          if (FAILED(hr)) {
    477            TASKBAR_PINNING_LOG(
    478                LogLevel::Debug,
    479                "Taskbar: is current app pinned check failed. HRESULT = 0x%lx",
    480                hr);
    481            return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    482          }
    483 
    484          if (isCurrentAppPinned) {
    485            TASKBAR_PINNING_LOG(LogLevel::Debug,
    486                                "Taskbar: current app is already pinned.");
    487            return CompletedOperations(
    488                Win11PinToTaskBarResultStatus::AlreadyPinned);
    489          }
    490          return CompletedOperations(Win11PinToTaskBarResultStatus::NotPinned);
    491        });
    492 
    493        HRESULT isPinnedOperationHR =
    494            isPinnedOperation->put_Completed(isPinnedCallback.Get());
    495        if (FAILED(isPinnedOperationHR)) {
    496          hr = isPinnedOperationHR;
    497          TASKBAR_PINNING_LOG(
    498              LogLevel::Debug,
    499              "Taskbar: is pinned operation failed when setting completion "
    500              "callback. HRESULT = 0x%lx",
    501              hr);
    502          return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
    503        }
    504 
    505        // DO NOT SET event HERE. It will be set in the is pin operation
    506        // callback As in, operations are not completed, so don't call
    507        // CompletedOperations
    508      }));
    509 
    510  // block until the pinning check is completed on the main thread
    511  event.Wait();
    512 
    513  return {hr, resultStatus};
    514 }
    515 
    516 #else  // MINGW32 implementation below
    517 
    518 Win11PinToTaskBarResult PinCurrentAppToTaskbarWin11(
    519    bool aCheckOnly, const nsAString& aAppUserModelId) {
    520  return {S_OK, Win11PinToTaskBarResultStatus::NotSupported};
    521 }
    522 
    523 Win11PinToTaskBarResult IsCurrentAppPinnedToTaskbarWin11(bool aCheckOnly) {
    524  return {S_OK, Win11PinToTaskBarResultStatus::NotSupported};
    525 }
    526 
    527 #endif  // #ifndef __MINGW32__  // WinRT headers not yet supported by MinGW