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

Web Share Target: ChromeOS support for receiving share requests

We generate a POST request from the share intent.

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


On Chrome OS, only one file type may be shared at a time.

Note that sharing title/text/url is not yet supported.



Bug: 1125880
Change-Id: I4aa2a0b887bbb208bf33fdef849b970d72bf1e5e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2560080
Auto-Submit: Eric Willigers <ericwilligers@chromium.org>
Reviewed-by: default avatarNancy Wang <nancylingwang@chromium.org>
Reviewed-by: default avatarGlen Robertson <glenrob@chromium.org>
Commit-Queue: Eric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#834114}
parent 4cad1e9f
...@@ -186,7 +186,8 @@ IN_PROC_BROWSER_TEST_F(WebAppsBaseBrowserTest, PopulateIntentFilters) { ...@@ -186,7 +186,8 @@ IN_PROC_BROWSER_TEST_F(WebAppsBaseBrowserTest, PopulateIntentFilters) {
CheckUrlScopeFilter(target[0], app_url.GetWithoutFilename(), CheckUrlScopeFilter(target[0], app_url.GetWithoutFilename(),
/*different_url=*/GURL("file:///")); /*different_url=*/GURL("file:///"));
const std::vector<std::string> content_types({"text/csv", "image/svg+xml"}); const std::vector<std::string> content_types(
{"text/*", "image/svg+xml", "*/*"});
CheckShareFileFilter( CheckShareFileFilter(
target[1], content_types, target[1], content_types,
/*different_content_type=*/"application/vnd.android.package-archive"); /*different_content_type=*/"application/vnd.android.package-archive");
......
...@@ -4365,6 +4365,7 @@ static_library("ui") { ...@@ -4365,6 +4365,7 @@ static_library("ui") {
"//chrome/browser/apps/platform_apps", # TODO(loyso): Remove this dep. "//chrome/browser/apps/platform_apps", # TODO(loyso): Remove this dep.
"//chrome/browser/apps/platform_apps/api", "//chrome/browser/apps/platform_apps/api",
"//chrome/browser/extensions", "//chrome/browser/extensions",
"//chrome/browser/web_share_target",
"//chrome/common/extensions/api", "//chrome/common/extensions/api",
"//components/drive", "//components/drive",
"//components/guest_view/browser", "//components/guest_view/browser",
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h" #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/browser_app_launcher.h" #include "chrome/browser/apps/app_service/browser_app_launcher.h"
#include "chrome/browser/installable/installable_metrics.h" #include "chrome/browser/installable/installable_metrics.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_list.h"
...@@ -34,6 +35,7 @@ ...@@ -34,6 +35,7 @@
#include "chrome/browser/web_applications/components/web_app_provider_base.h" #include "chrome/browser/web_applications/components/web_app_provider_base.h"
#include "chrome/browser/web_applications/components/web_app_tab_helper_base.h" #include "chrome/browser/web_applications/components/web_app_tab_helper_base.h"
#include "chrome/browser/web_applications/components/web_application_info.h" #include "chrome/browser/web_applications/components/web_application_info.h"
#include "chrome/browser/web_applications/test/service_worker_registration_waiter.h"
#include "chrome/browser/web_applications/web_app_provider.h" #include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/test/base/ui_test_utils.h" #include "chrome/test/base/ui_test_utils.h"
#include "components/security_interstitials/content/security_interstitial_tab_helper.h" #include "components/security_interstitials/content/security_interstitial_tab_helper.h"
...@@ -113,7 +115,10 @@ AppId InstallWebApp(Profile* profile, ...@@ -113,7 +115,10 @@ AppId InstallWebApp(Profile* profile,
} }
AppId InstallWebAppFromManifest(Browser* browser, const GURL& app_url) { AppId InstallWebAppFromManifest(Browser* browser, const GURL& app_url) {
ServiceWorkerRegistrationWaiter registration_waiter(browser->profile(),
app_url);
NavigateToURLAndWait(browser, app_url); NavigateToURLAndWait(browser, app_url);
registration_waiter.AwaitRegistration();
AppId app_id; AppId app_id;
base::RunLoop run_loop; base::RunLoop run_loop;
...@@ -123,7 +128,7 @@ AppId InstallWebAppFromManifest(Browser* browser, const GURL& app_url) { ...@@ -123,7 +128,7 @@ AppId InstallWebAppFromManifest(Browser* browser, const GURL& app_url) {
WaitUntilReady(provider); WaitUntilReady(provider);
provider->install_manager().InstallWebAppFromManifestWithFallback( provider->install_manager().InstallWebAppFromManifestWithFallback(
browser->tab_strip_model()->GetActiveWebContents(), browser->tab_strip_model()->GetActiveWebContents(),
/*force_shortcut_app=*/true, WebappInstallSource::MENU_BROWSER_TAB, /*force_shortcut_app=*/false, WebappInstallSource::MENU_BROWSER_TAB,
base::BindLambdaForTesting( base::BindLambdaForTesting(
[](content::WebContents* initiator_web_contents, [](content::WebContents* initiator_web_contents,
std::unique_ptr<WebApplicationInfo> web_app_info, std::unique_ptr<WebApplicationInfo> web_app_info,
......
...@@ -6,11 +6,17 @@ ...@@ -6,11 +6,17 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector>
#include "base/command_line.h" #include "base/command_line.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
#include "base/ranges/algorithm.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/app_mode/app_mode_utils.h" #include "chrome/browser/app_mode/app_mode_utils.h"
#include "chrome/browser/apps/app_service/app_launch_params.h" #include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/apps/app_service/launch_utils.h" #include "chrome/browser/apps/app_service/launch_utils.h"
...@@ -37,6 +43,7 @@ ...@@ -37,6 +43,7 @@
#include "chrome/browser/web_applications/web_app_registrar.h" #include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_tab_helper.h" #include "chrome/browser/web_applications/web_app_tab_helper.h"
#include "chrome/browser/web_launch/web_launch_files_helper.h" #include "chrome/browser/web_launch/web_launch_files_helper.h"
#include "chrome/browser/web_share_target/target_util.h"
#include "chrome/common/chrome_features.h" #include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_switches.h"
#include "content/public/browser/page_navigator.h" #include "content/public/browser/page_navigator.h"
...@@ -44,12 +51,20 @@ ...@@ -44,12 +51,20 @@
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/common/referrer.h" #include "content/public/common/referrer.h"
#include "extensions/common/constants.h" #include "extensions/common/constants.h"
#include "net/base/mime_util.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_url.h"
#include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/features.h"
#include "ui/base/page_transition_types.h" #include "ui/base/page_transition_types.h"
#include "ui/base/window_open_disposition.h" #include "ui/base/window_open_disposition.h"
#include "ui/display/scoped_display_for_new_windows.h" #include "ui/display/scoped_display_for_new_windows.h"
#include "url/gurl.h" #include "url/gurl.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
#endif
namespace web_app { namespace web_app {
namespace { namespace {
...@@ -71,6 +86,106 @@ void SetTabHelperAppId(content::WebContents* web_contents, ...@@ -71,6 +86,106 @@ void SetTabHelperAppId(content::WebContents* web_contents,
tab_helper->SetAppId(app_id); tab_helper->SetAppId(app_id);
} }
content::WebContents* NavigateWebAppUsingParams(const std::string& app_id,
NavigateParams& nav_params) {
Navigate(&nav_params);
content::WebContents* const web_contents =
nav_params.navigated_or_inserted_contents;
SetTabHelperAppId(web_contents, app_id);
web_app::SetAppPrefsForWebContents(web_contents);
return web_contents;
}
NavigateParams NavigateParamsForShareTarget(
Browser* browser,
const apps::ShareTarget& share_target,
const apps::mojom::Intent& intent) {
NavigateParams nav_params(browser, share_target.action,
ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
#if defined(OS_CHROMEOS)
std::vector<std::string> names;
std::vector<std::string> values;
std::vector<std::string> filenames;
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 (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;
}
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);
}
}
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);
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.
NOTIMPLEMENTED();
#endif
return nav_params;
}
GURL GetLaunchUrl(WebAppProvider& provider,
const apps::AppLaunchParams& params,
const apps::ShareTarget* share_target) {
if (!params.override_url.is_empty())
return params.override_url;
const GURL app_url =
share_target ? share_target->action
: provider.registrar().GetAppLaunchUrl(params.app_id);
return provider.os_integration_manager()
.GetMatchingFileHandlerURL(params.app_id, params.launch_files)
.value_or(app_url);
}
} // namespace } // namespace
Browser* CreateWebApplicationWindow(Profile* profile, Browser* CreateWebApplicationWindow(Profile* profile,
...@@ -99,15 +214,7 @@ content::WebContents* NavigateWebApplicationWindow( ...@@ -99,15 +214,7 @@ content::WebContents* NavigateWebApplicationWindow(
WindowOpenDisposition disposition) { WindowOpenDisposition disposition) {
NavigateParams nav_params(browser, url, ui::PAGE_TRANSITION_AUTO_BOOKMARK); NavigateParams nav_params(browser, url, ui::PAGE_TRANSITION_AUTO_BOOKMARK);
nav_params.disposition = disposition; nav_params.disposition = disposition;
Navigate(&nav_params); return NavigateWebAppUsingParams(app_id, nav_params);
content::WebContents* const web_contents =
nav_params.navigated_or_inserted_contents;
SetTabHelperAppId(web_contents, app_id);
web_app::SetAppPrefsForWebContents(web_contents);
return web_contents;
} }
WebAppLaunchManager::WebAppLaunchManager(Profile* profile) WebAppLaunchManager::WebAppLaunchManager(Profile* profile)
...@@ -126,15 +233,13 @@ content::WebContents* WebAppLaunchManager::OpenApplication( ...@@ -126,15 +233,13 @@ content::WebContents* WebAppLaunchManager::OpenApplication(
if (GetOpenApplicationCallback()) if (GetOpenApplicationCallback())
return GetOpenApplicationCallback().Run(std::move(params)); return GetOpenApplicationCallback().Run(std::move(params));
web_app::OsIntegrationManager& os_integration_manager = // TODO(crbug.com/1125880): Support Web Share Target with no files shared.
provider_->os_integration_manager(); const apps::ShareTarget* const share_target =
(params.intent && params.intent->mime_type.has_value() &&
const GURL url = params.intent->file_urls.has_value())
params.override_url.is_empty() ? provider_->registrar().GetAppShareTarget(params.app_id)
? os_integration_manager : nullptr;
.GetMatchingFileHandlerURL(params.app_id, params.launch_files) const GURL url = GetLaunchUrl(*provider_, params, share_target);
.value_or(provider_->registrar().GetAppLaunchUrl(params.app_id))
: params.override_url;
// Place new windows on the specified display. // Place new windows on the specified display.
display::ScopedDisplayForNewWindows scoped_display(params.display_id); display::ScopedDisplayForNewWindows scoped_display(params.display_id);
...@@ -181,7 +286,12 @@ content::WebContents* WebAppLaunchManager::OpenApplication( ...@@ -181,7 +286,12 @@ content::WebContents* WebAppLaunchManager::OpenApplication(
} }
content::WebContents* web_contents; content::WebContents* web_contents;
if (disposition == WindowOpenDisposition::CURRENT_TAB) { if (share_target) {
NavigateParams nav_params =
NavigateParamsForShareTarget(browser, *share_target, *params.intent);
nav_params.disposition = disposition;
web_contents = NavigateWebAppUsingParams(params.app_id, nav_params);
} else if (disposition == WindowOpenDisposition::CURRENT_TAB) {
TabStripModel* const model = browser->tab_strip_model(); TabStripModel* const model = browser->tab_strip_model();
content::WebContents* existing_tab = model->GetActiveWebContents(); content::WebContents* existing_tab = model->GetActiveWebContents();
const int tab_index = model->GetIndexOfWebContents(existing_tab); const int tab_index = model->GetIndexOfWebContents(existing_tab);
...@@ -204,6 +314,8 @@ content::WebContents* WebAppLaunchManager::OpenApplication( ...@@ -204,6 +314,8 @@ content::WebContents* WebAppLaunchManager::OpenApplication(
browser, params.app_id, url, WindowOpenDisposition::NEW_FOREGROUND_TAB); browser, params.app_id, url, WindowOpenDisposition::NEW_FOREGROUND_TAB);
} }
web_app::OsIntegrationManager& os_integration_manager =
provider_->os_integration_manager();
if (os_integration_manager.IsFileHandlingAPIAvailable(params.app_id)) { if (os_integration_manager.IsFileHandlingAPIAvailable(params.app_id)) {
web_launch::WebLaunchFilesHelper::SetLaunchPaths(web_contents, url, web_launch::WebLaunchFilesHelper::SetLaunchPaths(web_contents, url,
params.launch_files); params.launch_files);
......
// 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 <string>
#include <vector>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/browser_app_launcher.h"
#include "chrome/browser/apps/app_service/intent_util.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#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/test/base/ui_test_utils.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 "ui/display/types/display_constants.h"
#include "url/gurl.h"
namespace {
apps::AppServiceProxy* GetAppServiceProxy(Profile* profile) {
DCHECK(
apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile));
return apps::AppServiceProxyFactory::GetForProfile(profile);
}
base::FilePath PrepareWebShareDirectory(Profile* profile) {
constexpr base::FilePath::CharType kWebShareDirname[] =
FILE_PATH_LITERAL(".WebShare");
const base::FilePath directory =
file_manager::util::GetMyFilesFolderForProfile(profile).Append(
kWebShareDirname);
base::ScopedAllowBlockingForTesting allow_blocking;
base::File::Error result = base::File::FILE_OK;
EXPECT_TRUE(base::CreateDirectoryAndGetError(directory, &result));
return directory;
}
void RemoveWebShareDirectory(const base::FilePath& directory) {
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::DeletePathRecursively(directory));
}
base::FilePath StoreSharedFile(const base::FilePath& directory,
const base::StringPiece& name,
const base::StringPiece& content) {
const base::FilePath path = directory.Append(name);
base::ScopedAllowBlockingForTesting allow_blocking;
base::File file(path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
EXPECT_EQ(file.WriteAtCurrentPos(content.begin(), content.size()),
static_cast<int>(content.size()));
return path;
}
} // namespace
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");
}
content::WebContents* LaunchAppWithIntent(const AppId& app_id,
apps::mojom::IntentPtr&& intent) {
apps::AppLaunchParams params = apps::CreateAppLaunchParamsForIntent(
app_id,
/*event_flags=*/0, apps::mojom::AppLaunchSource::kSourceAppLauncher,
display::kDefaultDisplayId,
apps::mojom::LaunchContainer::kLaunchContainerWindow,
std::move(intent));
ui_test_utils::UrlLoadObserver url_observer(
share_target_url(), content::NotificationService::AllSources());
content::WebContents* const web_contents =
GetAppServiceProxy(profile())
->BrowserAppLauncher()
->LaunchAppWithParams(std::move(params));
DCHECK(web_contents);
url_observer.Wait();
return web_contents;
}
};
IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareText) {
ASSERT_TRUE(embedded_test_server()->Start());
const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url());
const base::FilePath directory = PrepareWebShareDirectory(profile());
apps::mojom::IntentPtr intent;
{
const base::FilePath first_csv =
StoreSharedFile(directory, "first.csv", "1,2,3,4,5");
const base::FilePath second_csv =
StoreSharedFile(directory, "second.csv", "6,7,8,9,0");
std::vector<base::FilePath> file_paths({first_csv, second_csv});
std::vector<std::string> content_types(2, "text/csv");
intent = apps_util::CreateShareIntentFromFiles(
profile(), std::move(file_paths), std::move(content_types));
}
const std::string script = "document.getElementById('records').textContent";
content::WebContents* const web_contents =
LaunchAppWithIntent(app_id, std::move(intent));
const content::EvalJsResult result = content::EvalJs(web_contents, script);
EXPECT_EQ("1,2,3,4,5 6,7,8,9,0", result);
RemoveWebShareDirectory(directory);
}
IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareImage) {
ASSERT_TRUE(embedded_test_server()->Start());
const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url());
const base::FilePath directory = PrepareWebShareDirectory(profile());
apps::mojom::IntentPtr intent;
{
const base::FilePath first_svg =
StoreSharedFile(directory, "first.svg", "picture");
std::vector<base::FilePath> file_paths({first_svg});
std::vector<std::string> content_types(1, "image/svg+xml");
intent = apps_util::CreateShareIntentFromFiles(
profile(), std::move(file_paths), std::move(content_types));
}
const std::string script = "document.getElementById('graphs').textContent";
content::WebContents* const web_contents =
LaunchAppWithIntent(app_id, std::move(intent));
const content::EvalJsResult result = content::EvalJs(web_contents, script);
EXPECT_EQ("picture", result);
RemoveWebShareDirectory(directory);
}
IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareAudio) {
ASSERT_TRUE(embedded_test_server()->Start());
const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url());
const base::FilePath directory = PrepareWebShareDirectory(profile());
apps::mojom::IntentPtr intent;
{
const base::FilePath first_weba =
StoreSharedFile(directory, "first.weba", "a");
const base::FilePath second_weba =
StoreSharedFile(directory, "second.weba", "b");
const base::FilePath third_weba =
StoreSharedFile(directory, "third.weba", "c");
std::vector<base::FilePath> file_paths(
{first_weba, second_weba, third_weba});
std::vector<std::string> content_types(3, "audio/webm");
intent = apps_util::CreateShareIntentFromFiles(
profile(), std::move(file_paths), std::move(content_types));
}
const std::string script = "document.getElementById('notes').textContent";
content::WebContents* const web_contents =
LaunchAppWithIntent(app_id, std::move(intent));
const content::EvalJsResult result = content::EvalJs(web_contents, script);
EXPECT_EQ("a b c", result);
RemoveWebShareDirectory(directory);
}
} // namespace web_app
...@@ -2818,6 +2818,7 @@ if (!is_android) { ...@@ -2818,6 +2818,7 @@ if (!is_android) {
"../browser/ui/views/supervised_user/parent_permission_dialog_view_browsertest.cc", "../browser/ui/views/supervised_user/parent_permission_dialog_view_browsertest.cc",
"../browser/ui/views/web_apps/web_app_ash_interactive_ui_test.cc", "../browser/ui/views/web_apps/web_app_ash_interactive_ui_test.cc",
"../browser/ui/web_applications/web_app_guest_session_browsertest_chromeos.cc", "../browser/ui/web_applications/web_app_guest_session_browsertest_chromeos.cc",
"../browser/ui/web_applications/web_share_target_browsertest.cc",
"../browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder_browsertest.cc", "../browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder_browsertest.cc",
"../browser/ui/webui/chromeos/add_supervision/add_supervision_ui_browsertest.cc", "../browser/ui/webui/chromeos/add_supervision/add_supervision_ui_browsertest.cc",
"../browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog_browsertest.cc", "../browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog_browsertest.cc",
......
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8">
<link rel="manifest" href="charts.json"> <link rel="manifest" href="charts.json">
<link rel="icon" href="basic-48.png"> <link rel="icon" href="basic-48.png">
</head> </head>
<body> <body>
<h1>Charts web app</h1> <h1>Charts web app</h1>
<script> <script>
window.onload = () => {
navigator.serviceWorker.register('/web_share_target/service_worker.js'); navigator.serviceWorker.register('/web_share_target/service_worker.js');
};
</script> </script>
</body> </body>
</html> </html>
...@@ -23,11 +23,15 @@ ...@@ -23,11 +23,15 @@
"files": [ "files": [
{ {
"name": "records", "name": "records",
"accept": ["text/csv", ".csv"] "accept": ["text/*", ".csv"]
}, },
{ {
"name": "graphs", "name": "graphs",
"accept": "image/svg+xml" "accept": "image/svg+xml"
},
{
"name": "notes",
"accept": "*/*"
} }
] ]
} }
......
...@@ -4,12 +4,78 @@ ...@@ -4,12 +4,78 @@
'use strict'; 'use strict';
self.addEventListener('fetch', e => { // Promise-based version of FileReader.readAsText.
e.respondWith((async () => { function readAsFilePromise(fileReader, blob, encoding) {
try { return new Promise(resolve => {
return await fetch(e.request); fileReader.onload = e => resolve(e.target.result);
} catch (error) { fileReader.readAsText(blob, encoding);
return new Response('Hello offline page'); });
}
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 init = {
status: 200,
statusText: 'OK',
headers: {'Content-Type': 'text/html'}
};
const file_fields = ['records', 'graphs', 'notes'];
let field_index = 0;
let files = undefined;
let file_contents = '';
let index = 0;
function prepareField() {
files = formData.getAll(
file_fields[field_index]); // sequence of File objects
file_contents = '';
index = 0;
}
prepareField();
async function progress() {
while (index === files.length) {
body = body.replace(
'{{' + file_fields[field_index] + '}}', file_contents);
++field_index;
if (field_index === file_fields.length) {
return new Response(body, init);
} }
prepareField();
}
const fileReader = new FileReader();
const dataFromFileLoaded =
await readAsFilePromise(fileReader, files[index], 'UTF-8');
if (index > 0) {
file_contents += ' ';
}
file_contents += dataFromFileLoaded;
index += 1;
return await progress();
}
return await progress();
})()); })());
}
self.addEventListener('activate', event => {
event.waitUntil(clients.claim());
});
self.addEventListener('fetch', event => {
const pathname = (new URL(event.request.url)).pathname;
if (pathname.endsWith('/share.html')) {
respondToShare(event);
} else {
event.respondWith(fetch(event.request));
}
}); });
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<link rel="icon" href="basic-48.png">
</head>
<body>
<section id="records">{{records}}</section>
<section id="graphs">{{graphs}}</section>
<section id="notes">{{notes}}</section>
</body>
</html>
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