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