| // Copyright 2020 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/webshare/win/share_operation.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> |
| |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/strings/utf_string_conversions.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/browser_thread.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/base/win/internal_constants.h" |
| #include "ui/views/win/hwnd_util.h" |
| #include "url/gurl.h" |
| |
| using ABI::Windows::ApplicationModel::DataTransfer::IDataPackage; |
| using ABI::Windows::ApplicationModel::DataTransfer::IDataPackage2; |
| 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::Windows::Foundation::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 ABI::Windows::Foundation::Collections |
| |
| 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 final : 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) final { |
| 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(storage::FlushMode /*flush_mode*/, |
| net::CompletionOnceCallback callback) final { |
| 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::PostAsyncHandlers( |
| flush_operation_.Get(), |
| base::BindOnce(&DataWriterFileStreamWriter::OnFlushCompleted, |
| weak_factory_.GetWeakPtr())); |
| return net::ERR_IO_PENDING; |
| } |
| |
| int Write(net::IOBuffer* buf, |
| int buf_len, |
| net::CompletionOnceCallback callback) final { |
| 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::PostAsyncHandlers( |
| write_operation_.Get(), |
| 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, |
| mojo::PendingRemote<blink::mojom::Blob> blob) |
| : blob_context_getter_(blob_context_getter), |
| file_bytes_shared_(file_bytes_shared), |
| blob_(std::move(blob)) {} |
| |
| // 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 (!content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| 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_storage_context->GetBlobDataFromBlobRemote( |
| std::move(blob_), |
| base::BindOnce(&OutputStreamWriteOperation::BlobDataHandleReady, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void BlobDataHandleReady( |
| std::unique_ptr<storage::BlobDataHandle> data_handle) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| blob_handle_ = std::move(data_handle); |
| 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_; |
| mojo::PendingRemote<blink::mojom::Blob> blob_; |
| 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, |
| content::WebContents* web_contents) |
| : web_contents_(web_contents->GetWeakPtr()), |
| title_(std::move(title)), |
| text_(std::move(text)), |
| url_(std::move(url)) {} |
| |
| ShareOperation::~ShareOperation() { |
| if (callback_) |
| Complete(blink::mojom::ShareError::CANCELED); |
| } |
| |
| base::WeakPtr<ShareOperation> ShareOperation::AsWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void ShareOperation::Run(SharedFiles files, |
| blink::mojom::ShareService::ShareCallback callback) { |
| DCHECK(!callback_); |
| callback_ = std::move(callback); |
| |
| // If the corresponding web_contents have already been cleaned up, cancel |
| // the operation. |
| if (!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(); |
| std::wstring source = (source_url.is_valid() && |
| source_url.spec().size() <= INTERNET_MAX_URL_LENGTH) |
| ? base::UTF8ToWide(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( |
| file->name.path().value().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()); |
| |
| // Attempt to fetch the special HWND maintained for the primary WebContents of |
| // this window. For the sake of better communication with screen readers this |
| // HWND is (virtually) scoped to the same space as the WebContents (rather |
| // than the entire actual window), so allows the resulting Share dialog to |
| // better position/associate itself with the WebContents. |
| // |
| // Note: Though this is exposed to accessibility tools via standardized routes |
| // we could expect to leverage here, the browser may choose to not set up all |
| // these routes until an accessibility tool has been detected. Instead we look |
| // for this specific class directly so we can find it even if accessibility |
| // has not been configured yet. |
| if (hwnd) { |
| HWND accessible_hwnd = |
| ::FindWindowExW(/*hWndParent*/ hwnd, /*hWndChildAfter*/ NULL, |
| /*lpszClass*/ ui::kLegacyRenderWidgetHostHwnd, |
| /*lpszWindow*/ NULL); |
| if (accessible_hwnd) { |
| hwnd = accessible_hwnd; |
| } |
| } |
| |
| 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(), std::move(files))); |
| } |
| |
| void ShareOperation::OnDataRequested(SharedFiles files, |
| IDataRequestedEventArgs* event_args) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| blink::mojom::ShareError share_result; |
| if (!web_contents_) { |
| share_result = blink::mojom::ShareError::CANCELED; |
| } else if (PutShareContentInEventArgs(std::move(files), 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( |
| SharedFiles files, |
| IDataRequestedEventArgs* event_args) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!event_args) |
| return false; |
| |
| 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(std::move(files), data_request.Get()); |
| } |
| |
| bool ShareOperation::PutShareContentInDataPackage(SharedFiles files, |
| 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; |
| |
| ComPtr<IDataPackage2> data_package_2; |
| if (FAILED(data_package_.As(&data_package_2))) |
| return false; |
| |
| if (FAILED(data_package_2->SetWebLink(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>( |
| web_contents_->GetBrowserContext()->GetBlobStorageContext(), |
| file_bytes_shared, std::move(file->blob->blob)); |
| auto name_h = base::win::ScopedHString::Create(file->name.path().value()); |
| 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( |
| [](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; |
| } |
| |
| async_operations_.push_back(async_operation); |
| |
| if (FAILED(base::win::PostAsyncHandlers( |
| async_operation.Get(), |
| base::BindOnce(&ShareOperation::OnStreamedFileCreated, |
| weak_factory_.GetWeakPtr(), files.size())))) { |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| void ShareOperation::OnStreamedFileCreated(uint32_t expected_file_count, |
| 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 != expected_file_count) { |
| 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 |