Commit c310416d authored by Eric Willigers's avatar Eric Willigers Committed by Commit Bot

Web Share: File sharing implementation for Chrome OS

When navigator.share() is called with files, the files are saved
to a new directory, and sharesheet::SharesheetService is used to
present the user with a choice of share targets.

ShareServiceUnitTest now adds blobs to blob storage context.
Test cases TotalBytes and FileBytes are removed as they
became redundant with
https://chromium-review.googlesource.com/c/chromium/src/+/2415431
and they risk flakiness by creating large blobs.

Spec: https://w3c.github.io/web-share/#share-method

Bug: 1110119
Change-Id: I8c7d44f899cf8c0d31cd87fcb0ce77b25f5a98b6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2422107
Commit-Queue: Eric Willigers <ericwilligers@chromium.org>
Reviewed-by: default avatarLuciano Pacheco <lucmult@chromium.org>
Reviewed-by: default avatarMaggie Cai <mxcai@chromium.org>
Reviewed-by: default avatarMelissa Zhang <melzhang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#810759}
parent 57341761
...@@ -4253,6 +4253,12 @@ static_library("browser") { ...@@ -4253,6 +4253,12 @@ static_library("browser") {
"upgrade_detector/installed_version_updater_chromeos.h", "upgrade_detector/installed_version_updater_chromeos.h",
"upgrade_detector/upgrade_detector_chromeos.cc", "upgrade_detector/upgrade_detector_chromeos.cc",
"upgrade_detector/upgrade_detector_chromeos.h", "upgrade_detector/upgrade_detector_chromeos.h",
"webshare/chromeos/prepare_directory_task.cc",
"webshare/chromeos/prepare_directory_task.h",
"webshare/chromeos/sharesheet_client.cc",
"webshare/chromeos/sharesheet_client.h",
"webshare/chromeos/store_files_task.cc",
"webshare/chromeos/store_files_task.h",
] ]
deps += [ deps += [
"//ash", "//ash",
......
// 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/chromeos/prepare_directory_task.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace webshare {
PrepareDirectoryTask::PrepareDirectoryTask(
base::FilePath directory,
blink::mojom::ShareService::ShareCallback callback)
: directory_(std::move(directory)), callback_(std::move(callback)) {}
PrepareDirectoryTask::~PrepareDirectoryTask() = default;
void PrepareDirectoryTask::Start() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::BindOnce(&PrepareDirectoryTask::PrepareDirectory, directory_),
base::BindOnce(&PrepareDirectoryTask::OnPrepareDirectory,
weak_ptr_factory_.GetWeakPtr()));
}
// static
base::File::Error PrepareDirectoryTask::PrepareDirectory(
base::FilePath directory) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::WILL_BLOCK);
base::File::Error result = base::File::FILE_OK;
if (!base::CreateDirectoryAndGetError(directory, &result)) {
DCHECK(result != base::File::FILE_OK);
}
// TODO(crbug.com/1110119): Delete any old files in directory.
return result;
}
void PrepareDirectoryTask::OnPrepareDirectory(base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::move(callback_).Run((result == base::File::FILE_OK)
? blink::mojom::ShareError::OK
: blink::mojom::ShareError::PERMISSION_DENIED);
}
} // 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_CHROMEOS_PREPARE_DIRECTORY_TASK_H_
#define CHROME_BROWSER_WEBSHARE_CHROMEOS_PREPARE_DIRECTORY_TASK_H_
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/weak_ptr.h"
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
namespace webshare {
// Creates a directory to hold files being shared to SharesheetService.
class PrepareDirectoryTask {
public:
PrepareDirectoryTask(base::FilePath directory,
blink::mojom::ShareService::ShareCallback callback);
PrepareDirectoryTask(const PrepareDirectoryTask&) = delete;
PrepareDirectoryTask& operator=(const PrepareDirectoryTask&) = delete;
~PrepareDirectoryTask();
// Launches the task. |callback_| will be called on the original (UI) thread
// when the task completes.
void Start();
private:
// Runs on a thread where blocking is permitted.
static base::File::Error PrepareDirectory(base::FilePath directory);
void OnPrepareDirectory(base::File::Error result);
const base::FilePath directory_;
blink::mojom::ShareService::ShareCallback callback_;
base::WeakPtrFactory<PrepareDirectoryTask> weak_ptr_factory_{this};
};
} // namespace webshare
#endif // CHROME_BROWSER_WEBSHARE_CHROMEOS_PREPARE_DIRECTORY_TASK_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/chromeos/sharesheet_client.h"
#include <memory>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sharesheet/sharesheet_service.h"
#include "chrome/browser/sharesheet/sharesheet_service_factory.h"
#include "chrome/browser/webshare/chromeos/prepare_directory_task.h"
#include "chrome/browser/webshare/chromeos/store_files_task.h"
#include "components/services/app_service/public/cpp/intent_util.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "net/base/filename_util.h"
using content::BrowserContext;
using content::BrowserThread;
using content::StoragePartition;
using content::WebContents;
namespace {
constexpr base::FilePath::CharType kWebShareDirname[] =
FILE_PATH_LITERAL("WebShare");
// We don't use |supplied_name| as it may contain special characters, and it may
// not be unique. The suffix has been checked by
// ShareServiceImpl::IsDangerousFilename().
base::FilePath GenerateFileName(const base::FilePath& directory,
const std::string& supplied_name) {
static unsigned counter = 0;
++counter;
size_t suffix_pos = supplied_name.find_last_of('.');
std::string filename = base::StringPrintf("share%u%s", counter,
supplied_name.c_str() + suffix_pos);
return directory.Append(filename);
}
} // namespace
namespace webshare {
SharesheetClient::CurrentShare::CurrentShare() = default;
SharesheetClient::CurrentShare::CurrentShare(CurrentShare&&) = default;
SharesheetClient::CurrentShare& SharesheetClient::CurrentShare::operator=(
SharesheetClient::CurrentShare&&) = default;
SharesheetClient::CurrentShare::~CurrentShare() = default;
SharesheetClient::SharesheetClient(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents) {}
SharesheetClient::~SharesheetClient() = default;
void SharesheetClient::Share(
const std::string& title,
const std::string& text,
const GURL& share_url,
std::vector<blink::mojom::SharedFilePtr> files,
blink::mojom::ShareService::ShareCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// The SharesheetClient only shows one share sheet at a time.
if (current_share_.has_value() || !web_contents()) {
std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED);
return;
}
if (files.empty()) {
// TODO(crbug.com/1127670): Support title/text/url sharing.
std::move(callback).Run(blink::mojom::ShareError::CANCELED);
return;
}
BrowserContext* const browser_context = web_contents()->GetBrowserContext();
StoragePartition* const partition =
BrowserContext::GetDefaultStoragePartition(browser_context);
current_share_ = CurrentShare();
current_share_->files = std::move(files);
current_share_->directory = partition->GetPath().Append(kWebShareDirname);
current_share_->callback = std::move(callback);
current_share_->prepare_directory_task =
std::make_unique<PrepareDirectoryTask>(
current_share_->directory,
base::BindOnce(&SharesheetClient::OnPrepareDirectory,
weak_ptr_factory_.GetWeakPtr()));
current_share_->prepare_directory_task->Start();
}
// static
void SharesheetClient::SetSharesheetCallbackForTesting(
SharesheetCallback callback) {
GetSharesheetCallback() = std::move(callback);
}
void SharesheetClient::OnPrepareDirectory(blink::mojom::ShareError error) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!web_contents() || error != blink::mojom::ShareError::OK) {
std::move(current_share_->callback).Run(error);
current_share_ = base::nullopt;
return;
}
std::vector<base::FilePath> filenames;
for (const auto& file : current_share_->files) {
base::FilePath filename =
GenerateFileName(current_share_->directory, file->name);
current_share_->content_types.push_back(file->blob->content_type);
current_share_->file_urls.push_back(net::FilePathToFileURL(filename));
filenames.push_back(std::move(filename));
}
BrowserContext* const browser_context = web_contents()->GetBrowserContext();
std::unique_ptr<StoreFilesTask> store_files_task =
std::make_unique<StoreFilesTask>(
BrowserContext::GetBlobStorageContext(browser_context),
std::move(filenames), std::move(current_share_->files),
base::BindOnce(&SharesheetClient::OnStoreFiles,
weak_ptr_factory_.GetWeakPtr()));
// The StoreFilesTask is self-owned.
store_files_task.release()->Start();
}
void SharesheetClient::OnStoreFiles(blink::mojom::ShareError error) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!web_contents() || error != blink::mojom::ShareError::OK) {
std::move(current_share_->callback).Run(error);
current_share_ = base::nullopt;
return;
}
blink::mojom::ShareError result = GetSharesheetCallback().Run(
web_contents(), std::move(current_share_->file_urls),
std::move(current_share_->content_types));
std::move(current_share_->callback).Run(result);
current_share_ = base::nullopt;
}
// static
blink::mojom::ShareError SharesheetClient::ShowSharesheet(
content::WebContents* web_contents,
std::vector<GURL> file_urls,
std::vector<std::string> content_types) {
Profile* const profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
DCHECK(profile);
sharesheet::SharesheetService* const sharesheet_service =
sharesheet::SharesheetServiceFactory::GetForProfile(profile);
sharesheet_service->ShowBubble(
web_contents, apps_util::CreateShareIntentFromFiles(
std::move(file_urls), std::move(content_types)));
return blink::mojom::ShareError::OK;
}
SharesheetClient::SharesheetCallback&
SharesheetClient::GetSharesheetCallback() {
static base::NoDestructor<SharesheetCallback> callback(
base::BindRepeating(&SharesheetClient::ShowSharesheet));
return *callback;
}
void SharesheetClient::WebContentsDestroyed() {
current_share_ = base::nullopt;
}
} // 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_CHROMEOS_SHARESHEET_CLIENT_H_
#define CHROME_BROWSER_WEBSHARE_CHROMEOS_SHARESHEET_CLIENT_H_
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "content/public/browser/web_contents_observer.h"
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#include "url/gurl.h"
namespace content {
class WebContents;
}
namespace webshare {
class PrepareDirectoryTask;
// Chrome-OS implementation of navigator.share() sharing to
// sharesheet::SharesheetService.
class SharesheetClient : public content::WebContentsObserver {
public:
using SharesheetCallback = base::RepeatingCallback<blink::mojom::ShareError(
content::WebContents* web_contents,
std::vector<GURL> file_urls,
std::vector<std::string> content_types)>;
explicit SharesheetClient(content::WebContents* web_contents);
SharesheetClient(const SharesheetClient&) = delete;
SharesheetClient& operator=(const SharesheetClient&) = delete;
~SharesheetClient() override;
void Share(const std::string& title,
const std::string& text,
const GURL& share_url,
std::vector<blink::mojom::SharedFilePtr> files,
blink::mojom::ShareService::ShareCallback callback);
static void SetSharesheetCallbackForTesting(SharesheetCallback callback);
private:
void OnPrepareDirectory(blink::mojom::ShareError);
void OnStoreFiles(blink::mojom::ShareError);
static blink::mojom::ShareError ShowSharesheet(
content::WebContents* web_contents,
std::vector<GURL> file_urls,
std::vector<std::string> content_types);
static SharesheetCallback& GetSharesheetCallback();
// WebContentsObserver:
void WebContentsDestroyed() override;
struct CurrentShare {
CurrentShare();
CurrentShare(CurrentShare&&);
CurrentShare& operator=(CurrentShare&&);
CurrentShare(const CurrentShare&) = delete;
CurrentShare& operator=(const CurrentShare&) = delete;
~CurrentShare();
std::vector<blink::mojom::SharedFilePtr> files;
base::FilePath directory;
std::vector<GURL> file_urls;
std::vector<std::string> content_types;
blink::mojom::ShareService::ShareCallback callback;
std::unique_ptr<PrepareDirectoryTask> prepare_directory_task;
};
base::Optional<CurrentShare> current_share_;
base::WeakPtrFactory<SharesheetClient> weak_ptr_factory_{this};
};
} // namespace webshare
#endif // CHROME_BROWSER_WEBSHARE_CHROMEOS_SHARESHEET_CLIENT_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/chromeos/sharesheet_client.h"
#include <string>
#include <vector>
#include "base/files/file_util.h"
#include "base/run_loop.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/scoped_blocking_call.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/base/filename_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#include "url/gurl.h"
namespace webshare {
class SharesheetClientBrowserTest : public InProcessBrowserTest {
public:
SharesheetClientBrowserTest() {
feature_list_.InitAndEnableFeature(features::kWebShare);
}
static void CheckSize(const base::FilePath& file_path,
int64_t expected_size) {
base::RunLoop run_loop;
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::BindLambdaForTesting([&file_path]() {
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
int64_t file_size;
EXPECT_TRUE(base::GetFileSize(file_path, &file_size));
return file_size;
}),
base::BindLambdaForTesting(
[&run_loop, &expected_size](int64_t file_size) {
EXPECT_EQ(expected_size, file_size);
run_loop.Quit();
}));
run_loop.Run();
}
GURL GetAppUrl() const {
return embedded_test_server()->GetURL("/webshare/index.html");
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, ShareTwoFiles) {
const std::string script = "share_multiple_files()";
ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL(browser(), GetAppUrl());
content::WebContents* const contents =
browser()->tab_strip_model()->GetActiveWebContents();
base::FilePath first_file_path;
base::FilePath second_file_path;
SharesheetClient::SetSharesheetCallbackForTesting(base::BindLambdaForTesting(
[contents, &first_file_path, &second_file_path](
content::WebContents* web_contents, std::vector<GURL> file_urls,
std::vector<std::string> content_types) {
EXPECT_EQ(contents, web_contents);
EXPECT_EQ(file_urls.size(), 2U);
EXPECT_TRUE(net::FileURLToFilePath(file_urls[0], &first_file_path));
EXPECT_TRUE(net::FileURLToFilePath(file_urls[1], &second_file_path));
EXPECT_EQ(content_types.size(), 2U);
EXPECT_EQ(content_types[0], "audio/mp3");
EXPECT_EQ(content_types[1], "video/mp4");
return blink::mojom::ShareError::OK;
}));
EXPECT_EQ("share succeeded", content::EvalJs(contents, script));
CheckSize(first_file_path, /*expected_size=*/345);
CheckSize(second_file_path, /*expected_size=*/67890);
}
IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, RepeatedShare) {
const int kRepeats = 3;
const std::string script = "share_single_file()";
ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL(browser(), GetAppUrl());
content::WebContents* const contents =
browser()->tab_strip_model()->GetActiveWebContents();
for (int count = 0; count < kRepeats; ++count) {
base::FilePath file_path;
SharesheetClient::SetSharesheetCallbackForTesting(
base::BindLambdaForTesting(
[contents, &file_path](content::WebContents* web_contents,
std::vector<GURL> file_urls,
std::vector<std::string> content_types) {
EXPECT_EQ(contents, web_contents);
EXPECT_EQ(file_urls.size(), 1U);
EXPECT_TRUE(net::FileURLToFilePath(file_urls[0], &file_path));
EXPECT_EQ(content_types.size(), 1U);
EXPECT_EQ(content_types[0], "image/webp");
return blink::mojom::ShareError::OK;
}));
EXPECT_EQ("share succeeded", content::EvalJs(contents, script));
CheckSize(file_path, /*expected_size=*/12);
}
}
} // 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.
#include "chrome/browser/webshare/chromeos/store_files_task.h"
#include <memory>
#include "base/bind.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "storage/browser/blob/blob_storage_context.h"
using content::BrowserThread;
namespace webshare {
StoreFilesTask::StoreFilesTask(
content::BrowserContext::BlobContextGetter blob_context_getter,
std::vector<base::FilePath> filenames,
std::vector<blink::mojom::SharedFilePtr> files,
blink::mojom::ShareService::ShareCallback callback)
: blob_context_getter_(std::move(blob_context_getter)),
filenames_(std::move(filenames)),
files_(std::move(files)),
callback_(std::move(callback)),
index_(files_.size()) {
DCHECK_EQ(filenames_.size(), files_.size());
DCHECK(!files_.empty());
}
StoreFilesTask::~StoreFilesTask() = default;
void StoreFilesTask::Start() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// TODO(crbug.com/1132202): Limit the total size of shared files to
// kMaxSharedFileBytes.
// The StoreFilesTask is self-owned. It is deleted in OnProgress.
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&StoreFilesTask::OnProgress, base::Unretained(this),
storage::mojom::WriteBlobToFileResult::kSuccess));
}
void StoreFilesTask::OnProgress(storage::mojom::WriteBlobToFileResult result) {
blink::mojom::ShareError share_result = blink::mojom::ShareError::OK;
storage::mojom::BlobStorageContext* const blob_storage_context =
blob_context_getter_.Run().get();
if (!blob_storage_context ||
result != storage::mojom::WriteBlobToFileResult::kSuccess) {
share_result = blink::mojom::ShareError::PERMISSION_DENIED;
index_ = 0U;
}
if (index_ == 0U) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_), share_result));
delete this;
return;
}
--index_;
blob_storage_context->WriteBlobToFile(
std::move(files_[index_]->blob->blob), filenames_[index_],
/*flush_on_write=*/true,
/*last_modified=*/base::nullopt,
base::BindOnce(&StoreFilesTask::OnProgress, base::Unretained(this)));
}
} // 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_CHROMEOS_STORE_FILES_TASK_H_
#define CHROME_BROWSER_WEBSHARE_CHROMEOS_STORE_FILES_TASK_H_
#include <vector>
#include "base/files/file_path.h"
#include "components/services/storage/public/mojom/blob_storage_context.mojom.h"
#include "content/public/browser/browser_context.h"
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
namespace webshare {
// Stores shared |files| using the specified |filenames|.
class StoreFilesTask {
public:
StoreFilesTask(content::BrowserContext::BlobContextGetter blob_context_getter,
std::vector<base::FilePath> filenames,
std::vector<blink::mojom::SharedFilePtr> files,
blink::mojom::ShareService::ShareCallback callback);
StoreFilesTask(const StoreFilesTask&) = delete;
StoreFilesTask& operator=(const StoreFilesTask&) = delete;
~StoreFilesTask();
// Takes ownership of the StoreFilesTask. |callback_| will be called on the
// original (UI) thread when the task completes.
void Start();
private:
// Runs on the IO thread.
void OnProgress(storage::mojom::WriteBlobToFileResult result);
content::BrowserContext::BlobContextGetter blob_context_getter_;
std::vector<base::FilePath> filenames_;
std::vector<blink::mojom::SharedFilePtr> files_;
blink::mojom::ShareService::ShareCallback callback_;
unsigned index_;
};
} // namespace webshare
#endif // CHROME_BROWSER_WEBSHARE_CHROMEOS_STORE_FILES_TASK_H_
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_features.h" #include "chrome/common/chrome_features.h"
...@@ -22,7 +23,7 @@ class ShareServiceBrowserTest : public InProcessBrowserTest { ...@@ -22,7 +23,7 @@ class ShareServiceBrowserTest : public InProcessBrowserTest {
base::test::ScopedFeatureList feature_list_; base::test::ScopedFeatureList feature_list_;
}; };
IN_PROC_BROWSER_TEST_F(ShareServiceBrowserTest, Cancellation) { IN_PROC_BROWSER_TEST_F(ShareServiceBrowserTest, Text) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL( ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/webshare/index.html")); browser(), embedded_test_server()->GetURL("/webshare/index.html"));
...@@ -30,6 +31,12 @@ IN_PROC_BROWSER_TEST_F(ShareServiceBrowserTest, Cancellation) { ...@@ -30,6 +31,12 @@ IN_PROC_BROWSER_TEST_F(ShareServiceBrowserTest, Cancellation) {
content::WebContents* const contents = content::WebContents* const contents =
browser()->tab_strip_model()->GetActiveWebContents(); browser()->tab_strip_model()->GetActiveWebContents();
const std::string script = "share_text('hello')"; const std::string script = "share_text('hello')";
EXPECT_EQ("share failed: AbortError: Share canceled", const content::EvalJsResult result = content::EvalJs(contents, script);
content::EvalJs(contents, script));
#if defined(OS_CHROMEOS)
// ChromeOS currently only supports file sharing.
EXPECT_EQ("share failed: AbortError: Share canceled", result);
#else
EXPECT_EQ("share succeeded", result);
#endif
} }
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
ShareServiceImpl::ShareServiceImpl(content::RenderFrameHost& render_frame_host) ShareServiceImpl::ShareServiceImpl(content::RenderFrameHost& render_frame_host)
: content::WebContentsObserver( : content::WebContentsObserver(
content::WebContents::FromRenderFrameHost(&render_frame_host)), content::WebContents::FromRenderFrameHost(&render_frame_host)),
#if defined(OS_CHROMEOS)
sharesheet_client_(web_contents()),
#endif
render_frame_host_(&render_frame_host) { render_frame_host_(&render_frame_host) {
DCHECK(base::FeatureList::IsEnabled(features::kWebShare)); DCHECK(base::FeatureList::IsEnabled(features::kWebShare));
} }
...@@ -128,13 +131,20 @@ void ShareServiceImpl::Share(const std::string& title, ...@@ -128,13 +131,20 @@ void ShareServiceImpl::Share(const std::string& title,
const GURL& share_url, const GURL& share_url,
std::vector<blink::mojom::SharedFilePtr> files, std::vector<blink::mojom::SharedFilePtr> files,
ShareCallback callback) { ShareCallback callback) {
content::WebContents* const web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host_);
if (!web_contents) {
std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED);
return;
}
if (files.size() > kMaxSharedFileCount) { if (files.size() > kMaxSharedFileCount) {
std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED); std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED);
return; return;
} }
for (auto& file : files) { for (auto& file : files) {
if (!file || !file->blob) { if (!file || !file->blob || !file->blob->blob) {
mojo::ReportBadMessage("Invalid file to share()"); mojo::ReportBadMessage("Invalid file to share()");
return; return;
} }
...@@ -152,10 +162,14 @@ void ShareServiceImpl::Share(const std::string& title, ...@@ -152,10 +162,14 @@ void ShareServiceImpl::Share(const std::string& title,
// the blobs. // the blobs.
} }
#if defined(OS_CHROMEOS)
sharesheet_client_.Share(title, text, share_url, std::move(files),
std::move(callback));
#else
// TODO(crbug.com/1035527): Add implementation for OS_WIN // TODO(crbug.com/1035527): Add implementation for OS_WIN
// TODO(crbug.com/1110119): Add implementation for OS_CHROMEOS
NOTIMPLEMENTED(); NOTIMPLEMENTED();
std::move(callback).Run(blink::mojom::ShareError::CANCELED); std::move(callback).Run(blink::mojom::ShareError::OK);
#endif
} }
void ShareServiceImpl::RenderFrameDeleted( void ShareServiceImpl::RenderFrameDeleted(
......
...@@ -9,9 +9,14 @@ ...@@ -9,9 +9,14 @@
#include <vector> #include <vector>
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "build/build_config.h"
#include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_observer.h"
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h" #include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/webshare/chromeos/sharesheet_client.h"
#endif
class GURL; class GURL;
namespace content { namespace content {
...@@ -47,6 +52,9 @@ class ShareServiceImpl : public blink::mojom::ShareService, ...@@ -47,6 +52,9 @@ class ShareServiceImpl : public blink::mojom::ShareService,
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override; void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
private: private:
#if defined(OS_CHROMEOS)
webshare::SharesheetClient sharesheet_client_;
#endif
content::RenderFrameHost* render_frame_host_; content::RenderFrameHost* render_frame_host_;
}; };
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "base/guid.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/test/bind_test_util.h" #include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
...@@ -13,10 +14,21 @@ ...@@ -13,10 +14,21 @@
#include "chrome/browser/webshare/share_service_impl.h" #include "chrome/browser/webshare/share_service_impl.h"
#include "chrome/common/chrome_features.h" #include "chrome/common/chrome_features.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.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 "third_party/blink/public/mojom/blob/serialized_blob.mojom.h"
#include "url/gurl.h" #include "url/gurl.h"
using blink::mojom::ShareError; using blink::mojom::ShareError;
#if defined(OS_CHROMEOS)
#include "chrome/browser/webshare/chromeos/sharesheet_client.h"
#endif
class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness { class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness {
public: public:
ShareServiceUnitTest() { ShareServiceUnitTest() {
...@@ -27,10 +39,15 @@ class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness { ...@@ -27,10 +39,15 @@ class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness {
void SetUp() override { void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp(); ChromeRenderViewHostTestHarness::SetUp();
share_service_ = std::make_unique<ShareServiceImpl>(*main_rfh()); share_service_ = std::make_unique<ShareServiceImpl>(*main_rfh());
#if defined(OS_CHROMEOS)
webshare::SharesheetClient::SetSharesheetCallbackForTesting(
base::BindRepeating(&ShareServiceUnitTest::AcceptShareRequest));
#endif
} }
ShareError ShareGeneratedFileData(const std::string& extension, ShareError ShareGeneratedFileData(const std::string& extension,
const std::string& mime_type, const std::string& content_type,
unsigned file_length = 100, unsigned file_length = 100,
unsigned file_count = 1) { unsigned file_count = 1) {
const std::string kTitle; const std::string kTitle;
...@@ -41,10 +58,7 @@ class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness { ...@@ -41,10 +58,7 @@ class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness {
for (unsigned index = 0; index < file_count; ++index) { for (unsigned index = 0; index < file_count; ++index) {
const std::string name = const std::string name =
base::StringPrintf("share%d%s", index, extension.c_str()); base::StringPrintf("share%d%s", index, extension.c_str());
auto blob = blink::mojom::SerializedBlob::New(); files.push_back(CreateSharedFile(name, content_type, file_length));
blob->content_type = mime_type;
blob->size = file_length;
files.push_back(blink::mojom::SharedFile::New(name, std::move(blob)));
} }
ShareError result; ShareError result;
...@@ -60,39 +74,65 @@ class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness { ...@@ -60,39 +74,65 @@ class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness {
} }
private: private:
blink::mojom::SharedFilePtr CreateSharedFile(const std::string& name,
const std::string& content_type,
unsigned file_length) {
const std::string uuid = base::GenerateGUID();
auto blob = blink::mojom::SerializedBlob::New();
blob->uuid = uuid;
blob->content_type = content_type;
blob->size = file_length;
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, file_length]() {
storage::BlobImpl::Create(
blob_context_getter.Run()->AddFinishedBlob(
CreateBuilder(uuid, content_type, file_length)),
blob->blob.InitWithNewPipeAndPassReceiver());
}),
base::BindLambdaForTesting([&run_loop]() { run_loop.Quit(); }));
run_loop.Run();
return blink::mojom::SharedFile::New(name, std::move(blob));
}
static std::unique_ptr<storage::BlobDataBuilder> CreateBuilder(
const std::string& uuid,
const std::string& content_type,
unsigned file_length) {
auto builder = std::make_unique<storage::BlobDataBuilder>(uuid);
builder->set_content_type(content_type);
const std::string contents(file_length, '*');
builder->AppendData(contents);
return builder;
}
#if defined(OS_CHROMEOS)
static ShareError AcceptShareRequest(content::WebContents* web_contents,
std::vector<GURL> file_urls,
std::vector<std::string> content_types) {
return ShareError::OK;
}
#endif
base::test::ScopedFeatureList feature_list_; base::test::ScopedFeatureList feature_list_;
std::unique_ptr<ShareServiceImpl> share_service_; std::unique_ptr<ShareServiceImpl> share_service_;
}; };
TEST_F(ShareServiceUnitTest, FileCount) { TEST_F(ShareServiceUnitTest, FileCount) {
EXPECT_EQ( EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".txt", "text/plain", 1234,
ShareError::CANCELED, kMaxSharedFileCount));
ShareGeneratedFileData(".txt", "text/plain", 1234, kMaxSharedFileCount));
EXPECT_EQ(ShareError::PERMISSION_DENIED, EXPECT_EQ(ShareError::PERMISSION_DENIED,
ShareGeneratedFileData(".txt", "text/plain", 1234, ShareGeneratedFileData(".txt", "text/plain", 1234,
kMaxSharedFileCount + 1)); kMaxSharedFileCount + 1));
} }
TEST_F(ShareServiceUnitTest, TotalBytes) {
EXPECT_EQ(ShareError::CANCELED,
ShareGeneratedFileData(".txt", "text/plain",
kMaxSharedFileBytes / kMaxSharedFileCount,
kMaxSharedFileCount));
EXPECT_EQ(
ShareError::CANCELED,
ShareGeneratedFileData(".txt", "text/plain",
(kMaxSharedFileBytes / kMaxSharedFileCount) + 1,
kMaxSharedFileCount));
}
TEST_F(ShareServiceUnitTest, FileBytes) {
EXPECT_EQ(ShareError::CANCELED,
ShareGeneratedFileData(".txt", "text/plain", kMaxSharedFileBytes));
EXPECT_EQ(
ShareError::CANCELED,
ShareGeneratedFileData(".txt", "text/plain", kMaxSharedFileBytes + 1));
}
TEST_F(ShareServiceUnitTest, DangerousFilename) { TEST_F(ShareServiceUnitTest, DangerousFilename) {
EXPECT_TRUE(ShareServiceImpl::IsDangerousFilename("")); EXPECT_TRUE(ShareServiceImpl::IsDangerousFilename(""));
EXPECT_TRUE(ShareServiceImpl::IsDangerousFilename(".")); EXPECT_TRUE(ShareServiceImpl::IsDangerousFilename("."));
...@@ -123,13 +163,10 @@ TEST_F(ShareServiceUnitTest, DangerousMimeType) { ...@@ -123,13 +163,10 @@ TEST_F(ShareServiceUnitTest, DangerousMimeType) {
} }
TEST_F(ShareServiceUnitTest, Multimedia) { TEST_F(ShareServiceUnitTest, Multimedia) {
EXPECT_EQ(ShareError::CANCELED, ShareGeneratedFileData(".bmp", "image/bmp")); EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".bmp", "image/bmp"));
EXPECT_EQ(ShareError::CANCELED, EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".xbm", "image/x-xbitmap"));
ShareGeneratedFileData(".xbm", "image/x-xbitmap")); EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".flac", "audio/flac"));
EXPECT_EQ(ShareError::CANCELED, EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".webm", "video/webm"));
ShareGeneratedFileData(".flac", "audio/flac"));
EXPECT_EQ(ShareError::CANCELED,
ShareGeneratedFileData(".webm", "video/webm"));
} }
TEST_F(ShareServiceUnitTest, PortableDocumentFormat) { TEST_F(ShareServiceUnitTest, PortableDocumentFormat) {
......
...@@ -1668,6 +1668,7 @@ if (!is_android) { ...@@ -1668,6 +1668,7 @@ if (!is_android) {
"../browser/policy/system_features_policy_browsertest.cc", "../browser/policy/system_features_policy_browsertest.cc",
"../browser/renderer_context_menu/quick_answers_menu_observer_browsertest.cc", "../browser/renderer_context_menu/quick_answers_menu_observer_browsertest.cc",
"../browser/ui/webui/nearby_share/nearby_share_dialog_ui_browsertest.cc", "../browser/ui/webui/nearby_share/nearby_share_dialog_ui_browsertest.cc",
"../browser/webshare/chromeos/sharesheet_client_browsertest.cc",
] ]
} }
......
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<link rel="icon" href="basic-48.png">
<script> <script>
'use strict'; 'use strict';
async function share_text(text) { async function share_text(text) {
try { try {
await navigator.share({text: text}); await navigator.share({text: text});
...@@ -11,6 +13,33 @@ ...@@ -11,6 +13,33 @@
return ('share failed: ' + error); return ('share failed: ' + error);
} }
} }
function create_file(name, content_type, length) {
const contents = ['*'.repeat(length)];
const options = {type: content_type};
return new File(contents, name, options);
}
async function share_single_file() {
try {
const single_file = create_file('sample.webp', 'image/webp', 12);
await navigator.share({files: [single_file]});
return 'share succeeded';
} catch(error) {
return ('share failed: ' + error);
}
}
async function share_multiple_files() {
try {
const first_file = create_file('sample.mp3', 'audio/mp3', 345);
const second_file = create_file('sample.mp4', 'video/mp4', 67890);
await navigator.share({files: [first_file, second_file]});
return 'share succeeded';
} catch(error) {
return ('share failed: ' + error);
}
}
</script> </script>
</head> </head>
<body> <body>
......
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