diff --git a/BUILD.gn b/BUILD.gn index ed822790f8..c27602f164 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -420,37 +420,6 @@ action("electron_generate_node_defines") { args = [ rebase_path(target_gen_dir) ] + rebase_path(inputs) } -# MSIX updater needs to be in a separate source_set because it uses C++/WinRT -# headers that require exceptions to be enabled. -source_set("electron_msix_updater") { - sources = [ - "shell/browser/api/electron_api_msix_updater.cc", - "shell/browser/api/electron_api_msix_updater.h", - ] - - configs += [ "//third_party/electron_node:node_external_config" ] - - public_configs = [ ":electron_lib_config" ] - - if (is_win) { - cflags_cc = [ - "/EHsc", # Enable C++ exceptions for C++/WinRT - "-Wno-c++98-compat-extra-semi", #Suppress C++98 compatibility warnings - ] - - include_dirs = [ "//third_party/nearby/src/internal/platform/implementation/windows/generated" ] - } - - deps = [ - "//base", - "//content/public/browser", - "//gin", - "//third_party/electron_node/deps/simdjson", - "//third_party/electron_node/deps/uv", - "//v8", - ] -} - source_set("electron_lib") { configs += [ "//v8:external_startup_data", @@ -466,7 +435,6 @@ source_set("electron_lib") { ":electron_fuses", ":electron_generate_node_defines", ":electron_js2c", - ":electron_msix_updater", ":electron_version_header", ":resources", "buildflags", diff --git a/filenames.gni b/filenames.gni index c40686d41d..b202f138bd 100644 --- a/filenames.gni +++ b/filenames.gni @@ -277,6 +277,8 @@ filenames = { "shell/browser/api/electron_api_in_app_purchase.h", "shell/browser/api/electron_api_menu.cc", "shell/browser/api/electron_api_menu.h", + "shell/browser/api/electron_api_msix_updater.cc", + "shell/browser/api/electron_api_msix_updater.h", "shell/browser/api/electron_api_native_theme.cc", "shell/browser/api/electron_api_native_theme.h", "shell/browser/api/electron_api_net_log.cc", diff --git a/shell/browser/api/electron_api_msix_updater.cc b/shell/browser/api/electron_api_msix_updater.cc index 21ed3fc33e..e9b90446f4 100644 --- a/shell/browser/api/electron_api_msix_updater.cc +++ b/shell/browser/api/electron_api_msix_updater.cc @@ -11,14 +11,9 @@ #include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "base/task/single_thread_task_runner.h" -#include "base/task/task_traits.h" -#include "base/task/thread_pool.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" -#include "shell/browser/browser.h" #include "shell/browser/javascript_environment.h" -#include "shell/browser/native_window.h" -#include "shell/browser/window_list.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/error_thrower.h" #include "shell/common/gin_helper/promise.h" @@ -33,16 +28,10 @@ #include #include #include -// Use pre-generated C++/WinRT headers from //third_party/nearby instead of the -// SDK's cppwinrt headers, which are missing implementation files. -#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.ApplicationModel.h" -#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Foundation.Collections.h" -#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Foundation.Metadata.h" -#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Foundation.h" -#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Management.Deployment.h" -#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/base.h" +#include -#include "base/win/scoped_com_initializer.h" +#include "base/win/core_winrt_util.h" +#include "base/win/scoped_hstring.h" #endif namespace electron { @@ -55,6 +44,53 @@ const bool debug_msix_updater = namespace { #if BUILDFLAG(IS_WIN) + +// Type aliases for cleaner code +using ABI::Windows::ApplicationModel::IAppInstallerInfo; +using ABI::Windows::ApplicationModel::IPackage; +using ABI::Windows::ApplicationModel::IPackage2; +using ABI::Windows::ApplicationModel::IPackage4; +using ABI::Windows::ApplicationModel::IPackage6; +using ABI::Windows::ApplicationModel::IPackageId; +using ABI::Windows::ApplicationModel::IPackageStatics; +using ABI::Windows::ApplicationModel::PackageSignatureKind; +using ABI::Windows::ApplicationModel::PackageSignatureKind_Developer; +using ABI::Windows::ApplicationModel::PackageSignatureKind_Enterprise; +using ABI::Windows::ApplicationModel::PackageSignatureKind_None; +using ABI::Windows::ApplicationModel::PackageSignatureKind_Store; +using ABI::Windows::ApplicationModel::PackageSignatureKind_System; +using ABI::Windows::Foundation::AsyncStatus; +using ABI::Windows::Foundation::IAsyncInfo; +using ABI::Windows::Foundation::IUriRuntimeClass; +using ABI::Windows::Foundation::IUriRuntimeClassFactory; +using ABI::Windows::Foundation::Metadata::IApiInformationStatics; +using ABI::Windows::Management::Deployment::DeploymentOptions; +using ABI::Windows::Management::Deployment:: + DeploymentOptions_ForceApplicationShutdown; +using ABI::Windows::Management::Deployment:: + DeploymentOptions_ForceTargetApplicationShutdown; +using ABI::Windows::Management::Deployment:: + DeploymentOptions_ForceUpdateFromAnyVersion; +using ABI::Windows::Management::Deployment::DeploymentOptions_None; +using ABI::Windows::Management::Deployment::IAddPackageOptions; +using ABI::Windows::Management::Deployment::IDeploymentResult; +using ABI::Windows::Management::Deployment::IPackageManager; +using ABI::Windows::Management::Deployment::IPackageManager5; +using ABI::Windows::Management::Deployment::IPackageManager9; +using Microsoft::WRL::Callback; +using Microsoft::WRL::ComPtr; + +// Type alias for deployment async operation +// AddPackageByUriAsync returns IAsyncOperationWithProgress +using DeploymentAsyncOp = ABI::Windows::Foundation::IAsyncOperationWithProgress< + ABI::Windows::Management::Deployment::DeploymentResult*, + ABI::Windows::Management::Deployment::DeploymentProgress>; +using DeploymentCompletedHandler = + ABI::Windows::Foundation::IAsyncOperationWithProgressCompletedHandler< + ABI::Windows::Management::Deployment::DeploymentResult*, + ABI::Windows::Management::Deployment::DeploymentProgress>; + // Helper function for debug logging void DebugLog(std::string_view log_msg) { if (electron::debug_msix_updater) @@ -84,32 +120,274 @@ struct RegisterPackageOptions { bool force_update_from_any_version = false; }; +// Helper: Create PackageManager using RoActivateInstance +// +// Note on COM interface versioning: In COM/WinRT, each interface version +// (IPackageManager, IPackageManager5, IPackageManager9, etc.) is a separate +// interface that must be queried independently. Unlike C++ inheritance, +// IPackageManager9 does NOT inherit methods from IPackageManager5 or the base +// IPackageManager. Each version only contains the methods that were newly +// added in that version. To call methods from different versions, you must +// QueryInterface (or ComPtr::As) for each specific interface version needed. +HRESULT CreatePackageManager(ComPtr* package_manager) { + base::win::ScopedHString class_id = base::win::ScopedHString::Create( + RuntimeClass_Windows_Management_Deployment_PackageManager); + if (!class_id.is_valid()) { + return E_FAIL; + } + + ComPtr inspectable; + HRESULT hr = base::win::RoActivateInstance(class_id.get(), &inspectable); + if (FAILED(hr)) { + return hr; + } + + return inspectable.As(package_manager); +} + +// Helper: Create URI using IUriRuntimeClassFactory +HRESULT CreateUri(const std::wstring& uri_string, + ComPtr* uri) { + ComPtr uri_factory; + HRESULT hr = + base::win::GetActivationFactory( + &uri_factory); + if (FAILED(hr)) { + return hr; + } + + base::win::ScopedHString uri_hstring = + base::win::ScopedHString::Create(uri_string); + if (!uri_hstring.is_valid()) { + return E_FAIL; + } + + return uri_factory->CreateUri(uri_hstring.get(), uri->GetAddressOf()); +} + +// Helper: Create and configure AddPackageOptions +HRESULT CreateAddPackageOptions(const UpdateMsixOptions& opts, + ComPtr* package_options) { + base::win::ScopedHString class_id = base::win::ScopedHString::Create( + RuntimeClass_Windows_Management_Deployment_AddPackageOptions); + if (!class_id.is_valid()) { + return E_FAIL; + } + + ComPtr inspectable; + HRESULT hr = base::win::RoActivateInstance(class_id.get(), &inspectable); + if (FAILED(hr)) { + return hr; + } + + hr = inspectable.As(package_options); + if (FAILED(hr)) { + return hr; + } + + // Configure options using ABI interface methods + (*package_options) + ->put_DeferRegistrationWhenPackagesAreInUse(opts.defer_registration); + (*package_options)->put_DeveloperMode(opts.developer_mode); + (*package_options)->put_ForceAppShutdown(opts.force_shutdown); + (*package_options)->put_ForceTargetAppShutdown(opts.force_target_shutdown); + (*package_options) + ->put_ForceUpdateFromAnyVersion(opts.force_update_from_any_version); + + return S_OK; +} + +// Helper: Check if API contract is present +HRESULT CheckApiContractPresent(UINT16 version, boolean* is_present) { + ComPtr api_info; + HRESULT hr = base::win::GetActivationFactory< + IApiInformationStatics, + RuntimeClass_Windows_Foundation_Metadata_ApiInformation>(&api_info); + if (FAILED(hr)) { + return hr; + } + + base::win::ScopedHString contract_name = base::win::ScopedHString::Create( + L"Windows.Foundation.UniversalApiContract"); + if (!contract_name.is_valid()) { + return E_FAIL; + } + + return api_info->IsApiContractPresentByMajor(contract_name.get(), version, + is_present); +} + +// Helper: Get current package using IPackageStatics +HRESULT GetCurrentPackage(ComPtr* package) { + ComPtr package_statics; + HRESULT hr = base::win::GetActivationFactory< + IPackageStatics, RuntimeClass_Windows_ApplicationModel_Package>( + &package_statics); + if (FAILED(hr)) { + return hr; + } + + return package_statics->get_Current(package->GetAddressOf()); +} + +// Structure to hold callback data for async operations +struct DeploymentCallbackData { + scoped_refptr reply_runner; + gin_helper::Promise promise; + bool fire_and_forget; + ComPtr async_op; // Keep async_op alive + std::string operation_name; // "Deployment" or "Registration" for logs +}; + +// Handler for deployment/registration completion +void OnDeploymentCompleted(std::unique_ptr data, + DeploymentAsyncOp* async_op, + AsyncStatus status) { + std::string error; + const std::string& op_name = data->operation_name; + + if (data->fire_and_forget) { + std::ostringstream oss; + oss << op_name + << " initiated. Force shutdown or target shutdown requested. " + "Good bye!"; + DebugLog(oss.str()); + // Don't wait for result in fire-and-forget mode + data->reply_runner->PostTask( + FROM_HERE, + base::BindOnce( + [](gin_helper::Promise promise) { promise.Resolve(); }, + std::move(data->promise))); + return; + } + + if (status == AsyncStatus::Error) { + ComPtr result; + HRESULT hr = async_op->GetResults(&result); + if (SUCCEEDED(hr) && result) { + HSTRING error_text_hstring; + hr = result->get_ErrorText(&error_text_hstring); + if (SUCCEEDED(hr)) { + base::win::ScopedHString scoped_error(error_text_hstring); + error = scoped_error.GetAsUTF8(); + } + + ComPtr async_info; + hr = async_op->QueryInterface(IID_PPV_ARGS(&async_info)); + if (SUCCEEDED(hr)) { + HRESULT error_code; + hr = async_info->get_ErrorCode(&error_code); + if (SUCCEEDED(hr)) { + error += " (" + std::to_string(static_cast(error_code)) + ")"; + } + } + } + if (error.empty()) { + error = op_name + " failed with unknown error"; + } + { + std::ostringstream oss; + oss << op_name << " failed: " << error; + DebugLog(oss.str()); + } + } else if (status == AsyncStatus::Canceled) { + std::ostringstream oss; + oss << op_name << " canceled"; + DebugLog(oss.str()); + error = op_name + " canceled"; + } else if (status == AsyncStatus::Completed) { + std::ostringstream oss; + oss << "MSIX " << op_name << " completed."; + DebugLog(oss.str()); + } else { + error = op_name + " status unknown"; + std::ostringstream oss; + oss << op_name << " status unknown"; + DebugLog(oss.str()); + } + + // Post result back to UI thread + data->reply_runner->PostTask( + FROM_HERE, base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + if (error.empty()) { + promise.Resolve(); + } else { + promise.RejectWithErrorMessage(error); + } + }, + std::move(data->promise), std::move(error))); +} + // Performs MSIX update on IO thread void DoUpdateMsix(const std::string& package_uri, UpdateMsixOptions opts, scoped_refptr reply_runner, gin_helper::Promise promise) { DebugLog("DoUpdateMsix: Starting"); - - using winrt::Windows::Foundation::AsyncStatus; - using winrt::Windows::Foundation::Uri; - using winrt::Windows::Management::Deployment::AddPackageOptions; - using winrt::Windows::Management::Deployment::DeploymentResult; - using winrt::Windows::Management::Deployment::PackageManager; - std::string error; - std::wstring packageUriString = - std::wstring(package_uri.begin(), package_uri.end()); - Uri uri{packageUriString}; - PackageManager packageManager; - AddPackageOptions packageOptions; - // Use the pre-parsed options - packageOptions.DeferRegistrationWhenPackagesAreInUse(opts.defer_registration); - packageOptions.DeveloperMode(opts.developer_mode); - packageOptions.ForceAppShutdown(opts.force_shutdown); - packageOptions.ForceTargetAppShutdown(opts.force_target_shutdown); - packageOptions.ForceUpdateFromAnyVersion(opts.force_update_from_any_version); + // Create PackageManager + ComPtr package_manager; + HRESULT hr = CreatePackageManager(&package_manager); + if (FAILED(hr)) { + error = "Failed to create PackageManager"; + reply_runner->PostTask( + FROM_HERE, + base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; + } + + // Get IPackageManager9 for AddPackageByUriAsync + ComPtr package_manager9; + hr = package_manager.As(&package_manager9); + if (FAILED(hr)) { + error = "Failed to get IPackageManager9 interface"; + reply_runner->PostTask( + FROM_HERE, + base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; + } + + // Create URI + std::wstring uri_wstring = base::UTF8ToWide(package_uri); + ComPtr uri; + hr = CreateUri(uri_wstring, &uri); + if (FAILED(hr)) { + error = "Failed to create URI"; + reply_runner->PostTask( + FROM_HERE, + base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; + } + + // Create AddPackageOptions + ComPtr package_options; + hr = CreateAddPackageOptions(opts, &package_options); + if (FAILED(hr)) { + error = "Failed to create AddPackageOptions"; + reply_runner->PostTask( + FROM_HERE, + base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; + } { std::ostringstream oss; @@ -127,63 +405,54 @@ void DoUpdateMsix(const std::string& package_uri, DebugLog(oss.str()); } - auto deploymentOperation = - packageManager.AddPackageByUriAsync(uri, packageOptions); - - if (!deploymentOperation) { - DebugLog("Deployment operation is null"); + // Start async operation + ComPtr async_op; + hr = package_manager9->AddPackageByUriAsync(uri.Get(), package_options.Get(), + &async_op); + if (FAILED(hr) || !async_op) { + DebugLog("AddPackageByUriAsync failed or returned null"); error = "Deployment is NULL. See " "http://go.microsoft.com/fwlink/?LinkId=235160 for diagnosing."; - } else { - if (!opts.force_shutdown && !opts.force_target_shutdown) { - DebugLog("Waiting for deployment..."); - deploymentOperation.get(); - DebugLog("Deployment finished."); - - if (deploymentOperation.Status() == AsyncStatus::Error) { - auto deploymentResult{deploymentOperation.GetResults()}; - std::string errorText = winrt::to_string(deploymentResult.ErrorText()); - std::string errorCode = - std::to_string(static_cast(deploymentOperation.ErrorCode())); - error = errorText + " (" + errorCode + ")"; - { - std::ostringstream oss; - oss << "Deployment failed: " << error; - DebugLog(oss.str()); - } - } else if (deploymentOperation.Status() == AsyncStatus::Canceled) { - DebugLog("Deployment canceled"); - error = "Deployment canceled"; - } else if (deploymentOperation.Status() == AsyncStatus::Completed) { - DebugLog("MSIX Deployment completed."); - } else { - error = "Deployment status unknown"; - DebugLog("Deployment status unknown"); - } - } else { - // At this point, we can not await the deployment because we require a - // shutdown of the app to continue, so we do a fire and forget. When the - // deployment process tries ot shutdown the app, the process waits for us - // to finish here. But to finish we need to shutdow. That leads to a 30s - // dealock, till we forcefully get shutdown by the OS. - DebugLog( - "Deployment initiated. Force shutdown or target shutdown requested. " - "Good bye!"); - } + reply_runner->PostTask( + FROM_HERE, + base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; } - // Post result back - reply_runner->PostTask( - FROM_HERE, base::BindOnce( - [](gin_helper::Promise promise, std::string error) { - if (error.empty()) { - promise.Resolve(); - } else { - promise.RejectWithErrorMessage(error); - } - }, - std::move(promise), error)); + // Set up callback data + auto callback_data = std::make_unique(); + callback_data->reply_runner = reply_runner; + callback_data->promise = std::move(promise); + callback_data->fire_and_forget = + opts.force_shutdown || opts.force_target_shutdown; + callback_data->async_op = async_op; // Keep async_op alive + callback_data->operation_name = "Deployment"; + + // Register completion handler + DeploymentCallbackData* raw_data = callback_data.get(); + hr = async_op->put_Completed( + Callback([data = std::move(callback_data)]( + DeploymentAsyncOp* op, + AsyncStatus status) mutable { + OnDeploymentCompleted(std::move(data), op, status); + return S_OK; + }).Get()); + + if (FAILED(hr)) { + DebugLog("Failed to register completion handler"); + raw_data->reply_runner->PostTask( + FROM_HERE, base::BindOnce( + [](gin_helper::Promise promise) { + promise.RejectWithErrorMessage( + "Failed to register completion handler"); + }, + std::move(raw_data->promise))); + } } // Performs package registration on IO thread @@ -192,31 +461,67 @@ void DoRegisterPackage(const std::string& family_name, scoped_refptr reply_runner, gin_helper::Promise promise) { DebugLog("DoRegisterPackage: Starting"); - - using winrt::Windows::Foundation::AsyncStatus; - using winrt::Windows::Foundation::Collections::IIterable; - using winrt::Windows::Management::Deployment::DeploymentOptions; - using winrt::Windows::Management::Deployment::PackageManager; - std::string error; - auto familyNameH = winrt::to_hstring(family_name); - PackageManager packageManager; - DeploymentOptions deploymentOptions = DeploymentOptions::None; - // Use the pre-parsed options (no V8 access needed) + // Create PackageManager + ComPtr package_manager; + HRESULT hr = CreatePackageManager(&package_manager); + if (FAILED(hr)) { + error = "Failed to create PackageManager"; + reply_runner->PostTask( + FROM_HERE, + base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; + } + + // Get IPackageManager5 for RegisterPackageByFamilyNameAsync + ComPtr package_manager5; + hr = package_manager.As(&package_manager5); + if (FAILED(hr)) { + error = "Failed to get IPackageManager5 interface"; + reply_runner->PostTask( + FROM_HERE, + base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; + } + + // Build DeploymentOptions flags + DeploymentOptions deployment_options = DeploymentOptions_None; if (opts.force_shutdown) { - deploymentOptions |= DeploymentOptions::ForceApplicationShutdown; + deployment_options = static_cast( + deployment_options | DeploymentOptions_ForceApplicationShutdown); } if (opts.force_target_shutdown) { - deploymentOptions |= DeploymentOptions::ForceTargetApplicationShutdown; + deployment_options = static_cast( + deployment_options | DeploymentOptions_ForceTargetApplicationShutdown); } if (opts.force_update_from_any_version) { - deploymentOptions |= DeploymentOptions::ForceUpdateFromAnyVersion; + deployment_options = static_cast( + deployment_options | DeploymentOptions_ForceUpdateFromAnyVersion); } - // Create empty collections for dependency and optional packages - IIterable emptyDependencies{nullptr}; - IIterable emptyOptional{nullptr}; + // Create HSTRING for family name + base::win::ScopedHString family_name_hstring = + base::win::ScopedHString::Create(family_name); + if (!family_name_hstring.is_valid()) { + error = "Failed to create family name string"; + reply_runner->PostTask( + FROM_HERE, + base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; + } { std::ostringstream oss; @@ -233,63 +538,59 @@ void DoRegisterPackage(const std::string& family_name, DebugLog(oss.str()); } - auto deploymentOperation = packageManager.RegisterPackageByFamilyNameAsync( - familyNameH, emptyDependencies, deploymentOptions, nullptr, - emptyOptional); + // RegisterPackageByFamilyNameAndOptionalPackagesAsync (ABI name) + ComPtr async_op; + hr = package_manager5->RegisterPackageByFamilyNameAndOptionalPackagesAsync( + family_name_hstring.get(), + nullptr, // dependencyPackageFamilyNames + deployment_options, + nullptr, // appDataVolume + nullptr, // optionalPackageFamilyNames + &async_op); - if (!deploymentOperation) { + if (FAILED(hr) || !async_op) { error = "Deployment is NULL. See " "http://go.microsoft.com/fwlink/?LinkId=235160 for diagnosing."; - } else { - if (!opts.force_shutdown && !opts.force_target_shutdown) { - DebugLog("Waiting for registration..."); - deploymentOperation.get(); - DebugLog("Registration finished."); - - if (deploymentOperation.Status() == AsyncStatus::Error) { - auto deploymentResult{deploymentOperation.GetResults()}; - std::string errorText = winrt::to_string(deploymentResult.ErrorText()); - std::string errorCode = - std::to_string(static_cast(deploymentOperation.ErrorCode())); - error = errorText + " (" + errorCode + ")"; - { - std::ostringstream oss; - oss << "Registration failed: " << error; - DebugLog(oss.str()); - } - } else if (deploymentOperation.Status() == AsyncStatus::Canceled) { - DebugLog("Registration canceled"); - error = "Registration canceled"; - } else if (deploymentOperation.Status() == AsyncStatus::Completed) { - DebugLog("MSIX Registration completed."); - } else { - error = "Registration status unknown"; - DebugLog("Registration status unknown"); - } - } else { - // At this point, we can not await the registration because we require a - // shutdown of the app to continue, so we do a fire and forget. When the - // registration process tries ot shutdown the app, the process waits for - // us to finish here. But to finish we need to shutdown. That leads to a - // 30s dealock, till we forcefully get shutdown by the OS. - DebugLog( - "Registration initiated. Force shutdown or target shutdown " - "requested. Good bye!"); - } + reply_runner->PostTask( + FROM_HERE, + base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; } - // Post result back to UI thread - reply_runner->PostTask( - FROM_HERE, base::BindOnce( - [](gin_helper::Promise promise, std::string error) { - if (error.empty()) { - promise.Resolve(); - } else { - promise.RejectWithErrorMessage(error); - } - }, - std::move(promise), error)); + // Set up callback data + auto callback_data = std::make_unique(); + callback_data->reply_runner = reply_runner; + callback_data->promise = std::move(promise); + callback_data->fire_and_forget = + opts.force_shutdown || opts.force_target_shutdown; + callback_data->async_op = async_op; // Keep async_op alive + callback_data->operation_name = "Registration"; + + // Register completion handler + DeploymentCallbackData* raw_data = callback_data.get(); + hr = async_op->put_Completed( + Callback([data = std::move(callback_data)]( + DeploymentAsyncOp* op, + AsyncStatus status) mutable { + OnDeploymentCompleted(std::move(data), op, status); + return S_OK; + }).Get()); + + if (FAILED(hr)) { + DebugLog("Failed to register completion handler"); + raw_data->reply_runner->PostTask( + FROM_HERE, base::BindOnce( + [](gin_helper::Promise promise) { + promise.RejectWithErrorMessage( + "Failed to register completion handler"); + }, + std::move(raw_data->promise))); + } } #endif @@ -307,6 +608,16 @@ v8::Local UpdateMsix(const std::string& package_uri, return handle; } + // Check for required API contract (IPackageManager9 requires v10) + boolean is_api_present = FALSE; + if (FAILED(CheckApiContractPresent(10, &is_api_present)) || !is_api_present) { + DebugLog("UpdateMsix: Required Windows API contract not present"); + promise.RejectWithErrorMessage( + "This Windows version does not support MSIX updates via this API. " + "Windows 10 version 2004 or later is required."); + return handle; + } + // Parse options on UI thread (where V8 is available) UpdateMsixOptions opts; options.Get("deferRegistration", &opts.defer_registration); @@ -349,6 +660,16 @@ v8::Local RegisterPackage(const std::string& family_name, return handle; } + // Check for required API contract (IPackageManager5 requires v3) + boolean is_api_present = FALSE; + if (FAILED(CheckApiContractPresent(3, &is_api_present)) || !is_api_present) { + DebugLog("RegisterPackage: Required Windows API contract not present"); + promise.RejectWithErrorMessage( + "This Windows version does not support package registration via this " + "API. Windows 10 version 1607 or later is required."); + return handle; + } + // Parse options on UI thread (where V8 is available) RegisterPackageOptions opts; options.Get("forceShutdown", &opts.force_shutdown); @@ -384,32 +705,30 @@ bool RegisterRestartOnUpdate(const std::string& command_line) { return false; } - const wchar_t* commandLine = nullptr; // Flags: RESTART_NO_CRASH | RESTART_NO_HANG | RESTART_NO_REBOOT // This means: only restart on updates (RESTART_NO_PATCH is NOT set) const DWORD dwFlags = 1 | 2 | 8; // 11 + // Convert command line to wide string (keep in scope for API call) + std::wstring command_line_wide; + const wchar_t* command_line_ptr = nullptr; if (!command_line.empty()) { - std::wstring commandLineW = - std::wstring(command_line.begin(), command_line.end()); - commandLine = commandLineW.c_str(); + command_line_wide = base::UTF8ToWide(command_line); + command_line_ptr = command_line_wide.c_str(); } - HRESULT hr = RegisterApplicationRestart(commandLine, dwFlags); + HRESULT hr = RegisterApplicationRestart(command_line_ptr, dwFlags); if (FAILED(hr)) { - { - std::ostringstream oss; - oss << "RegisterApplicationRestart failed with error code: " << hr; - DebugLog(oss.str()); - } + std::ostringstream oss; + oss << "RegisterApplicationRestart failed with error code: " << hr; + DebugLog(oss.str()); return false; } - { - std::ostringstream oss; - oss << "RegisterApplicationRestart succeeded" - << (command_line.empty() ? "" : " with command line"); - DebugLog(oss.str()); - } + + std::ostringstream oss; + oss << "RegisterApplicationRestart succeeded" + << (command_line.empty() ? "" : " with command line"); + DebugLog(oss.str()); return true; #else return false; @@ -434,57 +753,119 @@ v8::Local GetPackageInfo() { gin_helper::Dictionary result(isolate, v8::Object::New(isolate)); // Check API contract version (Windows 10 version 1703 or later) - if (winrt::Windows::Foundation::Metadata::ApiInformation:: - IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 7)) { - using winrt::Windows::ApplicationModel::Package; - using winrt::Windows::ApplicationModel::PackageSignatureKind; - Package package = Package::Current(); + boolean is_present = FALSE; + HRESULT hr = CheckApiContractPresent(7, &is_present); + if (SUCCEEDED(hr) && is_present) { + ComPtr package; + hr = GetCurrentPackage(&package); + if (SUCCEEDED(hr) && package) { + // Query all needed package interface versions upfront. + // Note: Like IPackageManager, each IPackage version (IPackage2, + // IPackage4, IPackage6) is a separate COM interface. IPackage6 does NOT + // inherit methods from earlier versions. We must query each version + // separately to access its specific methods: + // - IPackage2: get_IsDevelopmentMode + // - IPackage4: get_SignatureKind + // - IPackage6: GetAppInstallerInfo + ComPtr package2; + ComPtr package4; + ComPtr package6; + package.As(&package2); + package.As(&package4); + package.As(&package6); - // Get package ID and family name - std::string packageId = winrt::to_string(package.Id().FullName()); - std::string familyName = winrt::to_string(package.Id().FamilyName()); + // Get package ID (from base IPackage) + ComPtr package_id; + hr = package->get_Id(&package_id); + if (SUCCEEDED(hr) && package_id) { + // Get FullName + HSTRING full_name; + hr = package_id->get_FullName(&full_name); + if (SUCCEEDED(hr)) { + base::win::ScopedHString scoped_name(full_name); + result.Set("id", scoped_name.GetAsUTF8()); + } - result.Set("id", packageId); - result.Set("familyName", familyName); - result.Set("developmentMode", package.IsDevelopmentMode()); + // Get FamilyName + HSTRING family_name; + hr = package_id->get_FamilyName(&family_name); + if (SUCCEEDED(hr)) { + base::win::ScopedHString scoped_name(family_name); + result.Set("familyName", scoped_name.GetAsUTF8()); + } - // Get package version - auto packageVersion = package.Id().Version(); - std::string version = std::to_string(packageVersion.Major) + "." + - std::to_string(packageVersion.Minor) + "." + - std::to_string(packageVersion.Build) + "." + - std::to_string(packageVersion.Revision); - result.Set("version", version); + // Get Version + ABI::Windows::ApplicationModel::PackageVersion pkg_version; + hr = package_id->get_Version(&pkg_version); + if (SUCCEEDED(hr)) { + std::string version = std::to_string(pkg_version.Major) + "." + + std::to_string(pkg_version.Minor) + "." + + std::to_string(pkg_version.Build) + "." + + std::to_string(pkg_version.Revision); + result.Set("version", version); + } + } - // Convert signature kind to string - std::string signatureKind; - switch (package.SignatureKind()) { - case PackageSignatureKind::Developer: - signatureKind = "developer"; - break; - case PackageSignatureKind::Enterprise: - signatureKind = "enterprise"; - break; - case PackageSignatureKind::None: - signatureKind = "none"; - break; - case PackageSignatureKind::Store: - signatureKind = "store"; - break; - case PackageSignatureKind::System: - signatureKind = "system"; - break; - default: - signatureKind = "none"; - break; - } - result.Set("signatureKind", signatureKind); + // Get IsDevelopmentMode (from IPackage2) + if (package2) { + boolean is_dev_mode = FALSE; + hr = package2->get_IsDevelopmentMode(&is_dev_mode); + result.Set("developmentMode", SUCCEEDED(hr) && is_dev_mode != FALSE); + } else { + result.Set("developmentMode", false); + } - // Get app installer info if available - auto appInstallerInfo = package.GetAppInstallerInfo(); - if (appInstallerInfo != nullptr) { - std::string uriStr = winrt::to_string(appInstallerInfo.Uri().ToString()); - result.Set("appInstallerUri", uriStr); + // Get SignatureKind (from IPackage4) + if (package4) { + PackageSignatureKind sig_kind; + hr = package4->get_SignatureKind(&sig_kind); + if (SUCCEEDED(hr)) { + std::string signature_kind; + switch (sig_kind) { + case PackageSignatureKind_Developer: + signature_kind = "developer"; + break; + case PackageSignatureKind_Enterprise: + signature_kind = "enterprise"; + break; + case PackageSignatureKind_None: + signature_kind = "none"; + break; + case PackageSignatureKind_Store: + signature_kind = "store"; + break; + case PackageSignatureKind_System: + signature_kind = "system"; + break; + default: + signature_kind = "none"; + break; + } + result.Set("signatureKind", signature_kind); + } else { + result.Set("signatureKind", "none"); + } + } else { + result.Set("signatureKind", "none"); + } + + // Get AppInstallerInfo (from IPackage6) + if (package6) { + ComPtr app_installer_info; + hr = package6->GetAppInstallerInfo(&app_installer_info); + if (SUCCEEDED(hr) && app_installer_info) { + ComPtr uri; + hr = app_installer_info->get_Uri(&uri); + if (SUCCEEDED(hr) && uri) { + HSTRING uri_string; + hr = uri->get_AbsoluteUri(&uri_string); + if (SUCCEEDED(hr)) { + base::win::ScopedHString scoped_uri(uri_string); + result.Set("appInstallerUri", scoped_uri.GetAsUTF8()); + } + } + } + } } } else { // Windows version doesn't meet minimum API requirements