web_dialog_helper.cc 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. // Copyright (c) 2014 GitHub, Inc. All rights reserved.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "atom/browser/web_dialog_helper.h"
  5. #include <string>
  6. #include <utility>
  7. #include <vector>
  8. #include "atom/browser/atom_browser_context.h"
  9. #include "atom/browser/native_window.h"
  10. #include "atom/browser/ui/file_dialog.h"
  11. #include "atom/common/native_mate_converters/callback.h"
  12. #include "base/bind.h"
  13. #include "base/files/file_enumerator.h"
  14. #include "base/files/file_path.h"
  15. #include "base/strings/utf_string_conversions.h"
  16. #include "chrome/common/pref_names.h"
  17. #include "components/prefs/pref_service.h"
  18. #include "content/public/browser/file_select_listener.h"
  19. #include "content/public/browser/render_frame_host.h"
  20. #include "content/public/browser/render_process_host.h"
  21. #include "content/public/browser/render_view_host.h"
  22. #include "content/public/browser/web_contents.h"
  23. #include "content/public/browser/web_contents_observer.h"
  24. #include "native_mate/dictionary.h"
  25. #include "net/base/mime_util.h"
  26. #include "ui/shell_dialogs/selected_file_info.h"
  27. using blink::mojom::FileChooserFileInfo;
  28. using blink::mojom::FileChooserFileInfoPtr;
  29. using blink::mojom::FileChooserParams;
  30. namespace {
  31. class FileSelectHelper : public base::RefCounted<FileSelectHelper>,
  32. public content::WebContentsObserver {
  33. public:
  34. FileSelectHelper(content::RenderFrameHost* render_frame_host,
  35. std::unique_ptr<content::FileSelectListener> listener,
  36. blink::mojom::FileChooserParams::Mode mode)
  37. : render_frame_host_(render_frame_host),
  38. listener_(std::move(listener)),
  39. mode_(mode) {
  40. auto* web_contents =
  41. content::WebContents::FromRenderFrameHost(render_frame_host);
  42. content::WebContentsObserver::Observe(web_contents);
  43. }
  44. void ShowOpenDialog(const file_dialog::DialogSettings& settings) {
  45. v8::Isolate* isolate = v8::Isolate::GetCurrent();
  46. atom::util::Promise promise(isolate);
  47. auto callback = base::Bind(&FileSelectHelper::OnOpenDialogDone, this);
  48. ignore_result(promise.Then(callback));
  49. file_dialog::ShowOpenDialog(settings, std::move(promise));
  50. }
  51. void ShowSaveDialog(const file_dialog::DialogSettings& settings) {
  52. v8::Isolate* isolate = v8::Isolate::GetCurrent();
  53. v8::Local<v8::Context> context = isolate->GetCurrentContext();
  54. atom::util::Promise promise(isolate);
  55. v8::Local<v8::Promise> handle = promise.GetHandle();
  56. file_dialog::ShowSaveDialog(settings, std::move(promise));
  57. ignore_result(handle->Then(
  58. context,
  59. v8::Local<v8::Function>::Cast(mate::ConvertToV8(
  60. isolate, base::Bind(&FileSelectHelper::OnSaveDialogDone, this)))));
  61. }
  62. private:
  63. friend class base::RefCounted<FileSelectHelper>;
  64. ~FileSelectHelper() override {}
  65. void OnOpenDialogDone(mate::Dictionary result) {
  66. std::vector<FileChooserFileInfoPtr> file_info;
  67. bool canceled = true;
  68. result.Get("canceled", &canceled);
  69. if (!canceled) {
  70. std::vector<base::FilePath> paths;
  71. if (result.Get("filePaths", &paths)) {
  72. for (auto& path : paths) {
  73. file_info.push_back(FileChooserFileInfo::NewNativeFile(
  74. blink::mojom::NativeFileInfo::New(
  75. path, path.BaseName().AsUTF16Unsafe())));
  76. }
  77. if (render_frame_host_ && !paths.empty()) {
  78. auto* browser_context = static_cast<atom::AtomBrowserContext*>(
  79. render_frame_host_->GetProcess()->GetBrowserContext());
  80. browser_context->prefs()->SetFilePath(prefs::kSelectFileLastDirectory,
  81. paths[0].DirName());
  82. }
  83. }
  84. }
  85. OnFilesSelected(std::move(file_info));
  86. }
  87. void OnSaveDialogDone(mate::Dictionary result) {
  88. std::vector<FileChooserFileInfoPtr> file_info;
  89. bool canceled = true;
  90. result.Get("canceled", &canceled);
  91. if (!canceled) {
  92. base::FilePath path;
  93. if (result.Get("filePath", &path)) {
  94. file_info.push_back(FileChooserFileInfo::NewNativeFile(
  95. blink::mojom::NativeFileInfo::New(
  96. path, path.BaseName().AsUTF16Unsafe())));
  97. }
  98. }
  99. OnFilesSelected(std::move(file_info));
  100. }
  101. void OnFilesSelected(std::vector<FileChooserFileInfoPtr> file_info) {
  102. if (listener_) {
  103. listener_->FileSelected(std::move(file_info), base::FilePath(), mode_);
  104. listener_.reset();
  105. }
  106. render_frame_host_ = nullptr;
  107. }
  108. // content::WebContentsObserver:
  109. void RenderFrameHostChanged(content::RenderFrameHost* old_host,
  110. content::RenderFrameHost* new_host) override {
  111. if (old_host == render_frame_host_)
  112. render_frame_host_ = nullptr;
  113. }
  114. // content::WebContentsObserver:
  115. void RenderFrameDeleted(content::RenderFrameHost* deleted_host) override {
  116. if (deleted_host == render_frame_host_)
  117. render_frame_host_ = nullptr;
  118. }
  119. // content::WebContentsObserver:
  120. void WebContentsDestroyed() override { render_frame_host_ = nullptr; }
  121. content::RenderFrameHost* render_frame_host_;
  122. std::unique_ptr<content::FileSelectListener> listener_;
  123. blink::mojom::FileChooserParams::Mode mode_;
  124. };
  125. file_dialog::Filters GetFileTypesFromAcceptType(
  126. const std::vector<base::string16>& accept_types) {
  127. file_dialog::Filters filters;
  128. if (accept_types.empty())
  129. return filters;
  130. std::vector<base::FilePath::StringType> extensions;
  131. int valid_type_count = 0;
  132. std::string description;
  133. for (const auto& accept_type : accept_types) {
  134. std::string ascii_type = base::UTF16ToASCII(accept_type);
  135. auto old_extension_size = extensions.size();
  136. if (ascii_type[0] == '.') {
  137. // If the type starts with a period it is assumed to be a file extension,
  138. // like `.txt`, // so we just have to add it to the list.
  139. base::FilePath::StringType extension(ascii_type.begin(),
  140. ascii_type.end());
  141. // Skip the first character.
  142. extensions.push_back(extension.substr(1));
  143. } else {
  144. if (ascii_type == "image/*")
  145. description = "Image Files";
  146. else if (ascii_type == "audio/*")
  147. description = "Audio Files";
  148. else if (ascii_type == "video/*")
  149. description = "Video Files";
  150. // For MIME Type, `audio/*, video/*, image/*
  151. net::GetExtensionsForMimeType(ascii_type, &extensions);
  152. }
  153. if (extensions.size() > old_extension_size)
  154. valid_type_count++;
  155. }
  156. // If no valid exntesion is added, return empty filters.
  157. if (extensions.empty())
  158. return filters;
  159. filters.push_back(file_dialog::Filter());
  160. if (valid_type_count > 1 || (valid_type_count == 1 && description.empty()))
  161. description = "Custom Files";
  162. DCHECK(!description.empty());
  163. filters[0].first = description;
  164. for (const auto& extension : extensions) {
  165. #if defined(OS_WIN)
  166. filters[0].second.push_back(base::UTF16ToASCII(extension));
  167. #else
  168. filters[0].second.push_back(extension);
  169. #endif
  170. }
  171. // Allow all files when extension is specified.
  172. filters.push_back(file_dialog::Filter());
  173. filters.back().first = "All Files";
  174. filters.back().second.push_back("*");
  175. return filters;
  176. }
  177. } // namespace
  178. namespace atom {
  179. WebDialogHelper::WebDialogHelper(NativeWindow* window, bool offscreen)
  180. : window_(window), offscreen_(offscreen), weak_factory_(this) {}
  181. WebDialogHelper::~WebDialogHelper() {}
  182. void WebDialogHelper::RunFileChooser(
  183. content::RenderFrameHost* render_frame_host,
  184. std::unique_ptr<content::FileSelectListener> listener,
  185. const blink::mojom::FileChooserParams& params) {
  186. file_dialog::DialogSettings settings;
  187. settings.force_detached = offscreen_;
  188. settings.filters = GetFileTypesFromAcceptType(params.accept_types);
  189. settings.parent_window = window_;
  190. settings.title = base::UTF16ToUTF8(params.title);
  191. scoped_refptr<FileSelectHelper> file_select_helper(new FileSelectHelper(
  192. render_frame_host, std::move(listener), params.mode));
  193. if (params.mode == FileChooserParams::Mode::kSave) {
  194. settings.default_path = params.default_file_name;
  195. file_select_helper->ShowSaveDialog(settings);
  196. } else {
  197. int flags = file_dialog::FILE_DIALOG_CREATE_DIRECTORY;
  198. switch (params.mode) {
  199. case FileChooserParams::Mode::kOpenMultiple:
  200. flags |= file_dialog::FILE_DIALOG_MULTI_SELECTIONS;
  201. FALLTHROUGH;
  202. case FileChooserParams::Mode::kOpen:
  203. flags |= file_dialog::FILE_DIALOG_OPEN_FILE;
  204. flags |= file_dialog::FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY;
  205. break;
  206. case FileChooserParams::Mode::kUploadFolder:
  207. flags |= file_dialog::FILE_DIALOG_OPEN_DIRECTORY;
  208. break;
  209. default:
  210. NOTREACHED();
  211. }
  212. auto* browser_context = static_cast<atom::AtomBrowserContext*>(
  213. render_frame_host->GetProcess()->GetBrowserContext());
  214. settings.default_path = browser_context->prefs()
  215. ->GetFilePath(prefs::kSelectFileLastDirectory)
  216. .Append(params.default_file_name);
  217. settings.properties = flags;
  218. file_select_helper->ShowOpenDialog(settings);
  219. }
  220. }
  221. void WebDialogHelper::EnumerateDirectory(
  222. content::WebContents* web_contents,
  223. std::unique_ptr<content::FileSelectListener> listener,
  224. const base::FilePath& dir) {
  225. int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
  226. base::FileEnumerator::INCLUDE_DOT_DOT;
  227. base::FileEnumerator file_enum(dir, false, types);
  228. base::FilePath path;
  229. std::vector<FileChooserFileInfoPtr> file_info;
  230. while (!(path = file_enum.Next()).empty()) {
  231. file_info.push_back(FileChooserFileInfo::NewNativeFile(
  232. blink::mojom::NativeFileInfo::New(path, base::string16())));
  233. }
  234. listener->FileSelected(std::move(file_info), dir,
  235. FileChooserParams::Mode::kUploadFolder);
  236. }
  237. } // namespace atom