Commit a88b5166 authored by Tsuyoshi Horo's avatar Tsuyoshi Horo Committed by Commit Bot

Add WebBundle type in Save Page dialog

This CL adds a new item “Webpage, Single File (Web Bundle)” in the
“Save as type” select box of “Save As” dialog box for Desktop when
SavePageAsWebBundle feature flag is set using --enable-features command
line flag.

The logic of Web Bundle generation in WebBundler is not implemented yet.
WebBundler just returns WebBundlerError::kNotImplemented error.
So the request of save as WebBundle is just canceled.

Bug: 1040752
Change-Id: Iacb8a66e2fa8bc6b630d200a54bacd64a723eb0c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2053745Reviewed-by: default avatarMin Qin <qinmin@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Commit-Queue: Tsuyoshi Horo <horo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#750479}
parent cb0c1fdf
......@@ -7767,6 +7767,9 @@ the Bookmarks menu.">
<message name="IDS_SAVE_PAGE_DESC_COMPLETE" desc="In the Save Page dialog, the description of saving both the HTML and all shown resources.">
Webpage, Complete
</message>
<message name="IDS_SAVE_PAGE_DESC_WEB_BUNDLE_FILE" desc="In the Save Page dialog, the description of saving both the HTML and all shown resources into a single Web Bundle file.">
Webpage, Single File (Web Bundle)
</message>
<!-- General app failure messages -->
<message name="IDS_PROFILE_ERROR_DIALOG_TITLE" desc="The title of the dialog shown when there's an error page">
......
f900ca5539368f68460ce5caf3cb0221a8ef7c4f
\ No newline at end of file
......@@ -10,6 +10,7 @@
#include "base/bind.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/i18n/file_util_icu.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversions.h"
......@@ -30,6 +31,7 @@
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/save_page_type.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "ui/base/l10n/l10n_util.h"
using content::RenderProcessHost;
......@@ -58,18 +60,28 @@ void AddHtmlOnlyFileTypeInfo(
extensions.push_back(FILE_PATH_LITERAL("htm"));
if (!extra_extension.empty())
extensions.push_back(extra_extension);
file_type_info->extensions.push_back(extensions);
file_type_info->extensions.emplace_back(std::move(extensions));
}
// Adds "Web Archive, Single File" type to FileTypeInfo.
// Adds "Webpage, Single File" type to FileTypeInfo.
void AddSingleFileFileTypeInfo(
ui::SelectFileDialog::FileTypeInfo* file_type_info) {
file_type_info->extension_description_overrides.push_back(
l10n_util::GetStringUTF16(IDS_SAVE_PAGE_DESC_SINGLE_FILE));
std::vector<base::FilePath::StringType> extensions;
extensions.push_back(FILE_PATH_LITERAL("mhtml"));
file_type_info->extensions.push_back(extensions);
file_type_info->extensions.emplace_back(
std::initializer_list<base::FilePath::StringType>{
FILE_PATH_LITERAL("mhtml")});
}
// Adds "Webpage, Single File (Web Bundle)" type to FileTypeInfo.
void AddWebBundleFileFileTypeInfo(
ui::SelectFileDialog::FileTypeInfo* file_type_info) {
file_type_info->extension_description_overrides.push_back(
l10n_util::GetStringUTF16(IDS_SAVE_PAGE_DESC_WEB_BUNDLE_FILE));
file_type_info->extensions.emplace_back(
std::initializer_list<base::FilePath::StringType>{
FILE_PATH_LITERAL("wbn")});
}
// Chrome OS doesn't support HTML-Complete. crbug.com/154823
......@@ -172,6 +184,11 @@ SavePackageFilePicker::SavePackageFilePicker(
if (can_save_as_complete_) {
AddSingleFileFileTypeInfo(&file_type_info);
save_types_.push_back(content::SAVE_PAGE_TYPE_AS_MHTML);
if (base::FeatureList::IsEnabled(features::kSavePageAsWebBundle)) {
AddWebBundleFileFileTypeInfo(&file_type_info);
save_types_.push_back(content::SAVE_PAGE_TYPE_AS_WEB_BUNDLE);
}
}
#if !defined(OS_CHROMEOS)
......
......@@ -129,6 +129,23 @@ void CancelSavePackage(base::WeakPtr<SavePackage> save_package,
save_package->Cancel(user_cancel, false);
}
const std::string GetMimeTypeForSaveType(SavePageType save_type) {
switch (save_type) {
case SAVE_PAGE_TYPE_AS_ONLY_HTML:
case SAVE_PAGE_TYPE_AS_COMPLETE_HTML:
return "text/html";
case SAVE_PAGE_TYPE_AS_MHTML:
return "multipart/related";
case SAVE_PAGE_TYPE_AS_WEB_BUNDLE:
return "application/webbundle";
case SAVE_PAGE_TYPE_UNKNOWN:
case SAVE_PAGE_TYPE_MAX:
NOTREACHED();
return "";
}
NOTREACHED();
}
} // namespace
const base::FilePath::CharType SavePackage::kDefaultHtmlExtension[] =
......@@ -162,7 +179,8 @@ SavePackage::SavePackage(WebContents* web_contents,
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK((save_type_ == SAVE_PAGE_TYPE_AS_ONLY_HTML) ||
(save_type_ == SAVE_PAGE_TYPE_AS_MHTML) ||
(save_type_ == SAVE_PAGE_TYPE_AS_COMPLETE_HTML))
(save_type_ == SAVE_PAGE_TYPE_AS_COMPLETE_HTML) ||
(save_type_ == SAVE_PAGE_TYPE_AS_WEB_BUNDLE))
<< save_type_;
DCHECK(!saved_main_file_path_.empty() &&
saved_main_file_path_.value().length() <= kMaxFilePathLength);
......@@ -273,9 +291,7 @@ bool SavePackage::Init(
RenderFrameHost* frame_host = web_contents()->GetMainFrame();
download_manager_->CreateSavePackageDownloadItem(
saved_main_file_path_, page_url_,
((save_type_ == SAVE_PAGE_TYPE_AS_MHTML) ? "multipart/related"
: "text/html"),
saved_main_file_path_, page_url_, GetMimeTypeForSaveType(save_type_),
frame_host->GetProcess()->GetID(), frame_host->GetRoutingID(),
base::BindOnce(&CancelSavePackage, AsWeakPtr()),
base::BindOnce(&SavePackage::InitWithDownloadItem, AsWeakPtr(),
......@@ -302,7 +318,11 @@ void SavePackage::InitWithDownloadItem(
MHTMLGenerationParams mhtml_generation_params(saved_main_file_path_);
web_contents()->GenerateMHTML(
mhtml_generation_params,
base::BindOnce(&SavePackage::OnMHTMLGenerated, this));
base::BindOnce(&SavePackage::OnMHTMLOrWebBundleGenerated, this));
} else if (save_type_ == SAVE_PAGE_TYPE_AS_WEB_BUNDLE) {
web_contents()->GenerateWebBundle(
saved_main_file_path_,
base::BindOnce(&SavePackage::OnWebBundleGenerated, this));
} else {
DCHECK_EQ(SAVE_PAGE_TYPE_AS_ONLY_HTML, save_type_);
wait_state_ = NET_FILES;
......@@ -318,7 +338,7 @@ void SavePackage::InitWithDownloadItem(
}
}
void SavePackage::OnMHTMLGenerated(int64_t size) {
void SavePackage::OnMHTMLOrWebBundleGenerated(int64_t size) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!download_)
return;
......@@ -339,6 +359,16 @@ void SavePackage::OnMHTMLGenerated(int64_t size) {
}
}
void SavePackage::OnWebBundleGenerated(
uint64_t size,
data_decoder::mojom::WebBundlerError error) {
if (error == data_decoder::mojom::WebBundlerError::kOK)
DCHECK_GT(size, 0ULL);
else
DCHECK_EQ(size, 0ULL);
OnMHTMLOrWebBundleGenerated(size);
}
// On POSIX, the length of |base_name| + |file_name_ext| is further
// restricted by NAME_MAX. The maximum allowed path looks like:
// '/path/to/save_dir' + '/' + NAME_MAX.
......@@ -706,7 +736,8 @@ void SavePackage::Finish() {
file_manager_, list_of_failed_save_item_ids));
if (download_) {
if (save_type_ != SAVE_PAGE_TYPE_AS_MHTML) {
if (save_type_ != SAVE_PAGE_TYPE_AS_MHTML &&
save_type_ != SAVE_PAGE_TYPE_AS_WEB_BUNDLE) {
CHECK_EQ(download_->GetState(), download::DownloadItem::IN_PROGRESS);
download_->DestinationUpdate(
all_save_items_count_, CurrentSpeed(),
......@@ -840,10 +871,11 @@ int64_t SavePackage::CurrentSpeed() const {
void SavePackage::DoSavingProcess() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (save_type_ != SAVE_PAGE_TYPE_AS_COMPLETE_HTML) {
// Save as HTML only or MHTML.
// Save as HTML only or MHTML, or Web Bundle.
DCHECK_EQ(NET_FILES, wait_state_);
DCHECK((save_type_ == SAVE_PAGE_TYPE_AS_ONLY_HTML) ||
(save_type_ == SAVE_PAGE_TYPE_AS_MHTML))
(save_type_ == SAVE_PAGE_TYPE_AS_MHTML) ||
(save_type_ == SAVE_PAGE_TYPE_AS_WEB_BUNDLE))
<< save_type_;
if (waiting_item_queue_.size()) {
DCHECK_EQ(all_save_items_count_, waiting_item_queue_.size());
......@@ -1327,7 +1359,9 @@ void SavePackage::OnPathPicked(
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK((type == SAVE_PAGE_TYPE_AS_ONLY_HTML) ||
(type == SAVE_PAGE_TYPE_AS_MHTML) ||
(type == SAVE_PAGE_TYPE_AS_COMPLETE_HTML)) << type;
(type == SAVE_PAGE_TYPE_AS_COMPLETE_HTML) ||
(type == SAVE_PAGE_TYPE_AS_WEB_BUNDLE))
<< type;
// Ensure the filename is safe.
saved_main_file_path_ = final_name;
// TODO(asanka): This call may block on IO and shouldn't be made
......
......@@ -30,6 +30,7 @@
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/referrer.h"
#include "net/base/net_errors.h"
#include "services/data_decoder/public/mojom/web_bundler.mojom.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "url/gurl.h"
......@@ -162,8 +163,13 @@ class CONTENT_EXPORT SavePackage
SavePackageDownloadCreatedCallback download_created_callback,
download::DownloadItemImpl* item);
// Callback for WebContents::GenerateMHTML().
void OnMHTMLGenerated(int64_t size);
// Callback for WebContents::GenerateMHTML() and
// WebContents::GenerateWebBundle().
void OnMHTMLOrWebBundleGenerated(int64_t size);
// Callback for WebContents::GenerateWebBundle().
void OnWebBundleGenerated(uint64_t size,
data_decoder::mojom::WebBundlerError error);
// Notes from Init() above applies here as well.
void InternalInit();
......
......@@ -20,6 +20,8 @@
namespace content {
namespace {
const char kTestFile[] = "/simple_page.html";
class TestShellDownloadManagerDelegate : public ShellDownloadManagerDelegate {
......@@ -54,8 +56,7 @@ class TestShellDownloadManagerDelegate : public ShellDownloadManagerDelegate {
class DownloadicidalObserver : public DownloadManager::Observer {
public:
explicit DownloadicidalObserver(bool remove_download,
base::OnceClosure after_closure)
DownloadicidalObserver(bool remove_download, base::OnceClosure after_closure)
: remove_download_(remove_download),
after_closure_(std::move(after_closure)) {}
void OnDownloadCreated(DownloadManager* manager,
......@@ -75,6 +76,64 @@ class DownloadicidalObserver : public DownloadManager::Observer {
base::OnceClosure after_closure_;
};
class DownloadCancelObserver : public DownloadManager::Observer {
public:
explicit DownloadCancelObserver(base::OnceClosure canceled_closure,
std::string* mime_type_out)
: canceled_closure_(std::move(canceled_closure)),
mime_type_out_(mime_type_out) {}
DownloadCancelObserver(const DownloadCancelObserver&) = delete;
DownloadCancelObserver& operator=(const DownloadCancelObserver&) = delete;
void OnDownloadCreated(DownloadManager* manager,
download::DownloadItem* item) override {
*mime_type_out_ = item->GetMimeType();
DCHECK(!item_cancel_observer_);
item_cancel_observer_ = std::make_unique<DownloadItemCancelObserver>(
item, std::move(canceled_closure_));
}
private:
class DownloadItemCancelObserver : public download::DownloadItem::Observer {
public:
DownloadItemCancelObserver(download::DownloadItem* item,
base::OnceClosure canceled_closure)
: item_(item), canceled_closure_(std::move(canceled_closure)) {
item_->AddObserver(this);
}
DownloadItemCancelObserver(const DownloadItemCancelObserver&) = delete;
DownloadItemCancelObserver& operator=(const DownloadItemCancelObserver&) =
delete;
~DownloadItemCancelObserver() override {
if (item_)
item_->RemoveObserver(this);
}
private:
void OnDownloadUpdated(download::DownloadItem* item) override {
DCHECK_EQ(item_, item);
if (item_->GetState() == download::DownloadItem::CANCELLED)
std::move(canceled_closure_).Run();
}
void OnDownloadDestroyed(download::DownloadItem* item) override {
DCHECK_EQ(item_, item);
item_->RemoveObserver(this);
item_ = nullptr;
}
download::DownloadItem* item_;
base::OnceClosure canceled_closure_;
};
std::unique_ptr<DownloadItemCancelObserver> item_cancel_observer_;
base::OnceClosure canceled_closure_;
std::string* mime_type_out_;
};
} // namespace
class SavePackageBrowserTest : public ContentBrowserTest {
protected:
void SetUp() override {
......@@ -174,4 +233,40 @@ IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest, DownloadItemCanceled) {
RunAndCancelSavePackageDownload(SAVE_PAGE_TYPE_AS_MHTML, false);
}
// Currently, SavePageAsWebBundle feature is not implemented yet.
// WebContentsImpl::GenerateWebBundle() will call the passed callback with 0
// file size and WebBundlerError::kNotImplemented via WebBundler in the utility
// process which means it cancels all SavePageAsWebBundle requests. So this test
// checks that the request is successfully canceled.
// TODO(crbug.com/1040752): Implement WebBundler and update this test.
IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest, SaveAsWebBundleCanceled) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL("/page_with_iframe.html");
EXPECT_TRUE(NavigateToURL(shell(), url));
auto* download_manager =
static_cast<DownloadManagerImpl*>(BrowserContext::GetDownloadManager(
shell()->web_contents()->GetBrowserContext()));
auto delegate = std::make_unique<TestShellDownloadManagerDelegate>(
SAVE_PAGE_TYPE_AS_WEB_BUNDLE);
delegate->download_dir_ = save_dir_.GetPath();
auto* old_delegate = download_manager->GetDelegate();
download_manager->SetDelegate(delegate.get());
{
base::RunLoop run_loop;
std::string mime_type;
DownloadCancelObserver observer(run_loop.QuitClosure(), &mime_type);
download_manager->AddObserver(&observer);
scoped_refptr<SavePackage> save_package(
new SavePackage(shell()->web_contents()));
save_package->GetSaveInfo();
run_loop.Run();
download_manager->RemoveObserver(&observer);
EXPECT_TRUE(save_package->canceled());
EXPECT_EQ("application/webbundle", mime_type);
}
download_manager->SetDelegate(old_delegate);
}
} // namespace content
......@@ -16,11 +16,12 @@ enum SavePageType {
SAVE_PAGE_TYPE_AS_COMPLETE_HTML = 1,
// User chose to save complete-html page as MHTML.
SAVE_PAGE_TYPE_AS_MHTML = 2,
// User chose to save complete-html page as Web Bundle.
SAVE_PAGE_TYPE_AS_WEB_BUNDLE = 3,
// Insert new values BEFORE this value.
SAVE_PAGE_TYPE_MAX,
};
}
#endif // CONTENT_PUBLIC_BROWSER_SAVE_PAGE_TYPE_H_
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