chromium/chrome/browser/win/conflicts/uninstall_application.cc

// Copyright 2018 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/win/conflicts/uninstall_application.h"

#include <wrl/client.h>

#include <memory>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/pattern.h"
#include "base/strings/string_util.h"
#include "base/synchronization/lock.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/win/atl.h"
#include "base/win/scoped_variant.h"
#include "chrome/browser/win/automation_controller.h"
#include "chrome/browser/win/ui_automation_util.h"
#include "chrome/installer/util/shell_util.h"

namespace uninstall_application {

namespace {

bool FindSearchBoxElement(IUIAutomation* automation,
                          IUIAutomationElement* sender,
                          IUIAutomationElement** search_box) {
  // Create a condition that will include only elements with the right
  // automation id in the tree walker.
  base::win::ScopedVariant search_box_id(
      L"SystemSettings_StorageSense_AppSizesListFilter_DisplayStringValue");
  Microsoft::WRL::ComPtr<IUIAutomationCondition> condition;
  HRESULT result = automation->CreatePropertyCondition(
      UIA_AutomationIdPropertyId, search_box_id, &condition);
  if (FAILED(result))
    return false;

  Microsoft::WRL::ComPtr<IUIAutomationTreeWalker> tree_walker;
  result = automation->CreateTreeWalker(condition.Get(), &tree_walker);
  if (FAILED(result))
    return false;

  // Setup a cache request so that the element contains the needed property
  // afterwards.
  Microsoft::WRL::ComPtr<IUIAutomationCacheRequest> cache_request;
  result = automation->CreateCacheRequest(&cache_request);
  if (FAILED(result))
    return false;
  cache_request->AddPattern(UIA_ValuePatternId);

  result = tree_walker->GetNextSiblingElementBuildCache(
      sender, cache_request.Get(), search_box);
  return SUCCEEDED(result) && *search_box;
}

// UninstallAppController ------------------------------------------------------

class UninstallAppController {
 public:
  UninstallAppController(const UninstallAppController&) = delete;
  UninstallAppController& operator=(const UninstallAppController&) = delete;

  // Launches the Apps & Features page, ensuring the |application_name| is
  // written into the search box.
  static void Launch(const std::wstring& application_name);

 private:
  class AutomationControllerDelegate;

  // The unique instance of this class.
  static UninstallAppController* instance_;

  explicit UninstallAppController(const std::wstring& application_name);
  ~UninstallAppController();

  void OnUninstallFinished();

  // Allows the use of the UI Automation API.
  std::unique_ptr<AutomationController> automation_controller_;

  base::WeakPtrFactory<UninstallAppController> weak_ptr_factory_;
};

// static
UninstallAppController* UninstallAppController::instance_ = nullptr;

// static
void UninstallAppController::Launch(const std::wstring& application_name) {
  // If an instance already exists, the previous controller is deleted to make
  // sure it doesn't interfere with the current call.
  delete instance_;

  // The instance handles its own lifetime.
  instance_ = new UninstallAppController(application_name);
}

UninstallAppController::UninstallAppController(
    const std::wstring& application_name)
    : weak_ptr_factory_(this) {
  auto automation_controller_delegate =
      std::make_unique<AutomationControllerDelegate>(
          base::SequencedTaskRunner::GetCurrentDefault(),
          base::BindOnce(&UninstallAppController::OnUninstallFinished,
                         weak_ptr_factory_.GetWeakPtr()),
          application_name);

  automation_controller_ = std::make_unique<AutomationController>(
      std::move(automation_controller_delegate));
}

UninstallAppController::~UninstallAppController() = default;

void UninstallAppController::OnUninstallFinished() {
  DCHECK_EQ(this, instance_);

  delete this;
  instance_ = nullptr;
}

// UninstallAppController::AutomationControllerDelegate ------------------------

class UninstallAppController::AutomationControllerDelegate
    : public AutomationController::Delegate {
 public:
  AutomationControllerDelegate(
      scoped_refptr<base::SequencedTaskRunner> controller_runner,
      base::OnceClosure on_automation_finished,
      const std::wstring& application_name);

  AutomationControllerDelegate(const AutomationControllerDelegate&) = delete;
  AutomationControllerDelegate& operator=(const AutomationControllerDelegate&) =
      delete;

  ~AutomationControllerDelegate() override;

  // AutomationController::Delegate:
  void OnInitialized(HRESULT result) const override;
  void ConfigureCacheRequest(
      IUIAutomationCacheRequest* cache_request) const override;
  void OnAutomationEvent(IUIAutomation* automation,
                         IUIAutomationElement* sender,
                         EVENTID event_id) const override;
  void OnFocusChangedEvent(IUIAutomation* automation,
                           IUIAutomationElement* sender) const override;

 private:
  // The task runner on which the UninstallAppController lives.
  scoped_refptr<base::SequencedTaskRunner> controller_runner_;

  // Protect against concurrent accesses to |on_automation_finished_|.
  mutable base::Lock on_automation_finished_lock_;

  // Called once when the automation work is done.
  mutable base::OnceClosure on_automation_finished_;

  const std::wstring application_name_;
};

UninstallAppController::AutomationControllerDelegate::
    AutomationControllerDelegate(
        scoped_refptr<base::SequencedTaskRunner> controller_runner,
        base::OnceClosure on_automation_finished,
        const std::wstring& application_name)
    : controller_runner_(std::move(controller_runner)),
      on_automation_finished_(std::move(on_automation_finished)),
      application_name_(application_name) {}

UninstallAppController::AutomationControllerDelegate::
    ~AutomationControllerDelegate() = default;

void UninstallAppController::AutomationControllerDelegate::OnInitialized(
    HRESULT result) const {
  // Launch the Apps & Features settings page regardless of the |result| of the
  // initialization. An initialization failure only means that the application
  // will not be written into the search box.
  ShellUtil::LaunchUninstallAppsSettings();
}

void UninstallAppController::AutomationControllerDelegate::
    ConfigureCacheRequest(IUIAutomationCacheRequest* cache_request) const {
  cache_request->AddPattern(UIA_ValuePatternId);
  cache_request->AddProperty(UIA_AutomationIdPropertyId);
  cache_request->AddProperty(UIA_IsWindowPatternAvailablePropertyId);
}

void UninstallAppController::AutomationControllerDelegate::OnAutomationEvent(
    IUIAutomation* automation,
    IUIAutomationElement* sender,
    EVENTID event_id) const {}

void UninstallAppController::AutomationControllerDelegate::OnFocusChangedEvent(
    IUIAutomation* automation,
    IUIAutomationElement* sender) const {
  std::wstring combo_box_id(
      GetCachedBstrValue(sender, UIA_AutomationIdPropertyId));
  if (combo_box_id != L"SystemSettings_AppsFeatures_AppControl_ComboBox")
    return;

  base::OnceClosure callback;
  {
    base::AutoLock auto_lock(on_automation_finished_lock_);
    callback = std::move(on_automation_finished_);
  }

  // This callback can be null if the application name was already written in
  // the search box and this instance is awaiting destruction.
  if (!callback)
    return;

  Microsoft::WRL::ComPtr<IUIAutomationElement> search_box;
  if (!FindSearchBoxElement(automation, sender, &search_box))
    return;

  Microsoft::WRL::ComPtr<IUIAutomationValuePattern> value_pattern;
  HRESULT result = search_box->GetCachedPatternAs(UIA_ValuePatternId,
                                                  IID_PPV_ARGS(&value_pattern));
  if (FAILED(result))
    return;

  CComBSTR bstr(application_name_.c_str());
  value_pattern->SetValue(bstr);

  controller_runner_->PostTask(FROM_HERE, std::move(callback));
}

}  // namespace

void LaunchUninstallFlow(const std::wstring& application_name) {
  UninstallAppController::Launch(application_name);
}

}  // namespace uninstall_application