Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: modify file extension generation on Windows #34723

Merged
merged 12 commits into from Aug 2, 2022
132 changes: 132 additions & 0 deletions shell/browser/electron_download_manager_delegate.cc
Expand Up @@ -30,6 +30,14 @@
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/options_switches.h"

#if BUILDFLAG(IS_WIN)
#include "base/i18n/case_conversion.h"
#include "base/win/registry.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/shell_dialogs/execute_select_file_win.h"
#include "ui/strings/grit/ui_strings.h"
#endif // BUILDFLAG(IS_WIN)

namespace electron {

namespace {
Expand Down Expand Up @@ -62,6 +70,111 @@ base::FilePath CreateDownloadPath(const GURL& url,
return download_path.Append(generated_name);
}

#if BUILDFLAG(IS_WIN)
// Get the file type description from the registry. This will be "Text Document"
// for .txt files, "JPEG Image" for .jpg files, etc. If the registry doesn't
// have an entry for the file type, we return false, true if the description was
// found. 'file_ext' must be in form ".txt".
// Copied from ui/shell_dialogs/select_file_dialog_win.cc
mlaurencin marked this conversation as resolved.
Show resolved Hide resolved
bool GetRegistryDescriptionFromExtension(const std::u16string& file_ext,
std::u16string* reg_description) {
DCHECK(reg_description);
base::win::RegKey reg_ext(HKEY_CLASSES_ROOT, base::as_wcstr(file_ext),
KEY_READ);
std::wstring reg_app;
if (reg_ext.ReadValue(nullptr, &reg_app) == ERROR_SUCCESS &&
!reg_app.empty()) {
base::win::RegKey reg_link(HKEY_CLASSES_ROOT, reg_app.c_str(), KEY_READ);
std::wstring description;
if (reg_link.ReadValue(nullptr, &description) == ERROR_SUCCESS) {
*reg_description = base::WideToUTF16(description);
return true;
}
}
return false;
}

// Set up a filter for a Save/Open dialog, |ext_desc| as the text descriptions
// of the |file_ext| types (optional), and (optionally) the default 'All Files'
// view. The purpose of the filter is to show only files of a particular type in
// a Windows Save/Open dialog box. The resulting filter is returned. The filter
// created here are:
// 1. only files that have 'file_ext' as their extension
// 2. all files (only added if 'include_all_files' is true)
// If a description is not provided for a file extension, it will be retrieved
// from the registry. If the file extension does not exist in the registry, a
// default description will be created (e.g. "qqq" yields "QQQ File").
// Copied from ui/shell_dialogs/select_file_dialog_win.cc
std::vector<ui::FileFilterSpec> FormatFilterForExtensions(
mlaurencin marked this conversation as resolved.
Show resolved Hide resolved
const std::vector<std::u16string>& file_ext,
const std::vector<std::u16string>& ext_desc,
bool include_all_files,
bool keep_extension_visible) {
const std::u16string all_ext = u"*.*";
const std::u16string all_desc =
l10n_util::GetStringUTF16(IDS_APP_SAVEAS_ALL_FILES);

DCHECK(file_ext.size() >= ext_desc.size());

if (file_ext.empty())
include_all_files = true;

std::vector<ui::FileFilterSpec> result;
result.reserve(file_ext.size() + 1);

for (size_t i = 0; i < file_ext.size(); ++i) {
std::u16string ext = file_ext[i];
std::u16string desc;
if (i < ext_desc.size())
desc = ext_desc[i];

if (ext.empty()) {
// Force something reasonable to appear in the dialog box if there is no
// extension provided.
include_all_files = true;
continue;
}

if (desc.empty()) {
DCHECK(ext.find(u'.') != std::u16string::npos);
std::u16string first_extension = ext.substr(ext.find(u'.'));
size_t first_separator_index = first_extension.find(u';');
if (first_separator_index != std::u16string::npos)
first_extension = first_extension.substr(0, first_separator_index);

// Find the extension name without the preceeding '.' character.
std::u16string ext_name = first_extension;
size_t ext_index = ext_name.find_first_not_of(u'.');
if (ext_index != std::u16string::npos)
ext_name = ext_name.substr(ext_index);

if (!GetRegistryDescriptionFromExtension(first_extension, &desc)) {
// The extension doesn't exist in the registry. Create a description
// based on the unknown extension type (i.e. if the extension is .qqq,
// then we create a description "QQQ File").
desc = l10n_util::GetStringFUTF16(IDS_APP_SAVEAS_EXTENSION_FORMAT,
base::i18n::ToUpper(ext_name));
include_all_files = true;
}
if (desc.empty())
desc = u"*." + ext_name;
} else if (keep_extension_visible) {
// Having '*' in the description could cause the windows file dialog to
// not include the file extension in the file dialog. So strip out any '*'
// characters if `keep_extension_visible` is set.
base::ReplaceChars(desc, u"*", base::StringPiece16(), &desc);
}

result.push_back({desc, ext});
}

if (include_all_files)
result.push_back({all_desc, all_ext});

return result;
}
#endif // BUILDFLAG(IS_WIN)

} // namespace

ElectronDownloadManagerDelegate::ElectronDownloadManagerDelegate(
Expand Down Expand Up @@ -130,6 +243,25 @@ void ElectronDownloadManagerDelegate::OnDownloadPathGenerated(
const bool offscreen = !web_preferences || web_preferences->IsOffscreen();
settings.force_detached = offscreen;

#if BUILDFLAG(IS_WIN)
std::wstring extension = settings.default_path.FinalExtension();
if (!extension.empty() && settings.filters.empty()) {
std::vector<ui::FileFilterSpec> filter_spec = FormatFilterForExtensions(
{base::WideToUTF16(extension)}, {u""}, true, true);

std::string extension_spec_conv(
base::UTF16ToUTF8(filter_spec[0].extension_spec));
const std::vector<std::string> filter{extension_spec_conv};
std::string description_conv(
base::UTF16ToUTF8(filter_spec[0].description));

settings.filters.emplace_back(std::make_pair(description_conv, filter));
mlaurencin marked this conversation as resolved.
Show resolved Hide resolved

std::vector<std::string> all_files{"*.*"};
mlaurencin marked this conversation as resolved.
Show resolved Hide resolved
settings.filters.emplace_back(std::make_pair("All Files", all_files));
mlaurencin marked this conversation as resolved.
Show resolved Hide resolved
}
#endif // BUILDFLAG(IS_WIN)

v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
gin_helper::Promise<gin_helper::Dictionary> dialog_promise(isolate);
Expand Down
2 changes: 2 additions & 0 deletions shell/browser/electron_download_manager_delegate.h
Expand Up @@ -5,6 +5,8 @@
#ifndef ELECTRON_SHELL_BROWSER_ELECTRON_DOWNLOAD_MANAGER_DELEGATE_H_
#define ELECTRON_SHELL_BROWSER_ELECTRON_DOWNLOAD_MANAGER_DELEGATE_H_

#include <vector>
mlaurencin marked this conversation as resolved.
Show resolved Hide resolved
mlaurencin marked this conversation as resolved.
Show resolved Hide resolved

#include "base/memory/weak_ptr.h"
#include "content/public/browser/download_manager_delegate.h"
#include "shell/browser/ui/file_dialog.h"
Expand Down