blob: 6345ce8baf33e880c8838600952f474bbb9023f7 [file] [log] [blame]
// 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/callback_helpers.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/no_destructor.h"
#include "base/rand_util.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "chrome/browser/apps/app_service/intent_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sharesheet/sharesheet_metrics.h"
#include "chrome/browser/sharesheet/sharesheet_service.h"
#include "chrome/browser/sharesheet/sharesheet_service_factory.h"
#include "chrome/browser/visibility_timer_tab_helper.h"
#include "chrome/browser/webshare/prepare_directory_task.h"
#include "chrome/browser/webshare/share_service_impl.h"
#include "chrome/browser/webshare/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/web_contents.h"
#include "net/base/filename_util.h"
using content::BrowserThread;
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);
}
blink::mojom::ShareError SharesheetResultToShareError(
sharesheet::SharesheetResult result) {
switch (result) {
case sharesheet::SharesheetResult::kSuccess:
return blink::mojom::ShareError::OK;
case sharesheet::SharesheetResult::kCancel:
case sharesheet::SharesheetResult::kErrorAlreadyOpen:
return blink::mojom::ShareError::CANCELED;
}
}
} // 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()) {
VLOG(1) << "Cannot share when an existing share is in progress, or after "
"navigating away";
std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED);
return;
}
Profile* const profile =
Profile::FromBrowserContext(web_contents()->GetBrowserContext());
DCHECK(profile);
// File sharing is denied in incognito, as files are written to disk.
// To prevent sites from using that to detect whether incognito mode is
// active, we deny after a random time delay, to simulate a user cancelling
// the share.
if (profile->IsIncognitoProfile() && !files.empty()) {
// Random number of seconds in the range [1.0, 2.0).
double delay_seconds = 1.0 + 1.0 * base::RandDouble();
VisibilityTimerTabHelper::CreateForWebContents(web_contents());
VisibilityTimerTabHelper::FromWebContents(web_contents())
->PostTaskAfterVisibleDelay(
FROM_HERE,
base::BindOnce(std::move(callback),
blink::mojom::ShareError::CANCELED),
base::Seconds(delay_seconds));
return;
}
current_share_ = CurrentShare();
current_share_->files = std::move(files);
current_share_->directory =
file_manager::util::GetMyFilesFolderForProfile(profile).Append(
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);
if (current_share_->files.empty()) {
GetSharesheetCallback().Run(
web_contents(), current_share_->file_paths,
current_share_->content_types, current_share_->file_sizes,
current_share_->text, current_share_->title,
base::BindOnce(&SharesheetClient::OnShowSharesheet,
weak_ptr_factory_.GetWeakPtr()));
return;
}
current_share_->prepare_directory_task =
std::make_unique<PrepareDirectoryTask>(
current_share_->directory, kMaxSharedFileBytes,
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 (!current_share_.has_value())
return;
if (!web_contents() || error != blink::mojom::ShareError::OK) {
std::move(current_share_->callback).Run(error);
current_share_ = absl::nullopt;
return;
}
for (const auto& file : current_share_->files) {
current_share_->content_types.push_back(file->blob->content_type);
current_share_->file_paths.push_back(
GenerateFileName(current_share_->directory, file->name));
current_share_->file_sizes.push_back(file->blob->size);
}
std::unique_ptr<StoreFilesTask> store_files_task =
std::make_unique<StoreFilesTask>(
current_share_->file_paths, std::move(current_share_->files),
kMaxSharedFileBytes,
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 (!current_share_.has_value())
return;
if (!web_contents() || error != blink::mojom::ShareError::OK) {
std::move(current_share_->callback).Run(error);
PrepareDirectoryTask::ScheduleSharedFileDeletion(
std::move(current_share_->file_paths), base::Minutes(0));
current_share_ = absl::nullopt;
return;
}
GetSharesheetCallback().Run(
web_contents(), current_share_->file_paths, current_share_->content_types,
current_share_->file_sizes, current_share_->text, current_share_->title,
base::BindOnce(&SharesheetClient::OnShowSharesheet,
weak_ptr_factory_.GetWeakPtr()));
}
void SharesheetClient::OnShowSharesheet(sharesheet::SharesheetResult result) {
if (!current_share_.has_value())
return;
std::move(current_share_->callback).Run(SharesheetResultToShareError(result));
PrepareDirectoryTask::ScheduleSharedFileDeletion(
std::move(current_share_->file_paths),
PrepareDirectoryTask::kSharedFileLifetime);
current_share_ = absl::nullopt;
}
// static
void SharesheetClient::ShowSharesheet(
content::WebContents* web_contents,
const std::vector<base::FilePath>& file_paths,
const std::vector<std::string>& content_types,
const std::vector<uint64_t>& file_sizes,
const std::string& text,
const std::string& title,
DeliveredCallback delivered_callback) {
DCHECK_EQ(file_paths.size(), content_types.size());
DCHECK_EQ(file_paths.size(), file_sizes.size());
Profile* const profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
DCHECK(profile);
sharesheet::SharesheetService* const sharesheet_service =
sharesheet::SharesheetServiceFactory::GetForProfile(profile);
apps::mojom::IntentPtr intent =
file_paths.empty() ? apps_util::CreateShareIntentFromText(text, title)
: apps_util::CreateShareIntentFromFiles(
profile, file_paths, content_types, text, title);
if (intent->files.has_value() && intent->files->size() == file_paths.size()) {
for (size_t index = 0; index < file_paths.size(); ++index) {
(*intent->files)[index]->mime_type = content_types[index];
(*intent->files)[index]->file_size = file_sizes[index];
}
}
sharesheet_service->ShowBubble(
web_contents, std::move(intent),
sharesheet::SharesheetMetrics::LaunchSource::kWebShare,
std::move(delivered_callback));
}
SharesheetClient::SharesheetCallback&
SharesheetClient::GetSharesheetCallback() {
static base::NoDestructor<SharesheetCallback> callback(
base::BindRepeating(&SharesheetClient::ShowSharesheet));
return *callback;
}
void SharesheetClient::WebContentsDestroyed() {
current_share_ = absl::nullopt;
}
} // namespace webshare