Commit 6b477d9e authored by Hoch Hochkeppel's avatar Hoch Hochkeppel Committed by Commit Bot

WebShare: Add Windows Share API Wrapper

Adding a wrapper function around Window's Share operation to allow using
it like a traditional async operation.

Adding tests (and one more test-helper class) that verify the behavior
of the wrapper function.

Bug: 1035527
Change-Id: I71cc7aaa6826d53cc1273c99b603db89612f1544
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2402293
Commit-Queue: Hoch Hochkeppel <mhochk@microsoft.com>
Reviewed-by: default avatarRobert Liao <robliao@chromium.org>
Reviewed-by: default avatarEric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#811318}
parent 46031628
......@@ -4462,6 +4462,8 @@ static_library("browser") {
"themes/theme_helper_win.cc",
"themes/theme_helper_win.h",
"upgrade_detector/get_installed_version_win.cc",
"webshare/win/show_share_ui_for_window_operation.cc",
"webshare/win/show_share_ui_for_window_operation.h",
"win/app_icon.cc",
"win/app_icon.h",
"win/automation_controller.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/webshare/win/scoped_fake_data_transfer_manager_interop.h"
#include <windows.applicationmodel.datatransfer.h>
#include <wrl/implements.h>
#include "base/strings/string_piece.h"
#include "base/win/com_init_util.h"
#include "base/win/core_winrt_util.h"
#include "base/win/win_util.h"
#include "chrome/browser/webshare/win/fake_data_transfer_manager_interop.h"
#include "chrome/browser/webshare/win/show_share_ui_for_window_operation.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace webshare {
namespace {
static FakeDataTransferManagerInterop* g_current_fake_interop = nullptr;
static HRESULT FakeRoGetActivationFactory(HSTRING class_id,
const IID& iid,
void** out_factory) {
base::win::ScopedHString class_id_hstring(class_id);
EXPECT_STREQ(
class_id_hstring.Get().data(),
RuntimeClass_Windows_ApplicationModel_DataTransfer_DataTransferManager);
if (g_current_fake_interop == nullptr) {
ADD_FAILURE();
return E_UNEXPECTED;
}
*out_factory = g_current_fake_interop;
g_current_fake_interop->AddRef();
return S_OK;
}
} // namespace
ScopedFakeDataTransferManagerInterop::ScopedFakeDataTransferManagerInterop() {
// Initialization work is done in an independent function so that the
// various test macros can be used.
Initialize();
}
ScopedFakeDataTransferManagerInterop::~ScopedFakeDataTransferManagerInterop() {
g_current_fake_interop = nullptr;
ShowShareUIForWindowOperation::SetRoGetActivationFactoryFunctionForTesting(
&base::win::RoGetActivationFactory);
}
FakeDataTransferManagerInterop&
ScopedFakeDataTransferManagerInterop::instance() {
return *(instance_.Get());
}
void ScopedFakeDataTransferManagerInterop::Initialize() {
ASSERT_TRUE(base::win::ResolveCoreWinRTDelayload());
ASSERT_TRUE(base::win::ScopedHString::ResolveCoreWinRTStringDelayload());
base::win::AssertComInitialized();
instance_ = Microsoft::WRL::Make<FakeDataTransferManagerInterop>();
// Confirm there is no competing instance and set this instance
// as the factory for the data_transfer_manager_util
ASSERT_EQ(g_current_fake_interop, nullptr);
g_current_fake_interop = instance_.Get();
ShowShareUIForWindowOperation::SetRoGetActivationFactoryFunctionForTesting(
&FakeRoGetActivationFactory);
}
} // namespace webshare
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_WEBSHARE_WIN_SCOPED_FAKE_DATA_TRANSFER_MANAGER_INTEROP_H_
#define CHROME_BROWSER_WEBSHARE_WIN_SCOPED_FAKE_DATA_TRANSFER_MANAGER_INTEROP_H_
#include <wrl/client.h>
namespace webshare {
class FakeDataTransferManagerInterop;
// Creates and registers a FakeDataTransferManagerInterop on creation and cleans
// it up on tear down, allowing GTests to easily simulate the Windows APIs used
// for the Share contract.
class ScopedFakeDataTransferManagerInterop {
public:
ScopedFakeDataTransferManagerInterop();
ScopedFakeDataTransferManagerInterop(
const ScopedFakeDataTransferManagerInterop&) = delete;
ScopedFakeDataTransferManagerInterop& operator=(
const ScopedFakeDataTransferManagerInterop&) = delete;
~ScopedFakeDataTransferManagerInterop();
FakeDataTransferManagerInterop& instance();
private:
void Initialize();
Microsoft::WRL::ComPtr<FakeDataTransferManagerInterop> instance_;
};
} // namespace webshare
#endif // CHROME_BROWSER_WEBSHARE_WIN_SCOPED_FAKE_DATA_TRANSFER_MANAGER_INTEROP_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/webshare/win/show_share_ui_for_window_operation.h"
#include <shlobj.h>
#include <windows.applicationmodel.datatransfer.h>
#include <wrl/event.h>
#include "base/callback.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/win/core_winrt_util.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
using ABI::Windows::ApplicationModel::DataTransfer::DataRequestedEventArgs;
using ABI::Windows::ApplicationModel::DataTransfer::DataTransferManager;
using ABI::Windows::ApplicationModel::DataTransfer::IDataRequestedEventArgs;
using ABI::Windows::ApplicationModel::DataTransfer::IDataTransferManager;
using ABI::Windows::Foundation::ITypedEventHandler;
using Microsoft::WRL::Callback;
using Microsoft::WRL::ComPtr;
namespace webshare {
namespace {
decltype(
&base::win::RoGetActivationFactory) ro_get_activation_factory_function_ =
&base::win::RoGetActivationFactory;
// Fetches handles to the IDataTransferManager[Interop] instances for the
// given |hwnd|
HRESULT GetDataTransferManagerHandles(
HWND hwnd,
IDataTransferManagerInterop** data_transfer_manager_interop,
IDataTransferManager** data_transfer_manager) {
// If the required WinRT functionality is not available, fail the operation
if (!base::win::ResolveCoreWinRTDelayload() ||
!base::win::ScopedHString::ResolveCoreWinRTStringDelayload()) {
return E_FAIL;
}
// IDataTransferManagerInterop is semi-hidden behind a CloakedIid
// structure on the DataTransferManager, excluding it from things
// used by RoGetActivationFactory like GetIids(). Because of this,
// the safe way to fetch a pointer to it is through a publicly
// supported IID (e.g. IUnknown), followed by a QueryInterface call
// (or something that simply wraps it like As()) to convert it.
auto class_id_hstring = base::win::ScopedHString::Create(
RuntimeClass_Windows_ApplicationModel_DataTransfer_DataTransferManager);
if (!class_id_hstring.is_valid())
return E_FAIL;
ComPtr<IUnknown> data_transfer_manager_factory;
HRESULT hr = ro_get_activation_factory_function_(
class_id_hstring.get(), IID_PPV_ARGS(&data_transfer_manager_factory));
if (FAILED(hr))
return hr;
hr = data_transfer_manager_factory->QueryInterface(
data_transfer_manager_interop);
if (FAILED(hr))
return hr;
hr = (*data_transfer_manager_interop)
->GetForWindow(hwnd, IID_PPV_ARGS(data_transfer_manager));
return hr;
}
} // namespace
ShowShareUIForWindowOperation::ShowShareUIForWindowOperation(HWND hwnd)
: hwnd_(hwnd) {
data_requested_token_.value = 0;
}
ShowShareUIForWindowOperation::~ShowShareUIForWindowOperation() {
Cancel();
}
// static
void ShowShareUIForWindowOperation::SetRoGetActivationFactoryFunctionForTesting(
decltype(&base::win::RoGetActivationFactory) value) {
ro_get_activation_factory_function_ = value;
}
void ShowShareUIForWindowOperation::Run(
DataRequestedCallback data_requested_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
data_requested_callback_ = std::move(data_requested_callback);
// Fetch the OS handles needed
ComPtr<IDataTransferManagerInterop> data_transfer_manager_interop;
HRESULT hr = GetDataTransferManagerHandles(
hwnd_, &data_transfer_manager_interop, &data_transfer_manager_);
if (FAILED(hr))
return Cancel();
// Create and register a data request handler
auto weak_ptr = weak_factory_.GetWeakPtr();
auto raw_data_requested_callback = Callback<
ITypedEventHandler<DataTransferManager*, DataRequestedEventArgs*>>(
[weak_ptr](IDataTransferManager* data_transfer_manager,
IDataRequestedEventArgs* event_args) -> HRESULT {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (weak_ptr)
weak_ptr.get()->OnDataRequested(data_transfer_manager, event_args);
// Always return S_OK, as returning a FAILED value results in the OS
// killing this process. If the data population failed the OS Share
// operation will fail gracefully with messaging to the user.
return S_OK;
});
hr = data_transfer_manager_->add_DataRequested(
raw_data_requested_callback.Get(), &data_requested_token_);
if (FAILED(hr))
return Cancel();
// Request showing the Share UI
show_share_ui_for_window_call_in_progress_ = true;
hr = data_transfer_manager_interop->ShowShareUIForWindow(hwnd_);
show_share_ui_for_window_call_in_progress_ = false;
// If the call is expected to complete later, schedule a timeout to cover
// any cases where it fails (and therefore never comes)
if (SUCCEEDED(hr) && data_requested_callback_) {
if (!base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ShowShareUIForWindowOperation::Cancel,
weak_factory_.GetWeakPtr()),
kMaxExecutionTime)) {
return Cancel();
}
} else {
RemoveDataRequestedListener();
}
}
void ShowShareUIForWindowOperation::Cancel() {
RemoveDataRequestedListener();
if (data_requested_callback_) {
std::move(data_requested_callback_).Run(nullptr);
}
}
void ShowShareUIForWindowOperation::OnDataRequested(
IDataTransferManager* data_transfer_manager,
IDataRequestedEventArgs* event_args) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(data_transfer_manager, data_transfer_manager_.Get());
// Remove the DataRequested handler if this is being invoked asynchronously.
// If this is an in-progress invocation the system APIs don't handle the
// event being unregistered while it is being executed, but we will unregister
// it after the ShowShareUIForWindow call completes.
if (!show_share_ui_for_window_call_in_progress_)
RemoveDataRequestedListener();
std::move(data_requested_callback_).Run(event_args);
}
void ShowShareUIForWindowOperation::RemoveDataRequestedListener() {
if (data_transfer_manager_ && data_requested_token_.value) {
data_transfer_manager_->remove_DataRequested(data_requested_token_);
data_requested_token_.value = 0;
}
}
} // namespace webshare
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_WEBSHARE_WIN_SHOW_SHARE_UI_FOR_WINDOW_OPERATION_H_
#define CHROME_BROWSER_WEBSHARE_WIN_SHOW_SHARE_UI_FOR_WINDOW_OPERATION_H_
#include <EventToken.h>
#include <wrl/client.h>
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/win/core_winrt_util.h"
namespace ABI {
namespace Windows {
namespace ApplicationModel {
namespace DataTransfer {
class IDataRequestedEventArgs;
class IDataTransferManager;
} // namespace DataTransfer
} // namespace ApplicationModel
} // namespace Windows
} // namespace ABI
namespace webshare {
// Represents a call to ShowShareUIForWindow in an async fashion.
class ShowShareUIForWindowOperation {
public:
using DataRequestedCallback = base::OnceCallback<void(
ABI::Windows::ApplicationModel::DataTransfer::IDataRequestedEventArgs*)>;
explicit ShowShareUIForWindowOperation(const HWND hwnd);
ShowShareUIForWindowOperation(const ShowShareUIForWindowOperation&) = delete;
ShowShareUIForWindowOperation& operator=(
const ShowShareUIForWindowOperation&) = delete;
~ShowShareUIForWindowOperation();
// Test hook for overriding the base RoGetActivationFactory function
static void SetRoGetActivationFactoryFunctionForTesting(
decltype(&base::win::RoGetActivationFactory) value);
static constexpr base::TimeDelta max_execution_time_for_testing() {
return kMaxExecutionTime;
}
// Requests the Window's Share operation for the previously supplied |hwnd|
// and uses the |data_requested_callback| to supply the operation with the
// data to share. This call does not impact the lifetime of this class, so the
// caller must keep this instance alive until it has completed or the caller
// no longer desires the operation to continue.
//
// The provided |data_requested_callback| will be invoked either
// synchronously as part of this call, or asynchronously at a
// later point when the OS Share operation requests it. In both cases, when
// the |data_requested_callback| is invoked it will be on the UI thread with
// |IDataRequestedEventArgs| from the OS. If an error is encountered the
// |data_requested_callback| will be invoked without any arguments.
//
// This should only be called from the UI thread.
void Run(DataRequestedCallback data_requested_callback);
private:
static constexpr base::TimeDelta kMaxExecutionTime =
base::TimeDelta::FromSeconds(30);
void Cancel();
void OnDataRequested(
ABI::Windows::ApplicationModel::DataTransfer::IDataTransferManager*
data_transfer_manager,
ABI::Windows::ApplicationModel::DataTransfer::IDataRequestedEventArgs*
event_args);
void RemoveDataRequestedListener();
DataRequestedCallback data_requested_callback_;
EventRegistrationToken data_requested_token_;
Microsoft::WRL::ComPtr<
ABI::Windows::ApplicationModel::DataTransfer::IDataTransferManager>
data_transfer_manager_;
const HWND hwnd_;
bool show_share_ui_for_window_call_in_progress_;
base::WeakPtrFactory<ShowShareUIForWindowOperation> weak_factory_{this};
};
} // namespace webshare
#endif // CHROME_BROWSER_WEBSHARE_WIN_SHOW_SHARE_UI_FOR_WINDOW_OPERATION_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/webshare/win/show_share_ui_for_window_operation.h"
#include <shlobj.h>
#include <windows.applicationmodel.datatransfer.h>
#include <wrl/implements.h>
#include <wrl/module.h>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/win/com_init_util.h"
#include "base/win/core_winrt_util.h"
#include "chrome/browser/webshare/win/fake_data_transfer_manager_interop.h"
#include "chrome/browser/webshare/win/scoped_fake_data_transfer_manager_interop.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
using ABI::Windows::ApplicationModel::DataTransfer::IDataRequest;
using ABI::Windows::ApplicationModel::DataTransfer::IDataRequestedEventArgs;
using ABI::Windows::ApplicationModel::DataTransfer::IDataTransferManager;
using Microsoft::WRL::ActivationFactory;
using Microsoft::WRL::ComPtr;
using Microsoft::WRL::RuntimeClass;
using Microsoft::WRL::RuntimeClassFlags;
namespace webshare {
using DataRequestedCallback =
ShowShareUIForWindowOperation::DataRequestedCallback;
using ShowShareUIForWindowBehavior =
FakeDataTransferManagerInterop::ShowShareUIForWindowBehavior;
class ShowShareUIForWindowOperationTest : public ::testing::Test {
protected:
enum TestCallbackState { NotRun = 0, RunWithoutValue, RunWithValue };
bool IsSupportedEnvironment() {
return base::win::ResolveCoreWinRTDelayload() &&
base::win::ScopedHString::ResolveCoreWinRTStringDelayload();
}
void SetUp() override {
if (!IsSupportedEnvironment())
return;
scoped_interop_ = std::make_unique<ScopedFakeDataTransferManagerInterop>();
auto weak_ptr = weak_factory_.GetWeakPtr();
test_callback_ = base::BindOnce(
[](base::WeakPtr<ShowShareUIForWindowOperationTest> weak_ptr,
IDataRequestedEventArgs* event_args) {
if (weak_ptr) {
EXPECT_EQ(weak_ptr->test_callback_state_,
TestCallbackState::NotRun);
weak_ptr->test_callback_state_ =
event_args ? TestCallbackState::RunWithValue
: TestCallbackState::RunWithoutValue;
}
},
weak_ptr);
}
void TearDown() override {
if (!IsSupportedEnvironment())
return;
base::win::RoUninitialize();
ASSERT_FALSE(fake_interop().HasDataRequestedListener(hwnd_));
}
FakeDataTransferManagerInterop& fake_interop() {
return scoped_interop_->instance();
}
const HWND hwnd_ = reinterpret_cast<HWND>(1);
std::unique_ptr<ScopedFakeDataTransferManagerInterop> scoped_interop_;
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
DataRequestedCallback test_callback_;
TestCallbackState test_callback_state_ = TestCallbackState::NotRun;
base::WeakPtrFactory<ShowShareUIForWindowOperationTest> weak_factory_{this};
};
TEST_F(ShowShareUIForWindowOperationTest, AsyncSuccess) {
if (!IsSupportedEnvironment())
return;
fake_interop().SetShowShareUIForWindowBehavior(
ShowShareUIForWindowBehavior::SucceedWithoutAction);
ShowShareUIForWindowOperation operation{hwnd_};
operation.Run(std::move(test_callback_));
ASSERT_EQ(test_callback_state_, TestCallbackState::NotRun);
auto data_requested_invoker = fake_interop().GetDataRequestedInvoker(hwnd_);
std::move(data_requested_invoker).Run();
ASSERT_EQ(test_callback_state_, TestCallbackState::RunWithValue);
}
TEST_F(ShowShareUIForWindowOperationTest, AsyncFailure) {
if (!IsSupportedEnvironment())
return;
fake_interop().SetShowShareUIForWindowBehavior(
ShowShareUIForWindowBehavior::SucceedWithoutAction);
ShowShareUIForWindowOperation operation{hwnd_};
operation.Run(std::move(test_callback_));
ASSERT_EQ(test_callback_state_, TestCallbackState::NotRun);
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
ASSERT_EQ(test_callback_state_, TestCallbackState::NotRun);
task_environment_.FastForwardBy(
ShowShareUIForWindowOperation::max_execution_time_for_testing());
ASSERT_EQ(test_callback_state_, TestCallbackState::RunWithoutValue);
}
TEST_F(ShowShareUIForWindowOperationTest, AsyncEarlyDestruction) {
if (!IsSupportedEnvironment())
return;
fake_interop().SetShowShareUIForWindowBehavior(
ShowShareUIForWindowBehavior::SucceedWithoutAction);
auto operation = std::make_unique<ShowShareUIForWindowOperation>(hwnd_);
operation->Run(std::move(test_callback_));
ASSERT_EQ(test_callback_state_, TestCallbackState::NotRun);
auto data_requested_invoker = fake_interop().GetDataRequestedInvoker(hwnd_);
ASSERT_NO_FATAL_FAILURE(operation.reset());
ASSERT_EQ(test_callback_state_, TestCallbackState::RunWithoutValue);
ASSERT_NO_FATAL_FAILURE(std::move(data_requested_invoker).Run());
}
TEST_F(ShowShareUIForWindowOperationTest, SyncSuccess) {
if (!IsSupportedEnvironment())
return;
fake_interop().SetShowShareUIForWindowBehavior(
ShowShareUIForWindowBehavior::InvokeEventSynchronously);
ShowShareUIForWindowOperation operation{hwnd_};
operation.Run(std::move(test_callback_));
ASSERT_EQ(test_callback_state_, TestCallbackState::RunWithValue);
}
TEST_F(ShowShareUIForWindowOperationTest, SyncEarlyFailure) {
if (!IsSupportedEnvironment())
return;
fake_interop().SetShowShareUIForWindowBehavior(
ShowShareUIForWindowBehavior::FailImmediately);
ShowShareUIForWindowOperation operation{hwnd_};
operation.Run(std::move(test_callback_));
ASSERT_EQ(test_callback_state_, TestCallbackState::NotRun);
}
TEST_F(ShowShareUIForWindowOperationTest, SyncLateFailure) {
if (!IsSupportedEnvironment())
return;
fake_interop().SetShowShareUIForWindowBehavior(
ShowShareUIForWindowBehavior::InvokeEventSynchronouslyAndReturnFailure);
ShowShareUIForWindowOperation operation{hwnd_};
operation.Run(std::move(test_callback_));
ASSERT_EQ(test_callback_state_, TestCallbackState::RunWithValue);
}
TEST_F(ShowShareUIForWindowOperationTest, DestructionWithoutRun) {
if (!IsSupportedEnvironment())
return;
auto operation = std::make_unique<ShowShareUIForWindowOperation>(hwnd_);
ASSERT_NO_FATAL_FAILURE(operation.reset());
}
} // namespace webshare
......@@ -319,6 +319,8 @@ static_library("test_support") {
"../browser/webshare/win/fake_data_transfer_manager.h",
"../browser/webshare/win/fake_data_transfer_manager_interop.cc",
"../browser/webshare/win/fake_data_transfer_manager_interop.h",
"../browser/webshare/win/scoped_fake_data_transfer_manager_interop.cc",
"../browser/webshare/win/scoped_fake_data_transfer_manager_interop.h",
"//chrome/app/chrome_crash_reporter_client_win.cc",
]
public_deps += [
......@@ -5660,6 +5662,7 @@ test("unit_tests") {
"../browser/ui/views/uninstall_view_unittest.cc",
"../browser/webshare/win/fake_data_transfer_manager_interop_unittest.cc",
"../browser/webshare/win/fake_data_transfer_manager_unittest.cc",
"../browser/webshare/win/show_share_ui_for_window_operation_unittest.cc",
]
deps += [
"//chrome:other_version",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment