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

Web Share Target for Chrome OS: Url-encoded post requests

We now support Web Share Targets with method POST and enctype
"application/x-www-form-urlencoded"

Note that enctype "multipart/form-data" is already supported.

Spec:
https://w3c.github.io/web-share-target/level-2/#dom-sharetarget-enctype

Not yet implemented:
- Receiving title/text/url using GET.
- Updating PopulateIntentFilters in web_apps_base.cc

Design doc:
https://docs.google.com/document/d/1E4CYASFDVNqmyCbaxa8u8sOn0-Sc1miLeZfA3t3ou5o/edit?usp=sharing

Bug: 1125880
Change-Id: I8fc5fe5dc008fddc6852f44572cf46325faa615f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2584625Reviewed-by: default avatarDaniel Murphy <dmurph@chromium.org>
Reviewed-by: default avatarAlan Cutter <alancutter@chromium.org>
Commit-Queue: Eric Willigers <ericwilligers@chromium.org>
Auto-Submit: Eric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#836508}
parent 7fbe70b2
......@@ -82,44 +82,42 @@ NavigateParams NavigateParamsForShareTarget(
std::vector<std::string> types;
std::vector<bool> is_value_file_uris;
DCHECK(intent.mime_type.has_value());
DCHECK(intent.file_urls.has_value());
const std::string& mime_type = intent.mime_type.value();
std::string name;
for (const apps::ShareTarget::Files& files : share_target.params.files) {
// Filter on MIME types. Chrome OS does not filter on file extensions.
// https://w3c.github.io/web-share-target/level-2/#dfn-accepted
if (base::ranges::any_of(files.accept, [&mime_type](const auto& criteria) {
return !base::StartsWith(criteria, ".") &&
net::MatchesMimeType(criteria, mime_type);
})) {
name = files.name;
break;
if (intent.mime_type.has_value() && intent.file_urls.has_value()) {
const std::string& mime_type = intent.mime_type.value();
std::string name;
for (const apps::ShareTarget::Files& files : share_target.params.files) {
// Filter on MIME types. Chrome OS does not filter on file extensions.
// https://w3c.github.io/web-share-target/level-2/#dfn-accepted
if (base::ranges::any_of(
files.accept, [&mime_type](const auto& criteria) {
return !base::StartsWith(criteria, ".") &&
net::MatchesMimeType(criteria, mime_type);
})) {
name = files.name;
break;
}
}
}
if (name.empty()) {
VLOG(1) << "Received unexpected MIME type: " << mime_type;
} else {
// Files for Web Share intents are created by the browser in
// a .WebShare directory, with generated file names and file urls - see
// //chrome/browser/webshare/chromeos/sharesheet_client.cc
for (const GURL& file_url : intent.file_urls.value()) {
storage::FileSystemContext* file_system_context =
file_manager::util::GetFileSystemContextForExtensionId(
browser->profile(), extension_misc::kFilesManagerAppId);
storage::FileSystemURL file_system_url =
file_system_context->CrackURL(file_url);
if (!file_system_url.is_valid()) {
VLOG(1) << "Received unexpected file URL: " << file_url.spec();
continue;
if (!name.empty()) {
// Files for Web Share intents are created by the browser in
// a .WebShare directory, with generated file names and file urls - see
// //chrome/browser/webshare/chromeos/sharesheet_client.cc
for (const GURL& file_url : intent.file_urls.value()) {
storage::FileSystemContext* file_system_context =
file_manager::util::GetFileSystemContextForExtensionId(
browser->profile(), extension_misc::kFilesManagerAppId);
storage::FileSystemURL file_system_url =
file_system_context->CrackURL(file_url);
if (!file_system_url.is_valid()) {
VLOG(1) << "Received unexpected file URL: " << file_url.spec();
continue;
}
names.push_back(name);
values.push_back(file_system_url.path().AsUTF8Unsafe());
filenames.push_back(file_system_url.path().BaseName().AsUTF8Unsafe());
types.push_back(mime_type);
is_value_file_uris.push_back(true);
}
names.push_back(name);
values.push_back(file_system_url.path().AsUTF8Unsafe());
filenames.push_back(file_system_url.path().BaseName().AsUTF8Unsafe());
types.push_back(mime_type);
is_value_file_uris.push_back(true);
}
}
......@@ -133,12 +131,22 @@ NavigateParams NavigateParamsForShareTarget(
is_value_file_uris.push_back(false);
}
const std::string boundary = net::GenerateMimeMultipartBoundary();
const std::string header_list = base::StringPrintf(
"Content-Type: multipart/form-data; boundary=%s\r\n", boundary.c_str());
scoped_refptr<network::ResourceRequestBody> post_data =
web_share_target::ComputeMultipartBody(names, values, is_value_file_uris,
filenames, types, boundary);
// 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(
"Content-Type: multipart/form-data; boundary=%s\r\n", boundary.c_str());
post_data = web_share_target::ComputeMultipartBody(
names, values, is_value_file_uris, filenames, types, boundary);
} else {
const std::string body =
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());
}
nav_params.post_data = post_data;
nav_params.extra_headers = header_list;
......
......@@ -145,12 +145,9 @@ content::WebContents* WebAppLaunchManager::OpenApplication(
if (GetOpenApplicationCallback())
return GetOpenApplicationCallback().Run(std::move(params));
// TODO(crbug.com/1125880): Support Web Share Target with no files shared.
const apps::ShareTarget* const share_target =
(params.intent && params.intent->mime_type.has_value() &&
params.intent->file_urls.has_value())
? provider_->registrar().GetAppShareTarget(params.app_id)
: nullptr;
params.intent ? provider_->registrar().GetAppShareTarget(params.app_id)
: nullptr;
const GURL url = GetLaunchUrl(*provider_, params, share_target);
// Place new windows on the specified display.
......
......@@ -18,10 +18,14 @@
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
#include "chrome/browser/web_applications/components/app_registrar.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/services/app_service/public/cpp/share_target.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "ui/display/types/display_constants.h"
#include "url/gurl.h"
......@@ -76,9 +80,6 @@ namespace web_app {
class WebShareTargetBrowserTest : public WebAppControllerBrowserTest {
public:
GURL app_url() const {
return embedded_test_server()->GetURL("/web_share_target/charts.html");
}
GURL share_target_url() const {
return embedded_test_server()->GetURL("/web_share_target/share.html");
}
......@@ -106,7 +107,9 @@ class WebShareTargetBrowserTest : public WebAppControllerBrowserTest {
IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareTextFiles) {
ASSERT_TRUE(embedded_test_server()->Start());
const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url());
const GURL app_url =
embedded_test_server()->GetURL("/web_share_target/charts.html");
const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url);
const base::FilePath directory = PrepareWebShareDirectory(profile());
apps::mojom::IntentPtr intent;
......@@ -131,7 +134,9 @@ IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareTextFiles) {
IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareImageWithText) {
ASSERT_TRUE(embedded_test_server()->Start());
const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url());
const GURL app_url =
embedded_test_server()->GetURL("/web_share_target/charts.html");
const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url);
const base::FilePath directory = PrepareWebShareDirectory(profile());
apps::mojom::IntentPtr intent;
......@@ -160,7 +165,9 @@ IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareImageWithText) {
IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareAudio) {
ASSERT_TRUE(embedded_test_server()->Start());
const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url());
const GURL app_url =
embedded_test_server()->GetURL("/web_share_target/charts.html");
const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url);
const base::FilePath directory = PrepareWebShareDirectory(profile());
apps::mojom::IntentPtr intent;
......@@ -186,4 +193,37 @@ IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareAudio) {
RemoveWebShareDirectory(directory);
}
IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, PostLink) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL app_url =
embedded_test_server()->GetURL("/web_share_target/poster.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::kPost);
EXPECT_EQ(share_target->enctype, apps::ShareTarget::Enctype::kFormUrlEncoded);
const std::string shared_title = "Hyperlink";
const std::string shared_link = "https://example.org/a?b=c&d=e%20#f";
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));
EXPECT_EQ("POST", ReadTextContent(web_contents, "method"));
EXPECT_EQ("application/x-www-form-urlencoded",
ReadTextContent(web_contents, "type"));
EXPECT_EQ(shared_title, ReadTextContent(web_contents, "headline"));
// Poster 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="poster.json">
<link rel="icon" href="basic-48.png">
</head>
<body>
<h1>Poster web app</h1>
<script>
window.onload = () => {
navigator.serviceWorker.register('/web_share_target/service_worker.js');
};
</script>
</body>
</html>
{
"name": "Poster web app",
"icons": [
{
"src": "basic-48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "basic-192.png",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "poster.html",
"display": "standalone",
"share_target": {
"action": "/web_share_target/share.html",
"method": "POST",
"params": {
"title": "headline",
"text": "author",
"url": "link"
}
}
}
......@@ -12,15 +12,23 @@ function readAsFilePromise(fileReader, blob, encoding) {
});
}
function readField(dictionary, name, defaultValue) {
return dictionary.get(name) || defaultValue;
}
function respondToShare(event) {
event.respondWith((async () => {
const template = await fetch('share.template.html');
let body = await template.text();
const formData = await event.request.formData();
const formData = (event.request.method === 'POST')
? await event.request.formData()
: (new URL(event.request.url)).searchParams;
body = body.replace('{{headline}}', formData.get('headline'))
.replace('{{author}}', formData.get('author'))
.replace('{{link}}', formData.get('link'));
body = body.replace('{{method}}', event.request.method)
.replace('{{type}}', event.request.headers.get('Content-Type'))
.replace('{{headline}}', readField(formData, 'headline', 'N/A'))
.replace('{{author}}', readField(formData, 'author', 'N/A'))
.replace('{{link}}', readField(formData, 'link', 'N/A'));
const init = {
status: 200,
......
......@@ -4,6 +4,9 @@
<link rel="icon" href="basic-48.png">
</head>
<body>
<section id="method">{{method}}</section>
<section id="type">{{type}}</section>
<section id="headline">{{headline}}</section>
<section id="author">{{author}}</section>
<section id="link">{{link}}</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