Commit 07b26aee authored by Eric Willigers's avatar Eric Willigers Committed by Chromium LUCI CQ

Web Share: Support title/text/url sharing from web apps on ChromeOS

Web apps on Chrome OS may use the Web Share API to send title,
text and/or url, in addition to files.

If title, text and/or url ShareData fields have been supplied, they
are used to set the text and title for the Intent.

If the web app is sharing both a text string and a URL, we combine
them into a single string, with a space separator.
The same approach is already taken on Android:
https://source.chromium.org/chromium/chromium/src/+/master:components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareHelper.java;drc=28d989a1fc83edb779c034cad48572775696bc66;l=357

Not yet implemented or tested: sharing title/text/url without files.

Design doc:
https://docs.google.com/document/d/1EjpgseTbBhT9ogQv6HV6sLlBl4hpb2viOcmPtFJorMQ/edit#


Bug: 1127670
Change-Id: Ie56a372816602b71ff386dc1094be61610d70a79
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2569329
Commit-Queue: Eric Willigers <ericwilligers@chromium.org>
Reviewed-by: default avatarAlan Cutter <alancutter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#833292}
parent 7a99c782
...@@ -92,7 +92,7 @@ void SharesheetClient::Share( ...@@ -92,7 +92,7 @@ void SharesheetClient::Share(
} }
if (files.empty()) { if (files.empty()) {
// TODO(crbug.com/1127670): Support title/text/url sharing. // TODO(crbug.com/1127670): Support title/text/url sharing without files.
std::move(callback).Run(blink::mojom::ShareError::CANCELED); std::move(callback).Run(blink::mojom::ShareError::CANCELED);
return; return;
} }
...@@ -102,6 +102,15 @@ void SharesheetClient::Share( ...@@ -102,6 +102,15 @@ void SharesheetClient::Share(
current_share_->directory = current_share_->directory =
chromeos::CrosDisksClient::GetArchiveMountPoint().Append( chromeos::CrosDisksClient::GetArchiveMountPoint().Append(
kWebShareDirname); kWebShareDirname);
if (share_url.is_valid()) {
if (text.empty())
current_share_->text = share_url.spec();
else
current_share_->text = text + " " + share_url.spec();
} else {
current_share_->text = text;
}
current_share_->title = title;
current_share_->callback = std::move(callback); current_share_->callback = std::move(callback);
current_share_->prepare_directory_task = current_share_->prepare_directory_task =
...@@ -155,7 +164,8 @@ void SharesheetClient::OnStoreFiles(blink::mojom::ShareError error) { ...@@ -155,7 +164,8 @@ void SharesheetClient::OnStoreFiles(blink::mojom::ShareError error) {
GetSharesheetCallback().Run( GetSharesheetCallback().Run(
web_contents(), std::move(current_share_->file_paths), web_contents(), std::move(current_share_->file_paths),
std::move(current_share_->content_types), std::move(current_share_->content_types), current_share_->text,
current_share_->title,
base::BindOnce(&SharesheetClient::OnShowSharesheet, base::BindOnce(&SharesheetClient::OnShowSharesheet,
weak_ptr_factory_.GetWeakPtr())); weak_ptr_factory_.GetWeakPtr()));
} }
...@@ -166,10 +176,13 @@ void SharesheetClient::OnShowSharesheet(sharesheet::SharesheetResult result) { ...@@ -166,10 +176,13 @@ void SharesheetClient::OnShowSharesheet(sharesheet::SharesheetResult result) {
} }
// static // static
void SharesheetClient::ShowSharesheet(content::WebContents* web_contents, void SharesheetClient::ShowSharesheet(
std::vector<base::FilePath> file_paths, content::WebContents* web_contents,
std::vector<std::string> content_types, const std::vector<base::FilePath>& file_paths,
CloseCallback close_callback) { const std::vector<std::string>& content_types,
const std::string& text,
const std::string& title,
CloseCallback close_callback) {
if (!base::FeatureList::IsEnabled(features::kSharesheet)) { if (!base::FeatureList::IsEnabled(features::kSharesheet)) {
std::move(close_callback).Run(sharesheet::SharesheetResult::kCancel); std::move(close_callback).Run(sharesheet::SharesheetResult::kCancel);
return; return;
...@@ -184,8 +197,8 @@ void SharesheetClient::ShowSharesheet(content::WebContents* web_contents, ...@@ -184,8 +197,8 @@ void SharesheetClient::ShowSharesheet(content::WebContents* web_contents,
sharesheet_service->ShowBubble( sharesheet_service->ShowBubble(
web_contents, web_contents,
apps_util::CreateShareIntentFromFiles(profile, std::move(file_paths), apps_util::CreateShareIntentFromFiles(profile, file_paths, content_types,
std::move(content_types)), text, title),
std::move(close_callback)); std::move(close_callback));
} }
......
...@@ -28,11 +28,13 @@ class PrepareDirectoryTask; ...@@ -28,11 +28,13 @@ class PrepareDirectoryTask;
class SharesheetClient : public content::WebContentsObserver { class SharesheetClient : public content::WebContentsObserver {
public: public:
using CloseCallback = sharesheet::CloseCallback; using CloseCallback = sharesheet::CloseCallback;
using SharesheetCallback = using SharesheetCallback = base::RepeatingCallback<void(
base::RepeatingCallback<void(content::WebContents* web_contents, content::WebContents* web_contents,
std::vector<base::FilePath> file_paths, const std::vector<base::FilePath>& file_paths,
std::vector<std::string> content_types, const std::vector<std::string>& content_types,
CloseCallback close_callback)>; const std::string& text,
const std::string& title,
CloseCallback close_callback)>;
explicit SharesheetClient(content::WebContents* web_contents); explicit SharesheetClient(content::WebContents* web_contents);
SharesheetClient(const SharesheetClient&) = delete; SharesheetClient(const SharesheetClient&) = delete;
...@@ -55,8 +57,10 @@ class SharesheetClient : public content::WebContentsObserver { ...@@ -55,8 +57,10 @@ class SharesheetClient : public content::WebContentsObserver {
void OnShowSharesheet(sharesheet::SharesheetResult result); void OnShowSharesheet(sharesheet::SharesheetResult result);
static void ShowSharesheet(content::WebContents* web_contents, static void ShowSharesheet(content::WebContents* web_contents,
std::vector<base::FilePath> file_paths, const std::vector<base::FilePath>& file_paths,
std::vector<std::string> content_types, const std::vector<std::string>& content_types,
const std::string& text,
const std::string& title,
CloseCallback close_callback); CloseCallback close_callback);
static SharesheetCallback& GetSharesheetCallback(); static SharesheetCallback& GetSharesheetCallback();
...@@ -76,6 +80,8 @@ class SharesheetClient : public content::WebContentsObserver { ...@@ -76,6 +80,8 @@ class SharesheetClient : public content::WebContentsObserver {
base::FilePath directory; base::FilePath directory;
std::vector<base::FilePath> file_paths; std::vector<base::FilePath> file_paths;
std::vector<std::string> content_types; std::vector<std::string> content_types;
std::string text;
std::string title;
blink::mojom::ShareService::ShareCallback callback; blink::mojom::ShareService::ShareCallback callback;
std::unique_ptr<PrepareDirectoryTask> prepare_directory_task; std::unique_ptr<PrepareDirectoryTask> prepare_directory_task;
......
...@@ -58,6 +58,29 @@ class SharesheetClientBrowserTest : public InProcessBrowserTest { ...@@ -58,6 +58,29 @@ class SharesheetClientBrowserTest : public InProcessBrowserTest {
return embedded_test_server()->GetURL("/webshare/index.html"); return embedded_test_server()->GetURL("/webshare/index.html");
} }
void ConfirmShareText(const std::string& script,
const char* expected_text,
const char* expected_title) {
SharesheetClient::SetSharesheetCallbackForTesting(
base::BindLambdaForTesting(
[&expected_text, &expected_title](
content::WebContents* in_contents,
const std::vector<base::FilePath>& file_paths,
const std::vector<std::string>& content_types,
const std::string& text, const std::string& title,
SharesheetClient::CloseCallback close_callback) {
EXPECT_EQ(text, expected_text);
EXPECT_EQ(title, expected_title);
std::move(close_callback)
.Run(sharesheet::SharesheetResult::kCancel);
}));
content::WebContents* const contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ("share failed: AbortError: Share canceled",
content::EvalJs(contents, script));
}
private: private:
base::test::ScopedFeatureList feature_list_; base::test::ScopedFeatureList feature_list_;
}; };
...@@ -72,8 +95,9 @@ IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, ShareTwoFiles) { ...@@ -72,8 +95,9 @@ IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, ShareTwoFiles) {
SharesheetClient::SetSharesheetCallbackForTesting(base::BindLambdaForTesting( SharesheetClient::SetSharesheetCallbackForTesting(base::BindLambdaForTesting(
[contents, &file_paths](content::WebContents* in_contents, [contents, &file_paths](content::WebContents* in_contents,
std::vector<base::FilePath> in_file_paths, const std::vector<base::FilePath>& in_file_paths,
std::vector<std::string> content_types, const std::vector<std::string>& content_types,
const std::string& text, const std::string& title,
SharesheetClient::CloseCallback close_callback) { SharesheetClient::CloseCallback close_callback) {
EXPECT_EQ(contents, in_contents); EXPECT_EQ(contents, in_contents);
...@@ -107,8 +131,9 @@ IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, RepeatedShare) { ...@@ -107,8 +131,9 @@ IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, RepeatedShare) {
base::BindLambdaForTesting( base::BindLambdaForTesting(
[contents, &file_paths]( [contents, &file_paths](
content::WebContents* in_contents, content::WebContents* in_contents,
std::vector<base::FilePath> in_file_paths, const std::vector<base::FilePath>& in_file_paths,
std::vector<std::string> content_types, const std::vector<std::string>& content_types,
const std::string& text, const std::string& title,
SharesheetClient::CloseCallback close_callback) { SharesheetClient::CloseCallback close_callback) {
EXPECT_EQ(contents, in_contents); EXPECT_EQ(contents, in_contents);
...@@ -136,8 +161,9 @@ IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, CancelledShare) { ...@@ -136,8 +161,9 @@ IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, CancelledShare) {
ui_test_utils::NavigateToURL(browser(), GetAppUrl()); ui_test_utils::NavigateToURL(browser(), GetAppUrl());
SharesheetClient::SetSharesheetCallbackForTesting(base::BindLambdaForTesting( SharesheetClient::SetSharesheetCallbackForTesting(base::BindLambdaForTesting(
[](content::WebContents* in_contents, [](content::WebContents* in_contents,
std::vector<base::FilePath> file_paths, const std::vector<base::FilePath>& file_paths,
std::vector<std::string> content_types, const std::vector<std::string>& content_types, const std::string& text,
const std::string& title,
SharesheetClient::CloseCallback close_callback) { SharesheetClient::CloseCallback close_callback) {
std::move(close_callback).Run(sharesheet::SharesheetResult::kCancel); std::move(close_callback).Run(sharesheet::SharesheetResult::kCancel);
})); }));
...@@ -146,4 +172,24 @@ IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, CancelledShare) { ...@@ -146,4 +172,24 @@ IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, CancelledShare) {
content::EvalJs(contents, script)); content::EvalJs(contents, script));
} }
IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, TextWithFile) {
ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL(browser(), GetAppUrl());
ConfirmShareText("share_file_title()",
/*expected_text=*/"",
/*expected_title=*/"Subject");
ConfirmShareText("share_file_title_url()",
/*expected_text=*/"https://example.com/",
/*expected_title=*/"Subject");
ConfirmShareText("share_file_text()",
/*expected_text=*/"Message",
/*expected_title=*/"");
ConfirmShareText("share_file_text_url()",
/*expected_text=*/"Message https://example.com/",
/*expected_title=*/"");
ConfirmShareText("share_file_url()",
/*expected_text=*/"https://example.com/",
/*expected_title=*/"");
}
} // namespace webshare } // namespace webshare
...@@ -131,8 +131,10 @@ class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness { ...@@ -131,8 +131,10 @@ class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness {
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
static void AcceptShareRequest(content::WebContents* web_contents, static void AcceptShareRequest(content::WebContents* web_contents,
std::vector<base::FilePath> file_paths, const std::vector<base::FilePath>& file_paths,
std::vector<std::string> content_types, const std::vector<std::string>& content_types,
const std::string& text,
const std::string& title,
sharesheet::CloseCallback close_callback) { sharesheet::CloseCallback close_callback) {
std::move(close_callback).Run(sharesheet::SharesheetResult::kSuccess); std::move(close_callback).Run(sharesheet::SharesheetResult::kSuccess);
} }
......
...@@ -40,6 +40,61 @@ ...@@ -40,6 +40,61 @@
return ('share failed: ' + error); return ('share failed: ' + error);
} }
} }
async function share_file_title() {
try {
const single_file = create_file('sample.webp', 'image/webp', 12);
await navigator.share({files: [single_file], title: "Subject"});
return 'share succeeded';
} catch(error) {
return ('share failed: ' + error);
}
}
async function share_file_title_url() {
try {
const single_file = create_file('sample.webp', 'image/webp', 12);
await navigator.share({files: [single_file],
title: "Subject",
url: "https://example.com/"});
return 'share succeeded';
} catch(error) {
return ('share failed: ' + error);
}
}
async function share_file_text() {
try {
const single_file = create_file('sample.webp', 'image/webp', 12);
await navigator.share({files: [single_file], text: "Message"});
return 'share succeeded';
} catch(error) {
return ('share failed: ' + error);
}
}
async function share_file_text_url() {
try {
const single_file = create_file('sample.webp', 'image/webp', 12);
await navigator.share({files: [single_file],
text: "Message",
url: "https://example.com/"});
return 'share succeeded';
} catch(error) {
return ('share failed: ' + error);
}
}
async function share_file_url() {
try {
const single_file = create_file('sample.webp', 'image/webp', 12);
await navigator.share({files: [single_file],
url: "https://example.com/"});
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