Skip to content

Commit

Permalink
WebShare: Add Windows Share API Wrapper
Browse files Browse the repository at this point in the history
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: Robert Liao <robliao@chromium.org>
Reviewed-by: Eric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#811318}
  • Loading branch information
mhochk authored and Commit Bot committed Sep 28, 2020
1 parent 4603162 commit 6b477d9
Show file tree
Hide file tree
Showing 7 changed files with 555 additions and 0 deletions.
2 changes: 2 additions & 0 deletions chrome/browser/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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_
171 changes: 171 additions & 0 deletions chrome/browser/webshare/win/show_share_ui_for_window_operation.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// 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
88 changes: 88 additions & 0 deletions chrome/browser/webshare/win/show_share_ui_for_window_operation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// 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_
Loading

0 comments on commit 6b477d9

Please sign in to comment.