blob: f72fb57e3caa1f3fb7cdfcd2d48b2786715867e2 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/webnn/win_app_runtime_installer.h"
#include <windows.h>
#include <Windows.ApplicationModel.store.preview.installcontrol.h>
#include <appmodel.h>
#include <wrl.h>
#include <ranges>
#include <string_view>
#include "base/files/file_path.h"
#include "base/functional/concurrent_callbacks.h"
#include "base/metrics/histogram_functions.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/win/core_winrt_util.h"
#include "base/win/post_async_results.h"
#include "base/win/scoped_hstring.h"
#include "base/win/windows_version.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/webnn/webnn_prefs.h"
#include "chrome/common/chrome_paths.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "services/webnn/public/cpp/platform_functions_win.h"
#include "services/webnn/public/cpp/win_app_runtime_package_info.h"
#include "services/webnn/public/mojom/features.mojom-features.h"
namespace webnn {
namespace {
namespace abi_install =
ABI::Windows::ApplicationModel::Store::Preview::InstallControl;
using AppInstallAsyncOp =
__FIAsyncOperation_1___FIVectorView_1_Windows__CApplicationModel__CStore__CPreview__CInstallControl__CAppInstallItem;
using AppInstallStatusChangedHandler =
__FITypedEventHandler_2_Windows__CApplicationModel__CStore__CPreview__CInstallControl__CAppInstallItem_IInspectable;
using AppInstallItems =
__FIVectorView_1_Windows__CApplicationModel__CStore__CPreview__CInstallControl__CAppInstallItem;
// The product ID of the Windows App Runtime package in Microsoft Store.
constexpr std::wstring_view kWinAppRuntimeProductId = L"9NKRJ3SJ9SDG";
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(WinAppRuntimeInstallStateUma)
enum class WinAppRuntimeInstallStateUma {
kWindowsVersionTooOld = 0,
kActivationFailure = 1,
kCompleted = 2,
kError = 3,
kCanceled = 4,
kPaused = 5,
kPausedLowBattery = 6,
kPausedWiFiRecommended = 7,
kPausedWiFiRequired = 8,
kRuntimeAlreadyPresent = 9,
kInstallationFailedToStart = 10,
kMaxValue = kInstallationFailedToStart,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/webnn/enums.xml:WinAppRuntimeInstallStateUma)
void RecordInstallState(WinAppRuntimeInstallStateUma state) {
base::UmaHistogramEnumeration("WebNN.ORT.WinAppRuntimeInstallState", state);
}
// Creates a Windows App Runtime package dependency with the lifetime of the
// user data directory.
std::wstring TryCreateWinAppRuntimePackageDependency() {
base::FilePath user_data_dir =
base::PathService::CheckedGet(chrome::DIR_USER_DATA);
return TryCreatePackageDependencyForFilePath(kWinAppRuntimePackageFamilyName,
kWinAppRuntimePackageMinVersion,
user_data_dir);
}
// Called if the installation succeeds (or already installed) and the package
// dependency is created successfully. Updates the local state prefs with the
// package information and provided dependency ID.
void UpdatePrefs(const std::wstring& dependency_id) {
PrefService* local_state = g_browser_process->local_state();
local_state->SetString(prefs::kWinAppRuntimePackageDependencyId,
base::WideToUTF8(dependency_id));
local_state->SetString(prefs::kWinAppRuntimePackageFamilyName,
base::WideToUTF8(kWinAppRuntimePackageFamilyName));
local_state->SetString(prefs::kWinAppRuntimePackageMinVersion,
kWinAppRuntimePackageMinVersionString);
}
// Called after all `AppInstallItems` reach the complete state (succeeded,
// canceled or failed).
void OnInstallationCompleted(std::vector<bool> results) {
if (std::ranges::any_of(results, [](bool success) { return !success; })) {
return;
}
std::wstring dependency_id = TryCreateWinAppRuntimePackageDependency();
if (dependency_id.empty()) {
return;
}
UpdatePrefs(dependency_id);
}
// Called after `StartProductInstallAsync()` completes. Adds a callback for each
// `AppInstallItem` to track their status change.
void OnInstallationStarted(
Microsoft::WRL::ComPtr<abi_install::IAppInstallManager> app_install_manager,
Microsoft::WRL::ComPtr<AppInstallItems> items) {
uint32_t count = 0;
HRESULT hr = items->get_Size(&count);
CHECK_EQ(hr, S_OK);
if (count == 0) {
RecordInstallState(
WinAppRuntimeInstallStateUma::kInstallationFailedToStart);
return;
}
// Wait for all the app install items to complete.
base::ConcurrentCallbacks<bool> concurrent_callbacks;
for (uint32_t i = 0; i < count; ++i) {
Microsoft::WRL::ComPtr<abi_install::IAppInstallItem> item;
hr = items->GetAt(i, &item);
CHECK_EQ(hr, S_OK);
// `token` receives the value assigned by `add_StatusChanged()` below.
// It is used to unregister the status change event handler.
auto token = std::make_unique<EventRegistrationToken>();
auto* token_ptr = token.get();
// Register the status change event handler for `item`.
// `app_install_manager` is captured in the callback to be kept alive to
// ensure the callback will be triggered.
item->add_StatusChanged(
Microsoft::WRL::Callback<AppInstallStatusChangedHandler>(
[app_install_manager, token = std::move(token),
callback = base::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(),
concurrent_callbacks.CreateCallback())](
abi_install::IAppInstallItem* item,
IInspectable* args) mutable {
Microsoft::WRL::ComPtr<abi_install::IAppInstallStatus> status;
HRESULT hr = item->GetCurrentStatus(&status);
CHECK_EQ(hr, S_OK);
abi_install::AppInstallState state;
hr = status->get_InstallState(&state);
CHECK_EQ(hr, S_OK);
switch (state) {
case abi_install::AppInstallState::AppInstallState_Completed: {
RecordInstallState(WinAppRuntimeInstallStateUma::kCompleted);
item->remove_StatusChanged(*token);
if (!callback.is_null()) {
std::move(callback).Run(true);
}
break;
}
case abi_install::AppInstallState::AppInstallState_Error: {
RecordInstallState(WinAppRuntimeInstallStateUma::kError);
item->remove_StatusChanged(*token);
if (!callback.is_null()) {
std::move(callback).Run(false);
}
break;
}
case abi_install::AppInstallState::AppInstallState_Canceled: {
RecordInstallState(WinAppRuntimeInstallStateUma::kCanceled);
item->remove_StatusChanged(*token);
if (!callback.is_null()) {
std::move(callback).Run(false);
}
break;
}
case abi_install::AppInstallState::AppInstallState_Paused: {
RecordInstallState(WinAppRuntimeInstallStateUma::kPaused);
break;
}
case abi_install::AppInstallState::
AppInstallState_PausedLowBattery: {
RecordInstallState(
WinAppRuntimeInstallStateUma::kPausedLowBattery);
break;
}
case abi_install::AppInstallState::
AppInstallState_PausedWiFiRecommended: {
RecordInstallState(
WinAppRuntimeInstallStateUma::kPausedWiFiRecommended);
break;
}
case abi_install::AppInstallState::
AppInstallState_PausedWiFiRequired: {
RecordInstallState(
WinAppRuntimeInstallStateUma::kPausedWiFiRequired);
break;
}
default:
break;
}
return S_OK;
})
.Get(),
token_ptr);
}
std::move(concurrent_callbacks)
.Done(base::BindOnce(&OnInstallationCompleted));
}
// Activates and returns the IAppInstallManager instance.
Microsoft::WRL::ComPtr<abi_install::IAppInstallManager>
ActivateAppInstallManager() {
// `RoActivateInstance()` below loads Microsoft Store Install Service dlls.
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
// `app_install_manager` must remain valid throughout the entire asynchronous
// installation process, otherwise WinRT may release related resources
// prematurely, causing the callbacks not to be triggered.
Microsoft::WRL::ComPtr<abi_install::IAppInstallManager> app_install_manager;
HRESULT hr = base::win::RoActivateInstance(
base::win::ScopedHString::Create(
RuntimeClass_Windows_ApplicationModel_Store_Preview_InstallControl_AppInstallManager)
.get(),
&app_install_manager);
if (FAILED(hr)) {
RecordInstallState(WinAppRuntimeInstallStateUma::kActivationFailure);
return nullptr;
}
return app_install_manager;
}
// Starts the installation using the IAppInstallManager API.
void StartInstallation(Microsoft::WRL::ComPtr<abi_install::IAppInstallManager>
app_install_manager) {
if (!app_install_manager) {
return;
}
Microsoft::WRL::ComPtr<abi_install::IAppInstallManager3>
app_install_manager_3;
HRESULT hr = app_install_manager.As(&app_install_manager_3);
CHECK_EQ(hr, S_OK);
auto catalog_id = base::win::ScopedHString::Create(std::wstring_view());
auto flight_id = base::win::ScopedHString::Create(std::wstring_view());
auto client_id = base::win::ScopedHString::Create(std::wstring_view());
auto correlation_vector =
base::win::ScopedHString::Create(std::wstring_view());
Microsoft::WRL::ComPtr<AppInstallAsyncOp> async_op;
hr = app_install_manager_3->StartProductInstallAsync(
base::win::ScopedHString::Create(kWinAppRuntimeProductId).get(),
catalog_id.get(), flight_id.get(), client_id.get(),
/*repair=*/false, /*forceUseOfNonRemovableStorage=*/false,
correlation_vector.get(), /*targetVolume=*/nullptr, &async_op);
if (FAILED(hr)) {
RecordInstallState(
WinAppRuntimeInstallStateUma::kInstallationFailedToStart);
return;
}
hr = base::win::PostAsyncHandlers(
async_op.Get(),
base::BindOnce(&OnInstallationStarted, std::move(app_install_manager)),
base::BindOnce([](HRESULT /*hr*/) {
RecordInstallState(
WinAppRuntimeInstallStateUma::kInstallationFailedToStart);
}));
if (FAILED(hr)) {
RecordInstallState(
WinAppRuntimeInstallStateUma::kInstallationFailedToStart);
}
}
// Ensures the Windows App Runtime package is installed and up to date.
// Runs on browser's UI thread.
void EnsureInstallation() {
PrefService* local_state = g_browser_process->local_state();
const std::string& dependency_id =
local_state->GetString(prefs::kWinAppRuntimePackageDependencyId);
if (!dependency_id.empty()) {
const std::string& family_name =
local_state->GetString(prefs::kWinAppRuntimePackageFamilyName);
const std::string& min_version =
local_state->GetString(prefs::kWinAppRuntimePackageMinVersion);
bool package_up_to_date =
family_name == base::WideToUTF8(kWinAppRuntimePackageFamilyName) &&
min_version == kWinAppRuntimePackageMinVersionString;
if (package_up_to_date) {
RecordInstallState(WinAppRuntimeInstallStateUma::kRuntimeAlreadyPresent);
return;
}
DeletePackageDependency(base::UTF8ToWide(dependency_id));
}
// Before starting the installation, first try to create the package
// dependency, in case it has already been installed by another application on
// the system.
std::wstring new_dependency_id = TryCreateWinAppRuntimePackageDependency();
if (!new_dependency_id.empty()) {
RecordInstallState(WinAppRuntimeInstallStateUma::kRuntimeAlreadyPresent);
UpdatePrefs(new_dependency_id);
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&ActivateAppInstallManager),
base::BindOnce(&StartInstallation));
}
} // namespace
void SchedulePlatformRuntimeInstallationIfRequired() {
if (!base::FeatureList::IsEnabled(
webnn::mojom::features::kWebMachineLearningNeuralNetwork) ||
!base::FeatureList::IsEnabled(
webnn::mojom::features::kWebNNOnnxRuntime)) {
return;
}
if (base::win::GetVersion() < base::win::Version::WIN11_24H2) {
RecordInstallState(WinAppRuntimeInstallStateUma::kWindowsVersionTooOld);
return;
}
content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
->PostTask(FROM_HERE, base::BindOnce(&EnsureInstallation));
}
} // namespace webnn