Commit e790f0c8 authored by Hoch Hochkeppel's avatar Hoch Hochkeppel Committed by Commit Bot

WebShare: Share operation for Windows

Adding a class for performing a navigator.share() operation on Windows,
and corresponding tests.

Originally committed as part of https://crrev.com/c/2443757, but
reverted due to test issues. Patchset 1 is a cherry pick of this
original change (reduced to the files relevant to this portion) for
easy comparison.

Bug: 1035527
Change-Id: I82a61739c168b0a2c9aa9f9f9d20572d8a45a2f7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2503819
Commit-Queue: Hoch Hochkeppel <mhochk@microsoft.com>
Reviewed-by: default avatarEric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#821806}
parent 8d7f9750
...@@ -4552,6 +4552,8 @@ static_library("browser") { ...@@ -4552,6 +4552,8 @@ static_library("browser") {
"upgrade_detector/get_installed_version_win.cc", "upgrade_detector/get_installed_version_win.cc",
"upgrade_detector/registry_monitor.cc", "upgrade_detector/registry_monitor.cc",
"upgrade_detector/registry_monitor.h", "upgrade_detector/registry_monitor.h",
"webshare/win/share_operation.cc",
"webshare/win/share_operation.h",
"webshare/win/show_share_ui_for_window_operation.cc", "webshare/win/show_share_ui_for_window_operation.cc",
"webshare/win/show_share_ui_for_window_operation.h", "webshare/win/show_share_ui_for_window_operation.h",
"win/app_icon.cc", "win/app_icon.cc",
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/macros.h"
#include "base/test/bind_test_util.h" #include "base/test/bind_test_util.h"
#include "base/test/fake_iasync_operation_win.h" #include "base/test/fake_iasync_operation_win.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
...@@ -61,9 +62,8 @@ class FakeStorageFile final ...@@ -61,9 +62,8 @@ class FakeStorageFile final
// API, so we use a temporary ScopedHString to make a copy we can safely own // API, so we use a temporary ScopedHString to make a copy we can safely own
// and release ownership of the original 'back' to the caller. // and release ownership of the original 'back' to the caller.
base::win::ScopedHString holder(display_name_with_extension); base::win::ScopedHString holder(display_name_with_extension);
display_name_with_extension_ = display_name_with_extension_ = holder.GetAsUTF8();
base::win::ScopedHString::Create(holder.Get()); ignore_result(holder.release());
(void)holder.release();
} }
FakeStorageFile(const FakeStorageFile&) = delete; FakeStorageFile(const FakeStorageFile&) = delete;
FakeStorageFile& operator=(const FakeStorageFile&) = delete; FakeStorageFile& operator=(const FakeStorageFile&) = delete;
...@@ -198,8 +198,7 @@ class FakeStorageFile final ...@@ -198,8 +198,7 @@ class FakeStorageFile final
return E_NOTIMPL; return E_NOTIMPL;
} }
IFACEMETHODIMP get_Name(HSTRING* value) final { IFACEMETHODIMP get_Name(HSTRING* value) final {
auto copy = auto copy = base::win::ScopedHString::Create(display_name_with_extension_);
base::win::ScopedHString::Create(display_name_with_extension_.Get());
*value = copy.release(); *value = copy.release();
return S_OK; return S_OK;
} }
...@@ -248,8 +247,7 @@ class FakeStorageFile final ...@@ -248,8 +247,7 @@ class FakeStorageFile final
streamed_file_data_requested_handler_->Invoke(output_stream.Get())); streamed_file_data_requested_handler_->Invoke(output_stream.Get()));
} }
base::win::ScopedHString display_name_with_extension_ = std::string display_name_with_extension_;
base::win::ScopedHString::Create("");
bool open_async_in_progress_ = false; bool open_async_in_progress_ = false;
ComPtr<IStreamedFileDataRequestedHandler> ComPtr<IStreamedFileDataRequestedHandler>
streamed_file_data_requested_handler_; streamed_file_data_requested_handler_;
......
// 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_share_operation_fake_components.h"
#include <windows.storage.streams.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 "base/win/windows_version.h"
#include "chrome/browser/webshare/win/fake_data_writer_factory.h"
#include "chrome/browser/webshare/win/fake_storage_file_statics.h"
#include "chrome/browser/webshare/win/fake_uri_runtime_class_factory.h"
#include "chrome/browser/webshare/win/scoped_fake_data_transfer_manager_interop.h"
#include "chrome/browser/webshare/win/share_operation.h"
#include "testing/gtest/include/gtest/gtest-spi.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace webshare {
namespace {
static FakeDataWriterFactory* g_current_fake_data_writer_factory = nullptr;
static FakeStorageFileStatics* g_current_fake_storage_file_statics = nullptr;
static FakeUriRuntimeClassFactory* g_current_fake_uri_runtime_class_factory =
nullptr;
static HRESULT FakeRoGetActivationFactory(HSTRING class_id,
const IID& iid,
void** out_factory) {
void* instance = nullptr;
base::win::ScopedHString class_id_hstring(class_id);
if (class_id_hstring.Get() == RuntimeClass_Windows_Storage_StorageFile) {
instance = g_current_fake_storage_file_statics;
} else if (class_id_hstring.Get() ==
RuntimeClass_Windows_Storage_Streams_DataWriter) {
instance = g_current_fake_data_writer_factory;
} else if (class_id_hstring.Get() == RuntimeClass_Windows_Foundation_Uri) {
instance = g_current_fake_uri_runtime_class_factory;
}
if (!instance) {
NOTREACHED();
return E_NOTIMPL;
}
*out_factory = instance;
reinterpret_cast<IUnknown*>(instance)->AddRef();
return S_OK;
}
} // namespace
// static
bool ScopedShareOperationFakeComponents::IsSupportedEnvironment() {
return ScopedFakeDataTransferManagerInterop::IsSupportedEnvironment();
}
ScopedShareOperationFakeComponents::ScopedShareOperationFakeComponents() =
default;
ScopedShareOperationFakeComponents::~ScopedShareOperationFakeComponents() {
g_current_fake_data_writer_factory = nullptr;
g_current_fake_storage_file_statics = nullptr;
g_current_fake_uri_runtime_class_factory = nullptr;
ShareOperation::SetRoGetActivationFactoryFunctionForTesting(
&base::win::RoGetActivationFactory);
}
void ScopedShareOperationFakeComponents::SetUp() {
ASSERT_TRUE(IsSupportedEnvironment());
base::win::AssertComInitialized();
ASSERT_NO_FATAL_FAILURE(scoped_fake_data_transfer_manager_interop_.SetUp());
fake_data_writer_factory_ = Microsoft::WRL::Make<FakeDataWriterFactory>();
fake_storage_file_statics_ = Microsoft::WRL::Make<FakeStorageFileStatics>();
fake_uri_runtime_class_factory_ =
Microsoft::WRL::Make<FakeUriRuntimeClassFactory>();
// Confirm there are no competing instances and set these instances
// for use by the main factory function
ASSERT_EQ(g_current_fake_data_writer_factory, nullptr);
g_current_fake_data_writer_factory = fake_data_writer_factory_.Get();
ASSERT_EQ(g_current_fake_storage_file_statics, nullptr);
g_current_fake_storage_file_statics = fake_storage_file_statics_.Get();
ASSERT_EQ(g_current_fake_uri_runtime_class_factory, nullptr);
g_current_fake_uri_runtime_class_factory =
fake_uri_runtime_class_factory_.Get();
ShareOperation::SetRoGetActivationFactoryFunctionForTesting(
&FakeRoGetActivationFactory);
}
FakeDataTransferManagerInterop&
ScopedShareOperationFakeComponents::fake_data_transfer_manager_interop() {
return scoped_fake_data_transfer_manager_interop_.instance();
}
} // 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_SHARE_OPERATION_FAKE_COMPONENTS_H_
#define CHROME_BROWSER_WEBSHARE_WIN_SCOPED_SHARE_OPERATION_FAKE_COMPONENTS_H_
#include <wrl/client.h>
#include "chrome/browser/webshare/win/scoped_fake_data_transfer_manager_interop.h"
namespace webshare {
class FakeDataTransferManagerInterop;
class FakeDataWriterFactory;
class FakeStorageFileStatics;
class FakeUriRuntimeClassFactory;
// Creates, registers, and maintains the fake equivalents of various Windows
// APIS used by the ShareOperation, allowing GTests to easily use a
// ShareOperation.
class ScopedShareOperationFakeComponents final {
public:
static bool IsSupportedEnvironment();
ScopedShareOperationFakeComponents();
ScopedShareOperationFakeComponents(
const ScopedShareOperationFakeComponents&) = delete;
ScopedShareOperationFakeComponents& operator=(
const ScopedShareOperationFakeComponents&) = delete;
~ScopedShareOperationFakeComponents();
// Initializes this component, creating test failures if anything does not
// succeed. Intended to be called from a test's SetUp function, after having
// verified this is a supported environment.
void SetUp();
FakeDataTransferManagerInterop& fake_data_transfer_manager_interop();
private:
Microsoft::WRL::ComPtr<FakeDataWriterFactory> fake_data_writer_factory_;
Microsoft::WRL::ComPtr<FakeStorageFileStatics> fake_storage_file_statics_;
Microsoft::WRL::ComPtr<FakeUriRuntimeClassFactory>
fake_uri_runtime_class_factory_;
ScopedFakeDataTransferManagerInterop
scoped_fake_data_transfer_manager_interop_;
};
} // namespace webshare
#endif // CHROME_BROWSER_WEBSHARE_WIN_SCOPED_SHARE_OPERATION_FAKE_COMPONENTS_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/share_operation.h"
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/win/core_winrt_util.h"
#include "base/win/post_async_results.h"
#include "base/win/scoped_hstring.h"
#include "base/win/vector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/webshare/share_service_impl.h"
#include "chrome/browser/webshare/win/show_share_ui_for_window_operation.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/web_contents.h"
#include "net/base/net_errors.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/file_system/file_stream_writer.h"
#include "storage/browser/file_system/file_writer_delegate.h"
#include "storage/common/file_system/file_system_mount_option.h"
#include "ui/views/win/hwnd_util.h"
#include "url/gurl.h"
#include <shlobj.h>
#include <windows.applicationmodel.datatransfer.h>
#include <windows.foundation.collections.h>
#include <windows.foundation.h>
#include <windows.storage.h>
#include <windows.storage.streams.h>
#include <wininet.h>
#include <wrl/client.h>
#include <wrl/event.h>
using ABI::Windows::ApplicationModel::DataTransfer::IDataPackage;
using ABI::Windows::ApplicationModel::DataTransfer::IDataPackagePropertySet;
using ABI::Windows::ApplicationModel::DataTransfer::IDataRequest;
using ABI::Windows::ApplicationModel::DataTransfer::IDataRequestDeferral;
using ABI::Windows::ApplicationModel::DataTransfer::IDataRequestedEventArgs;
using ABI::Windows::Foundation::AsyncStatus;
using ABI::Windows::Foundation::IAsyncOperation;
using ABI::Windows::Foundation::IAsyncOperationCompletedHandler;
using ABI::Windows::Foundation::IClosable;
using ABI::Windows::Foundation::IUriRuntimeClass;
using ABI::Windows::Foundation::IUriRuntimeClassFactory;
using ABI::Windows::Storage::IStorageFile;
using ABI::Windows::Storage::IStorageFileStatics;
using ABI::Windows::Storage::IStorageItem;
using ABI::Windows::Storage::IStreamedFileDataRequestedHandler;
using ABI::Windows::Storage::StorageFile;
using ABI::Windows::Storage::Streams::IDataWriter;
using ABI::Windows::Storage::Streams::IDataWriterFactory;
using ABI::Windows::Storage::Streams::IOutputStream;
using Microsoft::WRL::Callback;
using Microsoft::WRL::ComPtr;
using Microsoft::WRL::Make;
namespace ABI {
namespace Windows {
namespace Foundation {
namespace Collections {
// Define template specializations for the types used. These uuids were randomly
// generated.
template <>
struct __declspec(uuid("CBE31E85-DEC8-4227-987F-9C63D6AA1A2E"))
IObservableVector<IStorageItem*> : IObservableVector_impl<IStorageItem*> {};
template <>
struct __declspec(uuid("30BE4864-5EE5-4111-916E-15126649F3C9"))
VectorChangedEventHandler<IStorageItem*>
: VectorChangedEventHandler_impl<IStorageItem*> {};
} // namespace Collections
} // namespace Foundation
} // namespace Windows
} // namespace ABI
namespace webshare {
namespace {
uint64_t g_max_file_bytes = kMaxSharedFileBytes;
decltype(
&base::win::RoGetActivationFactory) g_ro_get_activation_factory_function =
&base::win::RoGetActivationFactory;
template <typename InterfaceType, wchar_t const* runtime_class_id>
HRESULT GetActivationFactory(InterfaceType** factory) {
auto class_id_hstring = base::win::ScopedHString::Create(runtime_class_id);
if (!class_id_hstring.is_valid())
return E_FAIL;
return g_ro_get_activation_factory_function(class_id_hstring.get(),
IID_PPV_ARGS(factory));
}
// Implements FileStreamWriter for an IDataWriter.
class DataWriterFileStreamWriter : public storage::FileStreamWriter {
public:
explicit DataWriterFileStreamWriter(
ComPtr<IDataWriter> data_writer,
scoped_refptr<base::RefCountedData<uint64_t>> file_bytes_shared)
: data_writer_(data_writer), file_bytes_shared_(file_bytes_shared) {}
int Cancel(net::CompletionOnceCallback callback) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// If there is no async operation in progress, Cancel() should
// return net::ERR_UNEXPECTED per file_stream_header.h
if (!flush_operation_ && !write_operation_)
return net::ERR_UNEXPECTED;
if (flush_operation_) {
flush_callback_.Reset();
ComPtr<IAsyncInfo> async_info;
auto hr = flush_operation_.As(&async_info);
if (FAILED(hr))
return net::ERR_UNEXPECTED;
hr = async_info->Cancel();
if (FAILED(hr))
return net::ERR_UNEXPECTED;
flush_operation_.Reset();
}
if (write_operation_) {
write_callback_.Reset();
ComPtr<IAsyncInfo> async_info;
auto hr = write_operation_.As(&async_info);
if (FAILED(hr))
return net::ERR_UNEXPECTED;
hr = async_info->Cancel();
if (FAILED(hr))
return net::ERR_UNEXPECTED;
write_operation_.Reset();
}
return net::OK;
}
int Flush(net::CompletionOnceCallback callback) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(flush_callback_.is_null());
DCHECK_EQ(flush_operation_, nullptr);
DCHECK(write_callback_.is_null());
DCHECK_EQ(write_operation_, nullptr);
auto hr = data_writer_->FlushAsync(&flush_operation_);
if (FAILED(hr))
return net::ERR_UNEXPECTED;
flush_callback_ = std::move(callback);
base::win::PostAsyncResults(
flush_operation_,
base::BindOnce(&DataWriterFileStreamWriter::OnFlushCompleted,
weak_factory_.GetWeakPtr()));
return net::ERR_IO_PENDING;
}
int Write(net::IOBuffer* buf,
int buf_len,
net::CompletionOnceCallback callback) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(flush_callback_.is_null());
DCHECK_EQ(flush_operation_, nullptr);
DCHECK(write_callback_.is_null());
DCHECK_EQ(write_operation_, nullptr);
// Before processing the Write request, increment the total number of file
// bytes shared as part of the overall Share operation this belongs to, and
// if it has exceeded the maximum allowed, abort writing to the streamed
// file.
file_bytes_shared_->data += buf_len;
if (file_bytes_shared_->data > g_max_file_bytes)
return net::ERR_UNEXPECTED;
auto hr =
data_writer_->WriteBytes(buf_len, reinterpret_cast<BYTE*>(buf->data()));
if (FAILED(hr))
return net::ERR_UNEXPECTED;
hr = data_writer_->StoreAsync(&write_operation_);
if (FAILED(hr))
return net::ERR_UNEXPECTED;
write_callback_ = std::move(callback);
base::win::PostAsyncResults(
write_operation_,
base::BindOnce(&DataWriterFileStreamWriter::OnWriteCompleted,
weak_factory_.GetWeakPtr()));
return net::ERR_IO_PENDING;
}
private:
void OnFlushCompleted(boolean operation_result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!flush_callback_.is_null());
DCHECK_NE(flush_operation_, nullptr);
DCHECK(write_callback_.is_null());
DCHECK_EQ(write_operation_, nullptr);
flush_operation_.Reset();
int result = operation_result == TRUE ? net::OK : net::ERR_UNEXPECTED;
std::move(flush_callback_).Run(result);
}
void OnWriteCompleted(UINT32 operation_result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(flush_callback_.is_null());
DCHECK_EQ(flush_operation_, nullptr);
DCHECK(!write_callback_.is_null());
DCHECK_NE(write_operation_, nullptr);
write_operation_.Reset();
std::move(write_callback_).Run(operation_result);
}
ComPtr<IDataWriter> data_writer_;
scoped_refptr<base::RefCountedData<uint64_t>> file_bytes_shared_;
net::CompletionOnceCallback flush_callback_;
ComPtr<IAsyncOperation<bool>> flush_operation_;
net::CompletionOnceCallback write_callback_;
ComPtr<IAsyncOperation<UINT32>> write_operation_;
base::WeakPtrFactory<DataWriterFileStreamWriter> weak_factory_{this};
};
// Represents an ongoing operation of writing to an IOutputStream.
class OutputStreamWriteOperation
: public base::RefCounted<OutputStreamWriteOperation> {
public:
OutputStreamWriteOperation(
content::BrowserContext::BlobContextGetter blob_context_getter,
scoped_refptr<base::RefCountedData<uint64_t>> file_bytes_shared,
std::string uuid)
: blob_context_getter_(blob_context_getter),
file_bytes_shared_(file_bytes_shared),
uuid_(uuid) {}
// Begins the write operation on the |stream|, maintaining a reference to the
// |stream| until the operation is completed, at which point it will be closed
// (if possible) and the |on_complete| callback will be invoked. The caller
// is still responsible for the lifetime of this object, but not of the
// |stream|.
void WriteStream(IOutputStream* stream,
base::OnceCallback<void()> on_complete) {
stream_ = ComPtr<IOutputStream>(stream);
on_complete_ = std::move(on_complete);
if (!base::PostTask(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&OutputStreamWriteOperation::WriteStreamOnIOThread,
weak_factory_.GetWeakPtr())))
Complete();
}
private:
friend class base::RefCounted<OutputStreamWriteOperation>;
~OutputStreamWriteOperation() = default;
void WriteStreamOnIOThread() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
storage::BlobStorageContext* blob_storage_context =
blob_context_getter_.Run().get();
if (!blob_storage_context) {
Complete();
return;
}
blob_handle_ = blob_storage_context->GetBlobDataFromUUID(uuid_);
ComPtr<IDataWriterFactory> data_writer_factory;
auto hr =
GetActivationFactory<IDataWriterFactory,
RuntimeClass_Windows_Storage_Streams_DataWriter>(
&data_writer_factory);
if (FAILED(hr)) {
Complete();
return;
}
ComPtr<IDataWriter> data_writer;
hr = data_writer_factory->CreateDataWriter(stream_.Get(), &data_writer);
if (FAILED(hr)) {
Complete();
return;
}
writer_delegate_ = std::make_unique<storage::FileWriterDelegate>(
std::make_unique<DataWriterFileStreamWriter>(std::move(data_writer),
file_bytes_shared_),
storage::FlushPolicy::FLUSH_ON_COMPLETION);
writer_delegate_->Start(
blob_handle_->CreateReader(),
base::BindRepeating(&OutputStreamWriteOperation::OnFileWritten,
weak_factory_.GetWeakPtr()));
}
void OnFileWritten(
base::File::Error error,
int64_t bytes_wrriten,
storage::FileWriterDelegate::WriteProgressStatus write_status) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// Any status other than SUCCESS_IO_PENDING indicates completion.
if (write_status !=
storage::FileWriterDelegate::WriteProgressStatus::SUCCESS_IO_PENDING) {
Complete();
}
}
void Complete() {
// If the IOutputStream implements IClosable (e.g. the OutputStream class),
// close the stream whenever we are done with this operation, regardless of
// the outcome.
if (stream_) {
ComPtr<IClosable> closable;
if (SUCCEEDED(stream_.As(&closable)))
closable->Close();
}
std::move(on_complete_).Run();
}
content::BrowserContext::BlobContextGetter blob_context_getter_;
std::unique_ptr<storage::BlobDataHandle> blob_handle_;
scoped_refptr<base::RefCountedData<uint64_t>> file_bytes_shared_;
base::OnceCallback<void()> on_complete_;
ComPtr<IOutputStream> stream_;
const std::string uuid_;
std::unique_ptr<storage::FileWriterDelegate> writer_delegate_;
base::WeakPtrFactory<OutputStreamWriteOperation> weak_factory_{this};
};
} // namespace
// static
void ShareOperation::SetMaxFileBytesForTesting(uint64_t max_file_bytes) {
g_max_file_bytes = max_file_bytes;
}
// static
void ShareOperation::SetRoGetActivationFactoryFunctionForTesting(
decltype(&base::win::RoGetActivationFactory) value) {
g_ro_get_activation_factory_function = value;
}
ShareOperation::ShareOperation(const std::string& title,
const std::string& text,
const GURL& url,
std::vector<blink::mojom::SharedFilePtr> files,
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
title_(std::move(title)),
text_(std::move(text)),
url_(std::move(url)),
files_(std::move(files)) {}
ShareOperation::~ShareOperation() {
if (callback_)
Complete(blink::mojom::ShareError::CANCELED);
}
base::WeakPtr<ShareOperation> ShareOperation::AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void ShareOperation::Run(blink::mojom::ShareService::ShareCallback callback) {
DCHECK(!callback_);
callback_ = std::move(callback);
// If the required WinRT functionality is not available, or the corresponding
// web_contents have already been cleaned up, cancel the operation
const bool winrt_environment_ok =
base::win::ResolveCoreWinRTDelayload() &&
base::win::ScopedHString::ResolveCoreWinRTStringDelayload();
if (!winrt_environment_ok || !web_contents()) {
Complete(blink::mojom::ShareError::CANCELED);
return;
}
if (files_.size() > 0) {
// Determine the source for use with the OS IAttachmentExecute.
// If the source cannot be determined, does not appear to be valid,
// or is longer than the max length supported by the IAttachmentExecute
// service, use a generic value that reliably maps to the Internet zone.
GURL source_url = web_contents()->GetLastCommittedURL();
base::string16 source =
(source_url.is_valid() &&
source_url.spec().size() <= INTERNET_MAX_URL_LENGTH)
? base::UTF8ToUTF16(source_url.spec())
: L"about:internet";
// For each "file", check against the OS that it is allowed
// The same instance cannot be used to check multiple files, so this
// makes a new one per-file. For more details on this functionality, see
// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-iattachmentexecute-checkpolicy
for (auto& file : files_) {
ComPtr<IAttachmentExecute> attachment_services;
if (FAILED(CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_ALL,
IID_PPV_ARGS(&attachment_services)))) {
Complete(blink::mojom::ShareError::INTERNAL_ERROR);
return;
}
if (FAILED(attachment_services->SetSource(source.c_str()))) {
Complete(blink::mojom::ShareError::INTERNAL_ERROR);
return;
}
if (FAILED(attachment_services->SetFileName(
base::UTF8ToWide(file->name).c_str()))) {
Complete(blink::mojom::ShareError::INTERNAL_ERROR);
return;
}
if (FAILED(attachment_services->CheckPolicy())) {
Complete(blink::mojom::ShareError::PERMISSION_DENIED);
return;
}
}
}
HWND hwnd =
views::HWNDForNativeWindow(web_contents()->GetTopLevelNativeWindow());
show_share_ui_for_window_operation_ =
std::make_unique<ShowShareUIForWindowOperation>(hwnd);
show_share_ui_for_window_operation_->Run(base::BindOnce(
&ShareOperation::OnDataRequested, weak_factory_.GetWeakPtr()));
}
void ShareOperation::OnDataRequested(IDataRequestedEventArgs* event_args) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
blink::mojom::ShareError share_result;
if (!event_args || !web_contents()) {
share_result = blink::mojom::ShareError::CANCELED;
} else {
if (PutShareContentInEventArgs(event_args)) {
share_result = blink::mojom::ShareError::OK;
} else {
share_result = blink::mojom::ShareError::INTERNAL_ERROR;
}
}
// If the share operation failed or is not being deferred, mark it as complete
if (share_result != blink::mojom::ShareError::OK || !data_request_deferral_)
Complete(share_result);
}
bool ShareOperation::PutShareContentInEventArgs(
IDataRequestedEventArgs* event_args) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
ComPtr<IDataRequest> data_request;
if (FAILED(event_args->get_Request(&data_request)))
return false;
if (FAILED(data_request->get_Data(&data_package_)))
return false;
ComPtr<IDataPackagePropertySet> data_prop_sets;
if (FAILED(data_package_->get_Properties(&data_prop_sets)))
return false;
// Title is a required property for the UWP Share contract, so
// if the provided title is empty we instead use a blank value.
// https://docs.microsoft.com/en-us/windows/uwp/app-to-app/share-data
base::win::ScopedHString title_h =
base::win::ScopedHString::Create(title_.empty() ? " " : title_.c_str());
if (FAILED(data_prop_sets->put_Title(title_h.get())))
return false;
return PutShareContentInDataPackage(data_request.Get());
}
bool ShareOperation::PutShareContentInDataPackage(IDataRequest* data_request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!text_.empty()) {
auto text_h = base::win::ScopedHString::Create(text_);
if (FAILED(data_package_->SetText(text_h.get())))
return false;
}
if (!url_.spec().empty()) {
ComPtr<IUriRuntimeClassFactory> uri_factory;
auto hr =
GetActivationFactory<IUriRuntimeClassFactory,
RuntimeClass_Windows_Foundation_Uri>(&uri_factory);
if (FAILED(hr))
return hr;
auto url_h = base::win::ScopedHString::Create(url_.spec().c_str());
ComPtr<IUriRuntimeClass> uri;
if (FAILED(uri_factory->CreateUri(url_h.get(), &uri)))
return false;
if (FAILED(data_package_->SetUri(uri.Get())))
return false;
}
if (!files_.empty()) {
// Fetch a deferral to allow for async operations
if (FAILED(data_request->GetDeferral(&data_request_deferral_)))
return false;
// Initialize the output collection for the async operation(s)
storage_items_ = Make<base::win::Vector<IStorageItem*>>();
// Create a variable to be shared between all the operations processing the
// blobs to streams. This will be used to keep a running count of total file
// bytes shared as part of this Share operation so that if the maximum
// allowed is exceeded the processing can be halted. Currently the
// ShareOperation class is not guaranteed to outlive these operations, but
// if that changes in the future it may be appropriate to make this a member
// of the ShareOperation that is shared only be reference.
auto file_bytes_shared =
base::MakeRefCounted<base::RefCountedData<uint64_t>>(0);
ComPtr<IStorageFileStatics> storage_statics;
auto hr = GetActivationFactory<IStorageFileStatics,
RuntimeClass_Windows_Storage_StorageFile>(
&storage_statics);
if (FAILED(hr))
return false;
for (auto& file : files_) {
// This operation for converting the corresponding blob to a stream is
// maintained as a scoped_refptr because it may out live this
// ShareOperation instance. It is only invoked when the user has chosen a
// Share target and that target decides to start reading the contents of
// the corresponding IStorageFile. See
// https://docs.microsoft.com/en-us/uwp/api/windows.storage.storagefile.createstreamedfileasync
// If in the future the ShareOperation class is changed to live until the
// target app has finished fully processing the shared content this could
// be updated to be owned/maintained by this ShareOperation instance.
auto operation = base::MakeRefCounted<OutputStreamWriteOperation>(
content::BrowserContext::GetBlobStorageContext(
web_contents()->GetBrowserContext()),
file_bytes_shared, file->blob->uuid);
auto name_h = base::win::ScopedHString::Create(file->name);
auto raw_data_requested_callback =
Callback<IStreamedFileDataRequestedHandler>(
[operation](IOutputStream* stream) -> HRESULT {
// No additional work is needed when the write has been
// completed, but a callback is created to hold a reference
// to the |operation| until the operation has completed.
operation->WriteStream(
stream,
base::BindOnce(
base::DoNothing::Once<
scoped_refptr<OutputStreamWriteOperation>>(),
operation));
return S_OK;
});
// The Callback function may return null in the E_OUTOFMEMORY case
if (!raw_data_requested_callback)
return false;
ComPtr<IAsyncOperation<StorageFile*>> async_operation;
if (FAILED(storage_statics->CreateStreamedFileAsync(
name_h.get(), raw_data_requested_callback.Get(),
/*thumbnail*/ nullptr, &async_operation))) {
return false;
}
if (FAILED(base::win::PostAsyncResults(
async_operation,
base::BindOnce(&ShareOperation::OnStreamedFileCreated,
weak_factory_.GetWeakPtr()))))
return false;
}
}
return true;
}
void ShareOperation::OnStreamedFileCreated(ComPtr<IStorageFile> storage_file) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// If there is no callback this ShareOperation already completed due to an
// error, so work can be halted early.
if (!callback_)
return;
if (!storage_file) {
Complete(blink::mojom::ShareError::INTERNAL_ERROR);
return;
}
ComPtr<IStorageItem> storage_item;
if (FAILED(storage_file.As(&storage_item))) {
Complete(blink::mojom::ShareError::INTERNAL_ERROR);
return;
}
if (FAILED(storage_items_->Append(storage_item.Get()))) {
Complete(blink::mojom::ShareError::INTERNAL_ERROR);
return;
}
unsigned int size;
if (FAILED(storage_items_->get_Size(&size))) {
Complete(blink::mojom::ShareError::INTERNAL_ERROR);
return;
}
// If this is not the final file, no more work to do
if (size != files_.size())
return;
if (FAILED(data_package_->SetStorageItems(storage_items_.Get(),
true /*readonly*/))) {
Complete(blink::mojom::ShareError::INTERNAL_ERROR);
return;
}
data_request_deferral_->Complete();
Complete(blink::mojom::ShareError::OK);
return;
}
void ShareOperation::Complete(const blink::mojom::ShareError share_result) {
std::move(callback_).Run(share_result);
}
} // 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_SHARE_OPERATION_H_
#define CHROME_BROWSER_WEBSHARE_WIN_SHARE_OPERATION_H_
#include <memory>
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/win/core_winrt_util.h"
#include "content/public/browser/web_contents_observer.h"
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#include "url/gurl.h"
#include <wrl/client.h>
namespace ABI {
namespace Windows {
namespace ApplicationModel {
namespace DataTransfer {
struct IDataPackage;
class IDataRequest;
class IDataRequestDeferral;
class IDataRequestedEventArgs;
} // namespace DataTransfer
} // namespace ApplicationModel
namespace Storage {
class IStorageFile;
class IStorageItem;
} // namespace Storage
} // namespace Windows
} // namespace ABI
namespace base {
namespace win {
template <typename T>
class Vector;
} // namespace win
} // namespace base
namespace webshare {
class ShowShareUIForWindowOperation;
class ShareOperation : content::WebContentsObserver {
public:
static void SetMaxFileBytesForTesting(uint64_t max_file_bytes);
// Test hook for overriding the base RoGetActivationFactory function
static void SetRoGetActivationFactoryFunctionForTesting(
decltype(&base::win::RoGetActivationFactory) value);
ShareOperation(const std::string& title,
const std::string& text,
const GURL& url,
std::vector<blink::mojom::SharedFilePtr> files,
content::WebContents* web_contents);
ShareOperation(const ShareOperation&) = delete;
ShareOperation& operator=(const ShareOperation&) = delete;
~ShareOperation() override;
base::WeakPtr<ShareOperation> AsWeakPtr();
// Starts this Windows Share operation for the previously provided content.
// The |callback| will be invoked upon completion of the operation with a
// value indicating the success of the operation, or if the returned instance
// is destroyed before the operation is completed the |callback| will be
// invoked with a CANCELLED value and the underlying Windows operation will be
// aborted.
void Run(blink::mojom::ShareService::ShareCallback callback);
private:
void OnDataRequested(
ABI::Windows::ApplicationModel::DataTransfer::IDataRequestedEventArgs* e);
bool PutShareContentInDataPackage(
ABI::Windows::ApplicationModel::DataTransfer::IDataRequest* data_request);
bool PutShareContentInEventArgs(
ABI::Windows::ApplicationModel::DataTransfer::IDataRequestedEventArgs* e);
void OnStreamedFileCreated(
Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFile> storage_file);
void Complete(const blink::mojom::ShareError share_error);
const std::string title_;
const std::string text_;
const GURL url_;
const std::vector<blink::mojom::SharedFilePtr> files_;
blink::mojom::ShareService::ShareCallback callback_;
Microsoft::WRL::ComPtr<
ABI::Windows::ApplicationModel::DataTransfer::IDataPackage>
data_package_;
Microsoft::WRL::ComPtr<
ABI::Windows::ApplicationModel::DataTransfer::IDataRequestDeferral>
data_request_deferral_;
std::unique_ptr<ShowShareUIForWindowOperation>
show_share_ui_for_window_operation_;
// Though this Vector is declared as using a raw IStorageItem*, because
// IStorageItem implements IUnknown it will be stored internally with
// ComPtrs, so does not need to be specially handled.
Microsoft::WRL::ComPtr<
base::win::Vector<ABI::Windows::Storage::IStorageItem*>>
storage_items_;
base::WeakPtrFactory<ShareOperation> weak_factory_{this};
};
} // namespace webshare
#endif // CHROME_BROWSER_WEBSHARE_WIN_SHARE_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/share_operation.h"
#include "base/guid.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/win/core_winrt_util.h"
#include "base/win/post_async_results.h"
#include "chrome/browser/webshare/share_service_impl.h"
#include "chrome/browser/webshare/win/fake_buffer.h"
#include "chrome/browser/webshare/win/fake_data_transfer_manager.h"
#include "chrome/browser/webshare/win/fake_data_transfer_manager_interop.h"
#include "chrome/browser/webshare/win/scoped_share_operation_fake_components.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "content/public/browser/browser_task_traits.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_impl.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "ui/views/win/hwnd_util.h"
#include "url/gurl.h"
#include <wrl/event.h>
using ABI::Windows::ApplicationModel::DataTransfer::IDataPackage;
using ABI::Windows::ApplicationModel::DataTransfer::IDataPackagePropertySet;
using ABI::Windows::ApplicationModel::DataTransfer::IDataRequest;
using ABI::Windows::ApplicationModel::DataTransfer::IDataRequestedEventArgs;
using ABI::Windows::Foundation::IAsyncOperation;
using ABI::Windows::Foundation::IAsyncOperationCompletedHandler;
using ABI::Windows::Foundation::IAsyncOperationWithProgress;
using ABI::Windows::Foundation::IAsyncOperationWithProgressCompletedHandler;
using ABI::Windows::Storage::FileAccessMode;
using ABI::Windows::Storage::IStorageFile;
using ABI::Windows::Storage::Streams::IBuffer;
using ABI::Windows::Storage::Streams::IDataReader;
using ABI::Windows::Storage::Streams::IDataReaderFactory;
using ABI::Windows::Storage::Streams::IInputStream;
using ABI::Windows::Storage::Streams::InputStreamOptions;
using ABI::Windows::Storage::Streams::IRandomAccessStream;
using Microsoft::WRL::Callback;
using Microsoft::WRL::ComPtr;
using Microsoft::WRL::Make;
namespace webshare {
namespace {
constexpr uint64_t kMaxSharedFileBytesForTest = 1024 * 100;
} // namespace
class ShareOperationUnitTest : public ChromeRenderViewHostTestHarness {
public:
ShareOperationUnitTest() {
feature_list_.InitAndEnableFeature(features::kWebShare);
}
~ShareOperationUnitTest() override = default;
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
if (!IsSupportedEnvironment())
return;
ASSERT_NO_FATAL_FAILURE(scoped_fake_components_.SetUp());
ShareOperation::SetMaxFileBytesForTesting(kMaxSharedFileBytesForTest);
}
void TearDown() override {
ChromeRenderViewHostTestHarness::TearDown();
ShareOperation::SetMaxFileBytesForTesting(kMaxSharedFileBytes);
}
protected:
void ReadFile(IStorageFile* file, std::string& result) {
ComPtr<IRandomAccessStream> stream;
{
base::RunLoop run_loop;
ComPtr<IAsyncOperation<IRandomAccessStream*>> open_operation;
ASSERT_HRESULT_SUCCEEDED(file->OpenAsync(
FileAccessMode::FileAccessMode_Read, &open_operation));
ASSERT_HRESULT_SUCCEEDED(open_operation->put_Completed(
Callback<IAsyncOperationCompletedHandler<IRandomAccessStream*>>(
[&run_loop, &stream](
IAsyncOperation<IRandomAccessStream*>* async_operation,
AsyncStatus async_status) {
EXPECT_EQ(async_status, AsyncStatus::Completed);
EXPECT_HRESULT_SUCCEEDED(async_operation->GetResults(&stream));
run_loop.Quit();
return S_OK;
})
.Get()));
run_loop.Run();
}
UINT64 size;
ASSERT_HRESULT_SUCCEEDED(stream->get_Size(&size));
ComPtr<IInputStream> input_stream;
ASSERT_HRESULT_SUCCEEDED(stream->GetInputStreamAt(0, &input_stream));
auto buffer = Make<FakeBuffer>(size);
{
base::RunLoop run_loop;
ComPtr<IAsyncOperationWithProgress<IBuffer*, UINT32>> read_operation;
ASSERT_HRESULT_SUCCEEDED(input_stream->ReadAsync(
buffer.Get(), size, InputStreamOptions::InputStreamOptions_None,
&read_operation));
ASSERT_HRESULT_SUCCEEDED(read_operation->put_Completed(
Callback<
IAsyncOperationWithProgressCompletedHandler<IBuffer*, UINT32>>(
[&run_loop](IAsyncOperationWithProgress<IBuffer*, UINT32>*
async_operation,
AsyncStatus async_status) {
EXPECT_EQ(async_status, AsyncStatus::Completed);
run_loop.Quit();
return S_OK;
})
.Get()));
run_loop.Run();
}
UINT32 bytes_loaded;
ASSERT_HRESULT_SUCCEEDED(buffer->get_Length(&bytes_loaded));
ASSERT_EQ(size, bytes_loaded);
BYTE* raw_buffer;
ASSERT_HRESULT_SUCCEEDED(buffer->Buffer(&raw_buffer));
std::vector<unsigned char> bytes(bytes_loaded);
for (UINT32 i = 0; i < bytes_loaded; i++) {
bytes[i] = raw_buffer[i];
}
result = std::string(bytes.begin(), bytes.end());
}
blink::mojom::SharedFilePtr CreateSharedFile(const std::string& name,
const std::string& content_type,
const std::string& contents) {
auto blob = blink::mojom::SerializedBlob::New();
const std::string uuid = base::GenerateGUID();
blob->uuid = uuid;
blob->content_type = content_type;
blob->size = contents.size();
base::RunLoop run_loop;
auto blob_context_getter =
content::BrowserContext::GetBlobStorageContext(browser_context());
content::GetIOThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindLambdaForTesting([&blob_context_getter, &blob, &uuid,
&content_type, &contents]() {
auto builder = std::make_unique<storage::BlobDataBuilder>(uuid);
builder->set_content_type(content_type);
builder->AppendData(contents);
storage::BlobImpl::Create(
blob_context_getter.Run()->AddFinishedBlob(std::move(builder)),
blob->blob.InitWithNewPipeAndPassReceiver());
}),
base::BindLambdaForTesting([&run_loop]() { run_loop.Quit(); }));
run_loop.Run();
return blink::mojom::SharedFile::New(name, std::move(blob));
}
bool IsSupportedEnvironment() {
return ScopedShareOperationFakeComponents::IsSupportedEnvironment();
}
// Fetches the FakeDataTransferManager associated with the current context.
// Returns a non-ref-counted pointer, as the lifetime is already maintained by
// the scoped_fake_components_.
FakeDataTransferManager* fake_data_transfer_manager() {
if (!fake_data_transfer_manager_) {
HWND hwnd =
views::HWNDForNativeWindow(web_contents()->GetTopLevelNativeWindow());
ComPtr<FakeDataTransferManager> fake_data_transfer_manager;
EXPECT_HRESULT_SUCCEEDED(
scoped_fake_components_.fake_data_transfer_manager_interop()
.GetForWindow(hwnd, IID_PPV_ARGS(&fake_data_transfer_manager)));
fake_data_transfer_manager_ = fake_data_transfer_manager.Get();
}
return fake_data_transfer_manager_;
}
private:
FakeDataTransferManager* fake_data_transfer_manager_ = nullptr;
base::test::ScopedFeatureList feature_list_;
ScopedShareOperationFakeComponents scoped_fake_components_;
};
TEST_F(ShareOperationUnitTest, WithoutTitle) {
if (!IsSupportedEnvironment())
return;
bool post_data_requested_callback_invoked = false;
fake_data_transfer_manager()->SetPostDataRequestedCallback(
base::BindLambdaForTesting(
[&post_data_requested_callback_invoked](
const FakeDataTransferManager::DataRequestedContent&
data_requested_content) {
ASSERT_FALSE(post_data_requested_callback_invoked);
post_data_requested_callback_invoked = true;
ASSERT_EQ(data_requested_content.title, " ");
ASSERT_EQ(data_requested_content.text, "shared Text");
}));
base::RunLoop run_loop;
std::vector<blink::mojom::SharedFilePtr> files;
ShareOperation operation{"", "shared Text", GURL::EmptyGURL(),
std::move(files), web_contents()};
operation.Run(
base::BindLambdaForTesting([&run_loop](blink::mojom::ShareError error) {
ASSERT_EQ(error, blink::mojom::ShareError::OK);
run_loop.Quit();
}));
run_loop.Run();
ASSERT_TRUE(post_data_requested_callback_invoked);
}
TEST_F(ShareOperationUnitTest, BasicFields) {
if (!IsSupportedEnvironment())
return;
bool post_data_requested_callback_invoked = false;
fake_data_transfer_manager()->SetPostDataRequestedCallback(
base::BindLambdaForTesting(
[&post_data_requested_callback_invoked](
const FakeDataTransferManager::DataRequestedContent&
data_requested_content) {
ASSERT_FALSE(post_data_requested_callback_invoked);
post_data_requested_callback_invoked = true;
ASSERT_EQ(data_requested_content.title, "shared title");
ASSERT_EQ(data_requested_content.text, "shared text");
ASSERT_EQ(GURL(data_requested_content.uri),
GURL("https://www.contoso.com"));
}));
base::RunLoop run_loop;
std::vector<blink::mojom::SharedFilePtr> files;
ShareOperation operation{"shared title", "shared text",
GURL("https://www.contoso.com"), std::move(files),
web_contents()};
operation.Run(
base::BindLambdaForTesting([&run_loop](blink::mojom::ShareError error) {
ASSERT_EQ(error, blink::mojom::ShareError::OK);
run_loop.Quit();
}));
run_loop.Run();
ASSERT_TRUE(post_data_requested_callback_invoked);
}
TEST_F(ShareOperationUnitTest, BasicFile) {
if (!IsSupportedEnvironment())
return;
bool post_data_requested_callback_invoked = false;
ComPtr<IStorageFile> shared_file;
fake_data_transfer_manager()->SetPostDataRequestedCallback(
base::BindLambdaForTesting(
[&](const FakeDataTransferManager::DataRequestedContent&
data_requested_content) {
ASSERT_FALSE(post_data_requested_callback_invoked);
post_data_requested_callback_invoked = true;
ASSERT_EQ(data_requested_content.title, "shared title");
ASSERT_EQ(data_requested_content.files.size(), 1ull);
ASSERT_EQ(data_requested_content.files[0].name, "MyFile.txt");
shared_file = data_requested_content.files[0].file;
}));
base::RunLoop run_loop;
std::vector<blink::mojom::SharedFilePtr> files;
files.push_back(
CreateSharedFile("MyFile.txt", "text/plain", "Contents of the file"));
ShareOperation operation{"shared title", "", GURL::EmptyGURL(),
std::move(files), web_contents()};
operation.Run(
base::BindLambdaForTesting([&run_loop](blink::mojom::ShareError error) {
ASSERT_EQ(error, blink::mojom::ShareError::OK);
run_loop.Quit();
}));
run_loop.Run();
ASSERT_TRUE(post_data_requested_callback_invoked);
ASSERT_TRUE(shared_file);
std::string file_contents;
ASSERT_NO_FATAL_FAILURE(ReadFile(shared_file.Get(), file_contents));
ASSERT_EQ(file_contents, "Contents of the file");
}
TEST_F(ShareOperationUnitTest, SingleFileAtSizeLimit) {
if (!IsSupportedEnvironment())
return;
bool post_data_requested_callback_invoked = false;
ComPtr<IStorageFile> shared_file;
fake_data_transfer_manager()->SetPostDataRequestedCallback(
base::BindLambdaForTesting(
[&](const FakeDataTransferManager::DataRequestedContent&
data_requested_content) {
ASSERT_FALSE(post_data_requested_callback_invoked);
post_data_requested_callback_invoked = true;
ASSERT_EQ(data_requested_content.files.size(), 1ull);
shared_file = data_requested_content.files[0].file;
}));
base::RunLoop run_loop;
std::vector<blink::mojom::SharedFilePtr> files;
files.push_back(
CreateSharedFile("MyFile.txt", "text/plain",
std::string(kMaxSharedFileBytesForTest, '*')));
ShareOperation operation{"", "", GURL::EmptyGURL(), std::move(files),
web_contents()};
operation.Run(
base::BindLambdaForTesting([&run_loop](blink::mojom::ShareError error) {
ASSERT_EQ(error, blink::mojom::ShareError::OK);
run_loop.Quit();
}));
run_loop.Run();
ASSERT_TRUE(post_data_requested_callback_invoked);
ASSERT_TRUE(shared_file);
std::string file_contents;
ASSERT_NO_FATAL_FAILURE(ReadFile(shared_file.Get(), file_contents));
ASSERT_EQ(file_contents.length(), kMaxSharedFileBytesForTest);
}
TEST_F(ShareOperationUnitTest, SingleFileLargerThanSizeLimit) {
if (!IsSupportedEnvironment())
return;
bool post_data_requested_callback_invoked = false;
ComPtr<IStorageFile> shared_file;
fake_data_transfer_manager()->SetPostDataRequestedCallback(
base::BindLambdaForTesting(
[&](const FakeDataTransferManager::DataRequestedContent&
data_requested_content) {
ASSERT_FALSE(post_data_requested_callback_invoked);
post_data_requested_callback_invoked = true;
ASSERT_EQ(data_requested_content.files.size(), 1ull);
shared_file = data_requested_content.files[0].file;
}));
base::RunLoop run_loop;
std::vector<blink::mojom::SharedFilePtr> files;
files.push_back(
CreateSharedFile("MyFile.txt", "text/plain",
std::string(kMaxSharedFileBytesForTest + 1, '*')));
ShareOperation operation{"", "", GURL::EmptyGURL(), std::move(files),
web_contents()};
operation.Run(
base::BindLambdaForTesting([&run_loop](blink::mojom::ShareError error) {
ASSERT_EQ(error, blink::mojom::ShareError::OK);
run_loop.Quit();
}));
run_loop.Run();
ASSERT_TRUE(post_data_requested_callback_invoked);
ASSERT_TRUE(shared_file);
std::string file_contents;
ASSERT_NO_FATAL_FAILURE(ReadFile(shared_file.Get(), file_contents));
ASSERT_LT(file_contents.length(), kMaxSharedFileBytesForTest + 1);
}
TEST_F(ShareOperationUnitTest, FilesTotallingSizeLimit) {
if (!IsSupportedEnvironment())
return;
bool post_data_requested_callback_invoked = false;
ComPtr<IStorageFile> shared_file_1;
ComPtr<IStorageFile> shared_file_2;
fake_data_transfer_manager()->SetPostDataRequestedCallback(
base::BindLambdaForTesting(
[&](const FakeDataTransferManager::DataRequestedContent&
data_requested_content) {
ASSERT_FALSE(post_data_requested_callback_invoked);
post_data_requested_callback_invoked = true;
ASSERT_EQ(data_requested_content.files.size(), 2ull);
shared_file_1 = data_requested_content.files[0].file;
shared_file_2 = data_requested_content.files[1].file;
}));
base::RunLoop run_loop;
std::vector<blink::mojom::SharedFilePtr> files;
files.push_back(
CreateSharedFile("File1.txt", "text/plain",
std::string(kMaxSharedFileBytesForTest / 2, '*')));
files.push_back(
CreateSharedFile("File2.txt", "text/plain",
std::string(kMaxSharedFileBytesForTest / 2, '*')));
ShareOperation operation{"", "", GURL::EmptyGURL(), std::move(files),
web_contents()};
operation.Run(
base::BindLambdaForTesting([&run_loop](blink::mojom::ShareError error) {
ASSERT_EQ(error, blink::mojom::ShareError::OK);
run_loop.Quit();
}));
run_loop.Run();
ASSERT_TRUE(post_data_requested_callback_invoked);
ASSERT_TRUE(shared_file_1);
ASSERT_TRUE(shared_file_2);
std::string file1_contents;
ASSERT_NO_FATAL_FAILURE(ReadFile(shared_file_1.Get(), file1_contents));
std::string file2_contents;
ASSERT_NO_FATAL_FAILURE(ReadFile(shared_file_2.Get(), file2_contents));
ASSERT_EQ(file1_contents.length() + file2_contents.length(),
kMaxSharedFileBytesForTest);
}
TEST_F(ShareOperationUnitTest, FilesTotallingLargerThanSizeLimit) {
if (!IsSupportedEnvironment())
return;
bool post_data_requested_callback_invoked = false;
ComPtr<IStorageFile> shared_file_1;
ComPtr<IStorageFile> shared_file_2;
fake_data_transfer_manager()->SetPostDataRequestedCallback(
base::BindLambdaForTesting(
[&](const FakeDataTransferManager::DataRequestedContent&
data_requested_content) {
ASSERT_FALSE(post_data_requested_callback_invoked);
post_data_requested_callback_invoked = true;
ASSERT_EQ(data_requested_content.files.size(), 2ull);
shared_file_1 = data_requested_content.files[0].file;
shared_file_2 = data_requested_content.files[1].file;
}));
base::RunLoop run_loop;
std::vector<blink::mojom::SharedFilePtr> files;
files.push_back(
CreateSharedFile("File1.txt", "text/plain",
std::string(kMaxSharedFileBytesForTest / 2, '*')));
files.push_back(
CreateSharedFile("File2.txt", "text/plain",
std::string((kMaxSharedFileBytesForTest / 2) + 1, '*')));
ShareOperation operation{"", "", GURL::EmptyGURL(), std::move(files),
web_contents()};
operation.Run(
base::BindLambdaForTesting([&run_loop](blink::mojom::ShareError error) {
ASSERT_EQ(error, blink::mojom::ShareError::OK);
run_loop.Quit();
}));
run_loop.Run();
ASSERT_TRUE(post_data_requested_callback_invoked);
ASSERT_TRUE(shared_file_1);
ASSERT_TRUE(shared_file_2);
std::string file1_contents;
ASSERT_NO_FATAL_FAILURE(ReadFile(shared_file_1.Get(), file1_contents));
std::string file2_contents;
ASSERT_NO_FATAL_FAILURE(ReadFile(shared_file_2.Get(), file2_contents));
ASSERT_LT(file1_contents.length() + file2_contents.length(),
kMaxSharedFileBytesForTest + 1);
}
} // namespace webshare
...@@ -338,6 +338,8 @@ static_library("test_support") { ...@@ -338,6 +338,8 @@ static_library("test_support") {
"../browser/webshare/win/fake_uri_runtime_class_factory.h", "../browser/webshare/win/fake_uri_runtime_class_factory.h",
"../browser/webshare/win/scoped_fake_data_transfer_manager_interop.cc", "../browser/webshare/win/scoped_fake_data_transfer_manager_interop.cc",
"../browser/webshare/win/scoped_fake_data_transfer_manager_interop.h", "../browser/webshare/win/scoped_fake_data_transfer_manager_interop.h",
"../browser/webshare/win/scoped_share_operation_fake_components.cc",
"../browser/webshare/win/scoped_share_operation_fake_components.h",
"//chrome/app/chrome_crash_reporter_client_win.cc", "//chrome/app/chrome_crash_reporter_client_win.cc",
] ]
public_deps += [ public_deps += [
...@@ -5835,6 +5837,7 @@ test("unit_tests") { ...@@ -5835,6 +5837,7 @@ test("unit_tests") {
"../browser/webshare/win/fake_random_access_stream_unittest.cc", "../browser/webshare/win/fake_random_access_stream_unittest.cc",
"../browser/webshare/win/fake_storage_file_statics_unittest.cc", "../browser/webshare/win/fake_storage_file_statics_unittest.cc",
"../browser/webshare/win/fake_uri_runtime_class_factory_unittest.cc", "../browser/webshare/win/fake_uri_runtime_class_factory_unittest.cc",
"../browser/webshare/win/share_operation_unittest.cc",
"../browser/webshare/win/show_share_ui_for_window_operation_unittest.cc", "../browser/webshare/win/show_share_ui_for_window_operation_unittest.cc",
] ]
deps += [ deps += [
......
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