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

Web Share Target for Chrome OS: Support navigation with method GET

When a share target has method GET, we set the url's query using
application/x-www-form-urlencoded serialization results.

Spec:
https://w3c.github.io/web-share-target/level-2/#launching-the-web-share-target

Design doc:
https://docs.google.com/document/d/1E4CYASFDVNqmyCbaxa8u8sOn0-Sc1miLeZfA3t3ou5o/edit#

Bug: 1125880
Change-Id: I09dc04df1535eb6e8cb0fc1fa61d926c7808ffef
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2590927Reviewed-by: default avatarAlan Cutter <alancutter@chromium.org>
Commit-Queue: Eric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#836912}
parent bdbed19d
......@@ -131,25 +131,27 @@ NavigateParams NavigateParamsForShareTarget(
is_value_file_uris.push_back(false);
}
// TODO(crbug.com/1125880): Support Web Share Target with method=GET.
scoped_refptr<network::ResourceRequestBody> post_data;
std::string header_list;
if (share_target.enctype == apps::ShareTarget::Enctype::kMultipartFormData) {
const std::string boundary = net::GenerateMimeMultipartBoundary();
header_list = base::StringPrintf(
nav_params.extra_headers = base::StringPrintf(
"Content-Type: multipart/form-data; boundary=%s\r\n", boundary.c_str());
post_data = web_share_target::ComputeMultipartBody(
nav_params.post_data = web_share_target::ComputeMultipartBody(
names, values, is_value_file_uris, filenames, types, boundary);
} else {
const std::string body =
const std::string serialization =
web_share_target::ComputeUrlEncodedBody(names, values);
header_list = "Content-Type: application/x-www-form-urlencoded\r\n";
post_data = network::ResourceRequestBody::CreateFromBytes(body.c_str(),
body.length());
if (share_target.method == apps::ShareTarget::Method::kPost) {
nav_params.extra_headers =
"Content-Type: application/x-www-form-urlencoded\r\n";
nav_params.post_data = network::ResourceRequestBody::CreateFromBytes(
serialization.c_str(), serialization.length());
} else {
url::Replacements<char> replacements;
replacements.SetQuery(serialization.c_str(),
url::Component(0, serialization.length()));
nav_params.url = nav_params.url.ReplaceComponents(replacements);
}
}
nav_params.post_data = post_data;
nav_params.extra_headers = header_list;
#else
// TODO(crbug.com/1153194): Support Web Share Target on Windows.
// TODO(crbug.com/1153195): Support Web Share Target on Mac.
......
......@@ -85,7 +85,8 @@ class WebShareTargetBrowserTest : public WebAppControllerBrowserTest {
}
content::WebContents* LaunchAppWithIntent(const AppId& app_id,
apps::mojom::IntentPtr&& intent) {
apps::mojom::IntentPtr&& intent,
const GURL& expected_url) {
apps::AppLaunchParams params = apps::CreateAppLaunchParamsForIntent(
app_id,
/*event_flags=*/0, apps::mojom::AppLaunchSource::kSourceAppLauncher,
......@@ -94,13 +95,14 @@ class WebShareTargetBrowserTest : public WebAppControllerBrowserTest {
std::move(intent));
ui_test_utils::UrlLoadObserver url_observer(
share_target_url(), content::NotificationService::AllSources());
expected_url, content::NotificationService::AllSources());
content::WebContents* const web_contents =
GetAppServiceProxy(profile())
->BrowserAppLauncher()
->LaunchAppWithParams(std::move(params));
DCHECK(web_contents);
url_observer.Wait();
EXPECT_EQ(expected_url, web_contents->GetVisibleURL());
return web_contents;
}
};
......@@ -126,7 +128,7 @@ IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareTextFiles) {
}
content::WebContents* const web_contents =
LaunchAppWithIntent(app_id, std::move(intent));
LaunchAppWithIntent(app_id, std::move(intent), share_target_url());
EXPECT_EQ("1,2,3,4,5 6,7,8,9,0", ReadTextContent(web_contents, "records"));
RemoveWebShareDirectory(directory);
......@@ -153,7 +155,7 @@ IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareImageWithText) {
}
content::WebContents* const web_contents =
LaunchAppWithIntent(app_id, std::move(intent));
LaunchAppWithIntent(app_id, std::move(intent), share_target_url());
EXPECT_EQ("picture", ReadTextContent(web_contents, "graphs"));
EXPECT_EQ("Elements", ReadTextContent(web_contents, "headline"));
......@@ -187,7 +189,7 @@ IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareAudio) {
}
content::WebContents* const web_contents =
LaunchAppWithIntent(app_id, std::move(intent));
LaunchAppWithIntent(app_id, std::move(intent), share_target_url());
EXPECT_EQ("a b c", ReadTextContent(web_contents, "notes"));
RemoveWebShareDirectory(directory);
......@@ -215,7 +217,7 @@ IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, PostLink) {
/*share_title=*/shared_title);
content::WebContents* const web_contents =
LaunchAppWithIntent(app_id, std::move(intent));
LaunchAppWithIntent(app_id, std::move(intent), share_target_url());
EXPECT_EQ("POST", ReadTextContent(web_contents, "method"));
EXPECT_EQ("application/x-www-form-urlencoded",
ReadTextContent(web_contents, "type"));
......@@ -226,4 +228,38 @@ IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, PostLink) {
EXPECT_EQ(shared_link, ReadTextContent(web_contents, "link"));
}
IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, GetLink) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL app_url =
embedded_test_server()->GetURL("/web_share_target/gatherer.html");
const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url);
const apps::ShareTarget* share_target =
WebAppProvider::Get(browser()->profile())
->registrar()
.GetAppShareTarget(app_id);
EXPECT_EQ(share_target->method, apps::ShareTarget::Method::kGet);
EXPECT_EQ(share_target->enctype, apps::ShareTarget::Enctype::kFormUrlEncoded);
const std::string shared_title = "My News";
const std::string shared_link = "http://example.com/news";
const GURL expected_url(share_target_url().spec() +
"?headline=My+News&link=http://example.com/news");
apps::mojom::IntentPtr intent = apps_util::CreateShareIntentFromFiles(
profile(), /*file_paths=*/std::vector<base::FilePath>(),
/*mime_types=*/std::vector<std::string>(),
/*share_text=*/shared_link,
/*share_title=*/shared_title);
content::WebContents* const web_contents =
LaunchAppWithIntent(app_id, std::move(intent), expected_url);
EXPECT_EQ("GET", ReadTextContent(web_contents, "method"));
EXPECT_EQ(expected_url.spec(), ReadTextContent(web_contents, "url"));
EXPECT_EQ(shared_title, ReadTextContent(web_contents, "headline"));
// Gatherer web app's service worker detects omitted value.
EXPECT_EQ("N/A", ReadTextContent(web_contents, "author"));
EXPECT_EQ(shared_link, ReadTextContent(web_contents, "link"));
}
} // namespace web_app
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="manifest" href="gatherer.json">
<link rel="icon" href="basic-48.png">
</head>
<body>
<h1>Gatherer web app</h1>
<script>
window.onload = () => {
navigator.serviceWorker.register('/web_share_target/service_worker.js');
};
</script>
</body>
</html>
{
"name": "Gatherer web app",
"icons": [
{
"src": "basic-48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "basic-192.png",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "gatherer.html",
"display": "fullscreen",
"share_target": {
"action": "/web_share_target/share.html",
"method": "GET",
"params": {
"title": "headline",
"text": "author",
"url": "link"
}
}
}
......@@ -26,6 +26,7 @@ function respondToShare(event) {
body = body.replace('{{method}}', event.request.method)
.replace('{{type}}', event.request.headers.get('Content-Type'))
.replace('{{url}}', event.request.url)
.replace('{{headline}}', readField(formData, 'headline', 'N/A'))
.replace('{{author}}', readField(formData, 'author', 'N/A'))
.replace('{{link}}', readField(formData, 'link', 'N/A'));
......
......@@ -6,6 +6,7 @@
<body>
<section id="method">{{method}}</section>
<section id="type">{{type}}</section>
<section id="url">{{url}}</section>
<section id="headline">{{headline}}</section>
<section id="author">{{author}}</section>
......
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